Marketplace Registry
read as.mdThe 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>.
Three nouns, three roles
Section titled “Three nouns, three roles”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".
Browse the registry
Section titled “Browse the registry”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.
Registry resolution
Section titled “Registry resolution”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 defaulthttps://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.
CLI surface — two sibling verbs
Section titled “CLI surface — two sibling verbs”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.
Author workflow
Section titled “Author workflow”# === Gadget ===
# 1. Scaffoldggui gadget create @my-org/weather-cardcd 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-formcd 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 publishThe 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.
Consumer workflow
Section titled “Consumer workflow”# Search the registry — each namespace hard-locks its kind filter.ggui gadget search "weather"ggui gadget search --hook=useWeatherCardggui blueprint search "login"
# Install. Verifies signature + SRI before writing anything.ggui gadget install @my-org/weather-card@0.1.0 # → ggui.json#app.gadgetsggui 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.
Installing gadgets from the ggui console
Section titled “Installing gadgets from the ggui console”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.jsonalways wins, and the web install path never writes that column — attempting to install a package that’s already declared there is rejected withpackage_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.
Trust model
Section titled “Trust model”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:
- bundleSha384 recompute. Compute
sha384(payload), compare againstsignature.bundleSha384. Fast tamper detection. Runs for both algorithms. - Cryptographic verify. Dispatches on
signature.algorithm:ed25519→derivePublicKeyId(returnedPubkey) === signature.publicKeyId(rejects key-swap attacks), thenverifyBundleEd25519. If the registry didn’t pin a public key for the version, the CLI warns and continues by default;--strictturns the warn into a hard fail.sigstore-cosign→verifyBundleSigstorevalidates 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.
Subresource integrity (bundleSri)
Section titled “Subresource integrity (bundleSri)”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.
Versioning
Section titled “Versioning”- Semver.
1.2.3-alpha.1+build.42form.parseArtifactManifestrejects ranges,latest, leading zeros (applies to gadgets and blueprints alike). - Immutable.
POST /publishof an existing(scope, name, version)triple returns409 version_exists. - Yank, not delete. Yanked versions return
410 Gonewith the manifest still in the body for audit. Hard delete is intentionally NOT supported.
HTTP API surface (core routes)
Section titled “HTTP API surface (core routes)”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).
What’s in ggui.json after install
Section titled “What’s in ggui.json after install”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.
Agent-facing MCP tools
Section titled “Agent-facing MCP tools”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.
Roadmap
Section titled “Roadmap”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 opaquenot_found. Org-membership admission (a teammate reading an org-mate’s private versions) is the next step. - Yank UX. No
ggui gadget yanksubcommand yet — yanking goes through registry admin tooling. The read + install sides already handle410 Gonecorrectly (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.
Reference
Section titled “Reference”- Browse. hub.ggui.ai — the public registry browser (search, per-version manifests, install commands).
- Protocol spec. Protocol overview
- Self-hosting. Self-hosted registry — byte-compatible
@ggui-ai/registry-serverfor air-gapped or local-dev use. - Glossary. gadget / tool / blueprint — the three-noun vocabulary used throughout.
- Author manifest.
@ggui-ai/artifact-manifest—gadgetManifestSchema/blueprintManifestSchema. - Signing.
@ggui-ai/gadget-signing— universal Ed25519 (browser + Node). - Publisher key registration.
ggui keys register.
Footnotes
Section titled “Footnotes”-
Public for
visibility: "public"artifacts. Avisibility: "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 samenot_foundresponse 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” = theggui loginsession token, verified at the hosted deployment’s gateway (see Auth). ↩