Skip to content

feat: secrets and automation writes from the CLI (write API, 0.1.33) - #53

Merged
sre-helmcode merged 7 commits into
mainfrom
feat/write-api
Oct 14, 2026
Merged

sre-helmcode merged 7 commits into
mainfrom
feat/write-api

Conversation

@sre-helmcode

Copy link
Copy Markdown
Contributor

What

The CLI half of the Phase 3 write API (spec: helmcode/nan-devops AGENT-AUTOMATIONS-PHASE3-WRITE-API.md, server: helmcode/nan-cloud-api#348). Version 0.1.33 (merging publishes).

New commands:

  • nan secrets show NAME|ID [--json], nan secrets set NAME (--value-file PATH | --value-stdin) [--kind generic|provider_key] [--provider P] [--base-url URL] [--description D] [--json], nan secrets edit NAME|ID [--description D] [--base-url URL] [--json], nan secrets rm NAME|ID [--yes] (aliases delete, remove).
  • nan automations create -f FILE|-, edit NAME|ID -f FILE|-, enable NAME|ID, disable NAME|ID, rm NAME|ID [--yes] (YAML or JSON, sent as written; --json prints the API answer).
  • nan automations triggers ls|add|edit|rotate|rm (rotate takes --url, --secret (default both) and --no-grace).
  • updated_by shown as "last changed from: Browser / CLI / API token" in secrets ls/show, automations show and triggers ls.

Secret values:

  • Never an argument: --value-file or a pipe into --value-stdin; --value-stdin refuses a terminal (exit 64). One trailing \n / \r\n dropped; empty, non-UTF-8 and >16 KiB refused. An extra positional argument or a NAME that looks like a key is refused without being echoed, with the "revoke it" hint.
  • set creates (POST with a generated Idempotency-Key, re-sent with the same key once if the answer is lost) or replaces the value (PUT, plus PATCH for description/base URL). Kind/provider of an existing secret cannot change (usage error). Request types redact the value in %v.

Webhook URL and signing secret: printed once to stdout (never stderr, never a file), with "Store these now: they are never shown again" on stderr; --json prints the raw one-time object.

Auth for the secrets and write commands: --token-file, then the CLI session, then NAN_TOKEN, then a saved platform token. sk- keys are refused locally, never sent. Gating: for a session, features.write_api from /api/auth/me; for a token, the request is sent and the refusal mapped. Write API off (flag false, /v1/secrets 404, Phase 2 browser_session_required) keeps exit 64 pointing at the portal. Mapped errors: credential_not_allowed, workspace_key_not_allowed, token_scope_denied (names the scope, says new scopes are added in the portal from a browser), rate_limited (69), name_taken, too_many_secrets, idempotency_key_reused, secret_in_use / secrets_too_large (list the automations), platform_access_required, secrets_unavailable, 404s naming the automation/trigger. Approving stays portal-only.

Help and README: the new commands, the scopes, and the warning that a credential that can write automations can reach every secret (log in on your own machine; tokens with only the needed scopes in workspaces/CI).

Tests

  • cmd/writeapi_test.go: an httptest fake of the write API with cloud-api's golden responses (internal/api/testdata/phase2, refreshed from #348 incl. trigger_create.json, trigger_rotate.json): every verb end to end through the real command tree, TTY refusal, value sources and trimming, value never in stdout/stderr/errors/other requests/files under HOME, idempotency key reused on a lost answer, kind/provider fixed, flag off (session and token) exit 64, credential order and sk- refusal, every error mapping, one-time trigger secrets printed once and never written, rotate bodies, golden views (cmd/testdata/writeapi).
  • Phase 2 tests updated: approving stays portal-only; secrets/automation writes no longer are.
  • gofmt, go vet ./..., go test ./..., cross-compile linux/windows: green locally.

Deploy

Merge = release 0.1.33 (tag workflow). Safe before the server flips WRITE_API_ENABLED: the commands exit 64 with the portal explanation until features.write_api is true.

🤖 Generated with Claude Code

barckcode and others added 7 commits October 14, 2026 15:53
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (0.1.33)

Write API (AGENT-AUTOMATIONS-PHASE3-WRITE-API.md, nan-cloud-api#348):
secrets and automations written from a CLI session or a platform token
with secrets:read, secrets:write or automations:write. Values only from a
file or a pipe; webhook URL and signing secret printed once. Exit 64 and
the portal when the account does not have the write API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…_found, size edges; QA notes

- the retry test now forces a fresh connection per request, so the second
  POST is the CLI's (net/http retries only on a reused one); lostAnswer unit test
- a token with secrets:write and no secrets:read creates, and names the scope it lacks
- set on an existing secret with --base-url, and a PATCH that fails after the PUT
- not_found on PUT/PATCH/DELETE; 16 KiB boundary; non-UTF-8
- a pasted value as a subcommand is not echoed; reads with the write API off say reading
- rotate --json prints the store-now note only when something was issued; --json and token tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s before reading the value

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oogle hint is asserted

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng-secret-file/--signing-secret-stdin

Stripe issues the whsec_, so cloud-api requires it in the rotate body and
the CLI had no way to send it: the default rotate and --secret always
failed for Stripe. The secret is read like a secret value (file or pipe,
never a terminal or an argument), sent once, never printed, and a 400
on signing_secret now explains what to do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…GitHub/NaN one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sre-helmcode
sre-helmcode merged commit 6e58b09 into main Oct 14, 2026
5 checks passed
sre-helmcode pushed a commit that referenced this pull request Oct 20, 2026
Brings #50, #51, #52, #53, #54, #56 and #57 onto the new structure:

- mimo-v2.6-flash, qwen3.8-flash at 1M, and the Codex model catalogue:
  the writer changes go to internal/tools, the catalogue and its
  embedded prompt to internal/tools/codexprompt.
- BYOK runs, secrets and automation writes, and notifications: the
  commands go to internal/cli and hang off NewRootCmd; their goldens
  to internal/cli/testdata.
- The --json contract test counts the commands with a --json of their
  own, not the one every command inherits from the root.
- The embedded Codex prompt is Apache-2.0: its NOTICE and licence are
  now in THIRD-PARTY-NOTICES.txt.
- Messages without em dashes, and the new code lint-clean.
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.

2 participants