feat(broker+cli): apikey create/list/revoke verbs (v0.2.0 #71)
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

Issuance flow over WS for now (REST endpoints come next slice).
Plaintext secret returned ONCE on create — never recoverable.

- broker: 3 WS handlers (apikey_create/list/revoke), wire types in
  union, audit log on issuance + revoke
- ws-client: apiKeyCreate/List/Revoke with resolver maps, response
  dispatch
- CLI: claudemesh apikey create <label> [--cap a,b] [--topic c,d]
  [--expires ISO]; list shows status, scope, last-used; revoke by id
- policy: apikey create + revoke prompt by default (issuing or
  disabling a credential is meaningful)

Default capability set is "send,read" — least privilege for unscoped
keys (admin must explicitly opt-in).

Spec: .artifacts/specs/2026-05-02-v0.2.0-scope.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-05-02 02:13:12 +01:00
parent f45380d231
commit 13d691980a
7 changed files with 350 additions and 0 deletions

View File

@@ -102,6 +102,11 @@ Profile / presence (resource form)
claudemesh group join @<name> join a group (--role X)
claudemesh group leave @<name> leave a group
API keys (REST + external WS auth, v0.2.0)
claudemesh apikey create <label> issue [--cap send,read] [--topic deploys]
claudemesh apikey list show keys (status, last-used, scope)
claudemesh apikey revoke <id> revoke a key
Topic (conversation scope, v0.2.0)
claudemesh topic create <name> create a topic [--description --visibility]
claudemesh topic list list topics in the mesh
@@ -509,6 +514,24 @@ async function main(): Promise<void> {
break;
}
// apikey — REST + external WS bearer tokens (v0.2.0)
case "apikey": case "api-key": {
const sub = positionals[0];
const f = {
mesh: flags.mesh as string,
json: !!flags.json,
cap: flags.cap as string,
topic: flags.topic as string,
expires: flags.expires as string,
};
const arg = positionals[1] ?? "";
if (sub === "create") { const { runApiKeyCreate } = await import("~/commands/apikey.js"); process.exit(await runApiKeyCreate(arg, f)); }
else if (sub === "list") { const { runApiKeyList } = await import("~/commands/apikey.js"); process.exit(await runApiKeyList(f)); }
else if (sub === "revoke") { const { runApiKeyRevoke } = await import("~/commands/apikey.js"); process.exit(await runApiKeyRevoke(arg, f)); }
else { console.error("Usage: claudemesh apikey <create|list|revoke>"); process.exit(EXIT.INVALID_ARGS); }
break;
}
// topic — conversational primitive within a mesh (v0.2.0)
case "topic": {
const sub = positionals[0];