Skip to content

feat(api-keys): add ulink api-keys command to manage client SDK keys - #5

Merged
mohn93 merged 2 commits into
mainfrom
feat/cli-api-keys-command
Sep 6, 2026
Merged

feat(api-keys): add ulink api-keys command to manage client SDK keys#5
mohn93 merged 2 commits into
mainfrom
feat/cli-api-keys-command

Conversation

@mohn93

@mohn93 mohn93 commented Sep 6, 2026

Copy link
Copy Markdown
Member

Why

The CLI had no way to manage a project's client SDK API keys — the key an app passes to UlinkSDK.initialize. Integrators had to open the dashboard, or use the MCP list_api_keys/create_api_key tools, which currently return 401 on the api-key/subscription routes. This adds a first-class CLI path that authenticates with the user's own ulink login session (or --api-key).

Command

ulink api-keys list    [--project-id <id>] [-p <dir>] [--json]
ulink api-keys create  --name "<name>" [--project-id <id>] [-p <dir>] [--json]
ulink api-keys revoke  <keyId>
  • create prints the full key value once, with a "shown only once, cannot be retrieved again" warning; list returns metadata only (name, prefix, id, created) — the secret is never exposed there.
  • Project id resolves from --project-id, then the saved directory config (ulink project set), then a single-project auto-select; ambiguity prints the choices and asks for --project-id.
  • --json on list/create for scripting.

Implementation

  • ULinkApiClient gains listApiKeys / createApiKey / revokeApiKey on top of the existing auth (_authHeaders: user JWT with refresh, or x-app-key), plus a _deleteWithRetry helper and api-key-specific error messages (a 401 points the user at ulink login).
  • Response parsing is schema-tolerant: unwraps data / apiKeys / keys, best-effort detection of the secret field on create, and if the field can't be identified it prints the whole response rather than dropping the key.
  • bin/ulink.dart: api-keys command with list / create / revoke subcommands, help routing, and a root help line. New ApiKeysCommand in lib/commands/.

Testing

Unit tests cover the network-free contract paths (help → 0; create without name / blank name → 2; revoke without key id → 2; unknown action → 2). Manually smoke-tested the compiled binary: --help for the command and subcommands, arg validation exit codes, and the unauthenticated auth-error message.

dart analyze lib bin test clean. dart test — 297 pass.

Notes

The CLI had no way to manage a project's client SDK API keys, so integrators
had to open the dashboard (or use the MCP tools, which currently 401 on the
api-key/subscription routes). Add list / create / revoke:

  ulink api-keys list    [--project-id <id>] [-p <dir>] [--json]
  ulink api-keys create  --name "<name>" [--project-id <id>] [-p <dir>] [--json]
  ulink api-keys revoke  <keyId>

- ULinkApiClient gains listApiKeys / createApiKey / revokeApiKey backed by the
  existing user-JWT (from `ulink login`) or --api-key auth, with a DELETE retry
  helper and API-key-specific error messages (a 401 points at `ulink login`).
- create surfaces the full key value once, with a "shown only once" warning;
  list shows metadata only. Response parsing is schema-tolerant (unwraps
  data/apiKeys/keys, best-effort field detection) and never silently drops the
  secret.
- Project id resolves from --project-id, the saved directory config, or a
  single-project auto-select; ambiguity prints the choices.
- Tests cover the network-free contract paths (help, required-arg validation,
  unknown action). README documents the command.
The backend DELETE /api-keys/:id resolves the owning project from
req.user.projectId || ?projectId= || x-project-id. The CLI authenticates
with a Supabase user JWT that carries no project scope, and revokeApiKey
sent no projectId, so every 'ulink api-keys revoke' failed with 400
'Project ID is required'.

Resolve the project id the same way list/create do (--project-id, then
saved directory config, then sole-project auto-select) and pass it as the
?projectId= query param. Register --project-id/-p on the revoke subcommand,
and add a 400 branch to the API-key error mapper that surfaces the server
message instead of a raw status+body.
@mohn93
mohn93 merged commit 02b68f9 into main Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant