Skip to content

ggui keys register

read as .md

ggui keys register ships your publisher Ed25519 public key to the marketplace registry’s POST /author-keys endpoint. After this, every subsequent ggui gadget publish / ggui blueprint publish whose bundle is signed by the matching private key validates against the registry’s stored row.

You run ggui keys register after ggui gadget publish (or ggui blueprint publish) has auto-generated a keypair on disk for a given scope. The publish flow does the keypair generation automatically on first run, then signs the artifact, then POSTs to /publish. The registry rejects a signed publish whose publicKeyId isn’t already registered for the caller’s identity — that’s the moment to run this command.

The typical bootstrap sequence for a new publisher:

Terminal window
# 1. First publish under @my-org auto-generates
# ~/.ggui/keys/@my-org/{private,public}.key and signs the bundle.
ggui gadget publish
# → Error: unknown_key — public key not registered for this publisher.
# 2. Register the public half with the registry.
ggui keys register --scope @my-org
# 3. Re-run the publish. The signature now verifies.
ggui gadget publish

After step 2, every future publish under @my-org from this machine (and any other machine you copy ~/.ggui/keys/@my-org/private.key to) validates without a second register call.

ggui keys register --scope <@scope> [--registry <url>] [--auth=bearer [--token <token>]]
FlagRequiredPurpose
--scopeyesnpm scope the key was generated under, prefixed with @ (e.g. --scope @my-org). The CLI reads ~/.ggui/keys/<scope>/public.key — must already exist (see “When to use it” above).
--registrynoOverride the registry URL. Same resolution chain as ggui gadget publish: --registry flag, then GGUI_REGISTRY env var, then ggui.json#registry walking up from CWD. No hard-coded default — pick a registry deliberately so a typo doesn’t accidentally register against prod.
--auth=bearernoSend an explicit bearer token instead of the stored ggui login session — for self-hosted registries that authenticate with a static publish token. Pair with --token <token> or set GGUI_REGISTRY_TOKEN. Same flags the publish verbs take.
--tokennoThe bearer token for --auth=bearer (overrides GGUI_REGISTRY_TOKEN).

ggui keys register uses your stored ggui login session by default — the same credential ggui gadget publish sends to /publish, and the one the hosted registry’s /author-keys route authenticates (see Marketplace § Auth). The CLI reads ~/.ggui/auth.json, refreshes the access token automatically when it has expired, and sends it as Authorization: Bearer <token> — the only identity surface; the request body carries only publicKeyBase64, never the caller’s user id. The server derives the publisher subject from the verified credential and the keyId from the raw public-key bytes.

Self-hosted operators running their own @ggui-ai/registry-server deployments pass --auth=bearer --token <token> (or set GGUI_REGISTRY_TOKEN) — the same escape hatch the publish flow takes (see ggui marketplace for the parallel).

Success — first-write (HTTP 201):

Terminal window
$ ggui keys register --scope @my-org
Registered publisher key for @my-org.
registry: https://registry.ggui.ai
subject: <your-user-id>
keyId: a1b2c3d4e5f60718

Idempotent re-register (HTTP 200) — same public-key bytes for (subject, keyId) already on file:

Terminal window
$ ggui keys register --scope @my-org
Already registered publisher key for @my-org.
registry: https://registry.ggui.ai
subject: <your-user-id>
keyId: a1b2c3d4e5f60718

Both exit 0. Safe to run unconditionally in CI bootstrap scripts.

CodeExitMeaning
no-registry1No registry URL resolved. Pass --registry, set GGUI_REGISTRY, or add registry to ggui.json.
invalid-registry1Resolved URL is malformed.
no-keypair1No ~/.ggui/keys/<scope>/public.key on disk. Run ggui gadget publish or ggui blueprint publish under this scope first — first-publish generates the keypair as a side-effect.
auth_failed1The stored login session expired (or its refresh was rejected). Run ggui login again.
auth_config_missing1No stored login session (~/.ggui/auth.json missing or unreadable) — run ggui login first. Or --auth=bearer was passed without --token / GGUI_REGISTRY_TOKEN.
network-error1fetch threw — DNS, TLS, or connection refused.
unauthorized1Registry rejected the bearer credential (HTTP 401). Re-run ggui login (the session may have been revoked) — or, for self-hosted registries, check the --auth=bearer token.
invalid_request1Registry refused the body (HTTP 400). The public-key file is corrupted or the wrong length — regenerate by deleting ~/.ggui/keys/<scope>/ and re-running ggui gadget publish.
key_conflict3Registry holds a different public key for the same (subject, keyId) tuple (HTTP 409). Vanishingly rare — a 64-bit keyId SHA-256 truncation collision OR a stale row from a previous owner. Exit 3 is distinct so scripts can detect it without parsing the message.
http-error1Any other non-2xx response (typically 5xx). Check the message for the registry’s error string; retry transient failures.
bad-response1Registry returned a 2xx with a malformed body, or any status with invalid JSON. Almost always a registry-side bug — re-run; if persistent, the registry is misconfigured.

The structured error field of the registry’s response body (closed enum: unauthorized / invalid_request / key_conflict / server_error) is preferred over status-code mapping when the response carries a well-formed body — key_conflict returned with a non-409 status still surfaces as key_conflict on the CLI side.

The on-disk private key under ~/.ggui/keys/<scope>/private.key is mode 0o600 — treat it like a long-lived password. Copying it between machines lets you publish from CI without re-registering. Losing it means generating a new keypair under the same scope: when the new public key gets registered, both old + new keys are valid (publish flow pins the signing key onto each ArtifactVersionRow at publish time, so historical versions still verify under the previous key). To rotate out the old key entirely, register the new one + remove the old row server-side (operator-only).

See Marketplace § Trust model for the full per-author-key + per-version pinning design and the install-time two-leg verification (SHA-384 + Ed25519).