Secret ⇄ service binding (CLI)#59
Conversation
… secrets Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jwfing
left a comment
There was a problem hiding this comment.
Review — Secret ⇄ service binding (CLI)
Summary: Clean, well-scoped CLI surface for secret↔service binding; the new commands' request/response shapes match the companion platform PR, and I found no blocking issues.
Requirements context
insta-cli has no docs/superpowers/ or docs/specs/ directory, so there is no in-repo spec/plan to check against — assessing against the PR description and the companion platform PR InsForge/insta-platform#47. I cross-checked the CLI's API assumptions against that PR's openapi.yaml, src/server.ts, and src/provisioning/userSecrets.ts (see Functionality below).
Findings
Critical
(none)
Suggestion
- [software-engineering] No tests were added for the new/changed CLI surface (
secretsTree,servicesSecrets, the rewrittensecretsList, and the--servicebranch-defaulting insecretsSet). This is consistent with the repo convention — only pure helpers are unit-tested (test/services.test.ts,run-secrets.test.ts), while command wrappers that callApiClient.load()are not — so it is not blocking. Butsecrets.ts:81(const branch = opts.service ? (opts.branch ?? p.branch) : opts.branch) and the tree-rendering inrenderBranch(secrets.ts:36-39) are real logic that could be extracted into pure functions and unit-tested, matching the "pure, unit-tested helpers" pattern inservices.ts:12-44. Worth noting the platform side (#47) shippedtest/secret-service-binding.test.ts; the CLI side shipped none. - [functionality]
secretsList(secrets.ts:54-65): when the requested branch has no entry intree.branches(e.g. a branch with no services and no unbound secrets), the command prints project-wide names and then says nothing at all about the branch. Consider a(no branch-scoped secrets)line for parity withservicesSecrets' empty-state message (secrets/services.ts:146).
Information
- [functionality] Response shapes verified correct against platform
#47:GET /secrets/treereturnsSecretTreeat the top level → CLI readsres.bodyas the tree (secrets.ts:47,:60). ✓GET /services/{id}/secretsreturns{ secrets: [...] }→ CLI readsres.body.secrets(services.ts:144). ✓PUT /secrets/{name}now accepts optionalservice→ CLI sendsservice: opts.service(secrets.ts:82). ✓- Server registers
/secrets/treebefore/secrets/:name, sotreeis not captured as a:nameparam — no client-side concern.
- [performance]
secretsListnow fetches the entire project tree (all branches) and filters client-side (secrets.ts:58-61), where it previously hit a branch-scoped endpoint. Platform#47exposes no branch-scoped tree endpoint, so this is the only option; blast radius is a single CLI invocation — noted only, no action needed. - [software-engineering]
svc secretsregisters--branch <b>(index.ts:121) while every otherservicessubcommand uses--branch <branch>. Cosmetic inconsistency in--helpoutput. - [software-engineering] Per the
developing-insta-cliskill, command/flag changes should be mirrored inskills/insta/cli-reference.md"in the same change set." The PR body notes those docs live in a separate insta-skills PR — acceptable since that reference is a cross-repo submodule, but please land the two together so the agent-facing docs don't lag the CLI. - [security] No security-relevant regressions: values still come from arg/stdin over the authenticated client; the three new read views are names-only and go through the same
secrets.read202 approval gate as the flat fetch;servicesSecretsvalidates itstypearg viaassertTypeand the--serviceref is validated server-side; nothing new is logged and no secret values are printed by the new views.
Verdict
approved — zero Critical findings. (Informational; explicit GitHub approval remains a separate human action.) The suggestions above are non-blocking.
Fermionic-Lyu
left a comment
There was a problem hiding this comment.
LGTM, Approved. (Relaying John-bot's approved verdict — approved with the maintainer account, since John-bot can't approve its own PR.)
There was a problem hiding this comment.
1 issue found across 3 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/commands/secrets.ts">
<violation number="1" location="src/commands/secrets.ts:61">
P2: `insta secrets list --branch <unknown>` silently looks like an empty branch because the missing branch is ignored and only project-wide names are printed. Validating the lookup before either output path would turn typos or stale links into an actionable error and keep the JSON shape stable.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| if (handleApproval(res)) return | ||
| for (const name of Object.keys(res.body.secrets)) info(name) | ||
| const tree: Tree = res.body | ||
| const b = tree.branches.find((x) => x.name === branch) |
There was a problem hiding this comment.
P2: insta secrets list --branch <unknown> silently looks like an empty branch because the missing branch is ignored and only project-wide names are printed. Validating the lookup before either output path would turn typos or stale links into an actionable error and keep the JSON shape stable.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/commands/secrets.ts, line 61:
<comment>`insta secrets list --branch <unknown>` silently looks like an empty branch because the missing branch is ignored and only project-wide names are printed. Validating the lookup before either output path would turn typos or stale links into an actionable error and keep the JSON shape stable.</comment>
<file context>
@@ -25,13 +25,43 @@ export async function secrets(opts: { branch?: string; output?: string; print?:
if (handleApproval(res)) return
- for (const name of Object.keys(res.body.secrets)) info(name)
+ const tree: Tree = res.body
+ const b = tree.branches.find((x) => x.name === branch)
+ if (opts.json) return printJson({ projectWide: tree.projectWide, branch: b })
+ if (tree.projectWide.length) { info('(project-wide)'); for (const n of tree.projectWide) info(` ${n}`) }
</file context>
| const b = tree.branches.find((x) => x.name === branch) | |
| const b = tree.branches.find((x) => x.name === branch) | |
| if (!b) die(`branch not found: ${branch}`) |
CLI surfaces for binding secrets to services (platform PR InsForge/insta-platform#47).
insta secrets set <NAME> [value] --service <type/name>— bind a user secret to a branch service (binding requires a branch; defaults to the current one).insta secrets list [--branch] [--json]— now grouped by service (+ branch-level unbound + project-wide), names only.insta secrets tree [--json]— the whole project asproject → branch → service → secrets(names only).insta services secrets <type> <name> [--branch] [--json]— a service's bound secret names.secrets.read202 approval flow (viarawRequest+handleApproval, like the flatsecretsfetch). The.envbundle is unchanged; new views are names-only.Verification:
npm run typecheck && npm run build && npm test(92 tests) pass. Agent-facing docs in the insta-skills PR.🤖 Generated with Claude Code
Summary by cubic
Add CLI support for binding secrets to branch services and new views to inspect secret bindings across a project. This scopes and audits secrets by service while keeping the
.envbundle unchanged.insta secrets set <name> [value] --service <type/name>(implies current branch; respects--branch).insta secrets list [--branch] [--json]now shows project-wide, per-service, and branch-level unbound names.insta secrets tree [--json]prints project → branch → service → secrets (names only).insta services secrets <type> <name> [--branch] [--json]lists names bound to a service.secrets.read202 approval flow.Written for commit eafb706. Summary will update on new commits.