Skip to content
Sneak peek — you found hosted ggui early · official launch soon

Marketplace Registry

read as .md

The marketplace registry is where authors publish ggui artifacts and consumers install them. It’s HTTP-only — distinct from the WebSocket protocol — and hosts immutable, signed, content-addressable bundles + manifests. The registry is for published, signed artifacts; to move your own deployment’s cached blueprints between servers, use ggui export-pool and load the pool on the other server with ggui serve --seed-pool <dir>.

Marketplace docs and registry wire shapes use exactly three terms. Do not say “plugin” / “library” / “package” — see the glossary for the canonical vocabulary.

Noun Role Where it lives
gadget ingredient — composed into a UI clientCapabilities.gadgets (client-side hook)
tool action — invoked by the agent agentCapabilities.tools (agent-side function)
blueprint recipe — returned directly as a cached UI TSX source + optional contract + variance hints

A gadget is a GadgetDescriptor (see Gadgets SDK) packaged for distribution. A blueprint is a (source, contract?, fixtureProps?, variance?) quad. Both ride ArtifactManifest — a discriminated union on kind: "gadget" | "blueprint".

hub.ggui.ai is the public, unauthenticated browser over the same registry — no account, no CLI. It lists recently published artifacts, has a search page (/search) with filters for kind (blueprint / gadget) and MCP binding (tool=, server= — find every artifact that works with a given tool or server), and a per-version page for each artifact showing the manifest, signing metadata (algorithm, verified-publisher badge where applicable), and a ready-to-copy install command. It reads only the registry’s public CORS-open endpoints, so it shows exactly what ggui gadget search / ggui blueprint search show — same catalog, browsable form.

The hosted registry lives at registry.ggui.ai; self-hosted registries work identically (see Self-Hosted Registry).

Every verb resolves the registry URL through the same chain (highest precedence first): --registry <url> flag → GGUI_REGISTRY env → ggui.json#registry (walking up from CWD). The verbs differ only on a full miss, following the npm model:

  • Read verbs (install, search) fall back to the public default https://registry.ggui.ai — zero config works out of the box.
  • Write verbs (publish, keys register) error if none is set — a publish target must be opted into explicitly.

Two parallel namespaces, one per artifact kind. Each verb hard-enforces its kind from the manifest — running a gadget verb in a blueprint repo (or vice versa) emits a friendly redirect and exits non-zero.

