Skip to content

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.

NounRoleWhere it lives
gadgetingredient — composed into a UIclientCapabilities.gadgets (client-side hook)
toolaction — invoked by the agentagentCapabilities.tools (agent-side function)
blueprintrecipe — returned directly as a cached UITSX 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".

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

The CLI resolves the registry URL per verb (highest precedence first):

  • publish / search--registry <url> flag → GGUI_REGISTRY env → ggui.json#registry. Errors if none is set.
  • install--registry <url> flag → ggui.json#registryGGUI_REGISTRY env → built-in default https://registry.ggui.ai.

Note the orders differ: with both the env var and ggui.json#registry set, publishes follow the env var while installs follow ggui.json.

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.

VerbWhat it does
ggui gadget create <id>Scaffold ggui.gadget.json + src/index.ts hook stub
ggui gadget publishBundle (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 blueprint create <id>Scaffold ggui.blueprint.json + src/blueprint.tsx + optional contract stub
ggui blueprint publishSign + 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.

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.

visibilityAlgorithmTrust rootKey/identity binding
publicsigstore-cosignFulcio cert + Rekor inclusion proofOIDC subject embedded in the Fulcio leaf cert
privateEd25519AuthorKeys table (per-publisher pinned key)publicKeyId = base64(sha256(publicKey))[:16] (16-char fingerprint)

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):

MethodPathAuthPurpose
GET/search?q=&kind=&hook=&tag=&author=&…publicList artifacts. AND-semantics across filters.
GET/pkg/{scope}/{name}/{version}mixed1Read a single version’s manifest + fetchable URLs + signing metadata.
POST/publishCLI sessionUpload {manifest, bundle?, bundleSha384?, signature}.
POST/conformance/checkCLI sessionDry-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:

ToolPurpose
ggui_list_featured_blueprintsEnumerate builder-curated featured blueprints (no filters; takes empty input).
ggui_search_blueprintsSemantic search across the app’s blueprints (manifest + cached generations).
ggui_render_blueprintResolve 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" today rejects only on missing JWT; per-org group enforcement on the read handler 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; an account credential is required for visibility: "private". “CLI session” = the ggui login session token, verified at the hosted deployment’s gateway (see Auth).