Verb What it does
ggui gadget create <id> Scaffold ggui.gadget.json + src/index.ts hook stub
ggui gadget publish Bundle (esbuild), sign, upload from CWD
ggui gadget search [query] List gadgets (--kind=gadget hard-locked)
ggui gadget install <id>@<v> Verify + register into ggui.json#app.gadgets[]
ggui gadget uninstall <id>@<v> Remove the installed (package, version) entry from ggui.json#app.gadgets[] (reverse of install; the emptied array is dropped)
ggui blueprint create <id> Scaffold ggui.blueprint.json + src/blueprint.tsx + optional contract stub
ggui blueprint publish Sign + upload from CWD (no bundling — TSX rides inline on the manifest)
ggui blueprint search [query] List blueprints (--kind=blueprint hard-locked)
ggui blueprint install <id>@<v> Materialize to .ggui/installed-blueprints/<scope>__<name>__<version>/ for discoverLocalUis to pick up
ggui blueprint uninstall <id>@<v> Remove a locally-installed blueprint (deletes the materialized directory; when it was the last installed blueprint, also strips the install-managed glob from ggui.json#blueprints.include)

The two namespaces are deliberately separate sibling verbs — NOT an umbrella ggui artifact that auto-detects kind. Gadgets and blueprints share a registry but mean different things to the install flow, so the kind discriminator is surfaced at the verb level.

Terminal window
# === Gadget ===
# 1. Scaffold
ggui gadget create @my-org/weather-card
cd weather-card
# 2. Implement the hook + edit ggui.gadget.json (description, usage,
# example, gotchas — these are required and shape the LLM's prompt
# at code-gen time, so write them carefully).
# 3. Authenticate (the publish flow reuses the session `ggui login`
# stores at ~/.ggui/auth.json, refreshing it automatically).
ggui login
# 4. Dry-run validates bundle + conformance preflight without
# uploading. Recommended on every change.
ggui gadget publish --dry-run
# 5. Upload. First publish auto-generates an Ed25519 keypair and
# registers your public key with the registry.
ggui gadget publish
# === Blueprint ===
# 1. Scaffold (different template — TSX source + contract stub)
ggui blueprint create @my-org/login-form
cd login-form
# 2. Edit src/blueprint.tsx + the contract; manifest carries `variance`
# hints that prime the matcher.
# 3. Same auth setup as gadgets.
# 4. Publish — no bundling step; TSX rides inline on the manifest.
ggui blueprint publish

The publish CLIs run a shared internal pipeline that asserts manifest.kind === verb-kind, runs the registry’s conformance gate, signs (gadget: sha384(bundleBytes) of the compiled bundle; blueprint: canonical JSON of the manifest), and POSTs {manifest, bundle?, signature} to /publish.

Terminal window
# Search the registry — each namespace hard-locks its kind filter.
ggui gadget search "weather"
ggui gadget search --hook=useWeatherCard
ggui blueprint search "login"
# Install. Verifies signature + SRI before writing anything.
ggui gadget install @my-org/weather-card@0.1.0 # → ggui.json#app.gadgets
ggui blueprint install @my-org/login-form@0.1.0 # → .ggui/installed-blueprints/

Install always runs both verification legs (bundleSha384 recompute + Ed25519). If the registry didn’t pin a public key on the version (legacy / pre-key-pinning artifacts), the default behavior is warn-and-continue; pass --strict to turn that into a hard exit 1 instead.

The CLI is the config-managed path (ggui.json#app.gadgets[], versioned alongside your deployment). On hosted ggui there’s also a per-app web path: an app’s Marketplace tab in the ggui console has a Blueprints / Gadgets toggle over the same registry search — pick Gadgets, find a package, and Install. It writes the latest version onto that app’s row in a separate provenance-split table, distinct from the CLI-managed ggui.json column. The app’s Gadgets tab shows the resolved catalog an agent actually gets from ggui_list_gadgets, and lets you remove a web-installed gadget or pin it to a different version (re-installing at a picked version is the pin operation).

Resolution when both paths touch the same package follows a fixed precedence, stdlib floor < console-installed < ggui.json-declared:

  • A package declared in ggui.json always wins, and the web install path never writes that column — attempting to install a package that’s already declared there is rejected with package_declared, pointing you back to the config-managed path instead.
  • A console-installed package overrides the stdlib floor but is itself overridden the moment the same package is declared in ggui.json.
  • Nothing installed anywhere falls through to the stdlib floor.

Use the console path for exploration and per-app changes without a deploy; use ggui gadget install when the gadget set needs to be versioned with the rest of your app’s config.

publish (always) and private-row reads accept --auth=bearer --token=<value> (or GGUI_REGISTRY_TOKEN) for self-hosted / local-development flows. Without the flag, the CLI sends the access token from your stored ggui login session (~/.ggui/auth.json), refreshing it automatically when expired. The hosted registry’s authenticated endpoints (/publish, /conformance/check, /author-keys) accept exactly this credential — run ggui login once and the default auth path works end-to-end. Third parties running their own @ggui-ai/registry-server deployments pass --auth=bearer.

The signing posture is determined by manifest.visibility. Both posters share the same bundleSha384-recompute fast leg; they differ only on the cryptographic verify leg.

visibility Algorithm Trust root Key/identity binding
public sigstore-cosign Fulcio cert + Rekor inclusion proof OIDC subject embedded in the Fulcio leaf cert
private Ed25519 AuthorKeys table (per-publisher pinned key) publicKeyId = base64(sha256(publicKey))[:16] (16-char fingerprint)

The pairing is enforced server-side at publish, not just by the CLI: POST /publish rejects a mismatched pair with 400 visibility_algorithm_mismatch. public requires sigstore-cosign so every publicly listable artifact has a transparency-log entry; private requires an Ed25519 author key. A hand-rolled request can’t opt a public artifact out of the transparency log by signing it with a private key.

Per-author Ed25519 keys (private gadgets). On first publish, the CLI generates a 32-byte private key at ~/.ggui/keys/<scope>/private.key. The public key (base64(pk)) is registered with the registry under the publisher’s server-side account subject — see ggui keys register for the explicit verb and the registration error matrix. The registry copies the publishing key onto every ArtifactVersionRow it writes (field: authorPublicKey, base64-encoded raw Ed25519 public key bytes). A later key rotation does NOT invalidate historical versions — each version verifies against the exact key that signed it.

Sigstore (public gadgets). Public artifacts sign via the keyless sigstore flow — the CLI obtains a short-lived Fulcio certificate bound to your OIDC identity and submits the signature to Rekor for a public transparency-log entry. There is no long-lived author key for the public path; trust roots in Fulcio + Rekor.

Two-leg verification on install. Both must pass before any disk write:

  1. bundleSha384 recompute. Compute sha384(payload), compare against signature.bundleSha384. Fast tamper detection. Runs for both algorithms.
  2. Cryptographic verify. Dispatches on signature.algorithm:
    • ed25519derivePublicKeyId(returnedPubkey) === signature.publicKeyId (rejects key-swap attacks), then verifyBundleEd25519. If the registry didn’t pin a public key for the version, the CLI warns and continues by default; --strict turns the warn into a hard fail.
    • sigstore-cosignverifyBundleSigstore validates the Fulcio cert chain + Rekor inclusion. By default any valid OIDC identity is accepted; pass --verify-identity <subject-or-/regex/> to additionally pin the Fulcio leaf’s subject claim.

Publish computes sha384-<base64(sha384(bundle))> server-side. The read endpoint returns it; install writes it onto GadgetDescriptor.bundleSri; the iframe runtime emits a <link rel="modulepreload" integrity="..."> tag before dynamic-importing the bundle. A CDN compromise can’t silently swap the bundle without breaking SRI.

CSP script-src auto-derives from each entry’s bundleUrl origin — no per-registry allowlist needed.

  • Semver. 1.2.3-alpha.1+build.42 form. parseArtifactManifest rejects ranges, latest, leading zeros (applies to gadgets and blueprints alike).
  • Immutable. POST /publish of an existing (scope, name, version) triple returns 409 version_exists.
  • Yank, not delete. Yanked versions return 410 Gone with the manifest still in the body for audit. Hard delete is intentionally NOT supported.

The core routes (version-listing GET /pkg/{scope}/{name} and the /bundles/* fetch routes ride alongside):

Method Path Auth Purpose
GET /search?q=&kind=&hook=&tag=&author=&tool=&server=&… public List artifacts. AND-semantics across filters. tool / server filter on an artifact’s MCP tool bindings (find everything that works with a given tool or server).
GET /pkg/{scope}/{name}/{version} mixed1 Read a single version’s manifest + fetchable URLs + signing metadata.
POST /publish CLI session Upload {manifest, bundle?, bundleSha384?, signature}.
POST /conformance/check CLI session Dry-run the conformance gate without publishing.

Wire shapes are defined in @ggui-ai/registry-core (shared by the hosted cloud and the self-hosted server).

Gadgets land on app.gadgets[]:

{
"app": {
"gadgets": [
{
"package": "@my-org/weather-card",
"version": "0.1.0",
"bundleUrl": "https://registry.ggui.ai/bundles/...",
"bundleSri": "sha384-Wj1...",
"exports": [
{
"hook": "useWeatherCard",
"description": "Renders a weather card.",
"usage": "Use to surface current weather.",
"example": { "city": "Berlin" }
}
]
}
]
}
}

Blueprints materialize to disk under .ggui/installed-blueprints/<scope>__<name>__<version>/:

.ggui/installed-blueprints/
└── my-org__weather-card__0.1.0/
├── index.tsx # the published TSX source
└── ggui.ui.json # generated UI manifest (id, contract, entryPoint)

The install adds .ggui/installed-blueprints/**/ggui.ui.json to ggui.json#blueprints.include automatically. The existing discoverLocalUis glob loader picks them up at boot — no special-case install path on the server side.

The marketplace surface for the agent (not the publisher CLI) lives on the same MCP endpoint as everything else. Three tools enumerate and materialize blueprints from the registry-backed catalog:

Tool Purpose
ggui_list_featured_blueprints Enumerate builder-curated featured blueprints (no filters; takes empty input).
ggui_search_blueprints Semantic search across the app’s blueprints (manifest + cached generations).
ggui_render_blueprint Resolve a registered blueprint by id to its compiled JS bundle — returns {blueprintId, blueprintName, code, contentType}; no session is created.

Full wire shapes + agent-side usage: MCP Protocol Reference. Gadgets are advertised via ggui_list_gadgets instead — the catalog rides on the contract, not as a separate “render” call.

Tracked-but-not-yet-shipped surfaces, in rough priority order:

  • Per-org private artifacts. visibility: "private" reads are enforced per-subject today — the publisher or the scope owner reads a private version; everyone else gets an opaque not_found. Org-membership admission (a teammate reading an org-mate’s private versions) is the next step.
  • Yank UX. No ggui gadget yank subcommand yet — yanking goes through registry admin tooling. The read + install sides already handle 410 Gone correctly (install exits 1 with a “yanked” message).
  • Cross-registry federation / mirror. Single-registry installs only; federated lookup is a separate slice.
  • Per-user authentication. Author identity is the hosted account’s subject today; team workflows share one account. Per-user identity within a team is future work.
  1. Public for visibility: "public" artifacts. A visibility: "private" version is served only to the verified account that published it or that owns the artifact’s scope; every other request — anonymous or authenticated — receives the same not_found response as a genuinely missing artifact (the version list likewise filters versions you cannot read), so the API never confirms a private artifact exists. “CLI session” = the ggui login session token, verified at the hosted deployment’s gateway (see Auth).