Skip to content

Add secret binding commands - #103

Merged
jwfing merged 1 commit into
mainfrom
jw/canonical-secret-bindings
Aug 19, 2026
Merged

Add secret binding commands#103
jwfing merged 1 commit into
mainfrom
jw/canonical-secret-bindings

Conversation

@jwfing

@jwfing jwfing commented Aug 18, 2026

Copy link
Copy Markdown
Member

Summary

  • Add insta secrets bind/unbind commands
  • Add commands to list credential sources and compute bindings

Validation

  • npm run build
  • npm test

Summary by cubic

Adds CLI commands to bind/unbind service credentials to compute env vars and to list available bindings and sources. This makes credential management per branch and compute target explicit and scriptable.

Review notes

  • Adds sec subcommands: bind <env-name> <source>, unbind <env-name>, bindings, sources.
  • Required flags: bind needs --to <compute-service>; unbind needs --from <compute-service>; bindings needs --target <compute-service>. Optional: --branch (default: current), --json; bind supports --source-name.
  • API calls: PUT /projects/:id/secret-bindings/:envName, DELETE /projects/:id/secret-bindings/:envName, GET /projects/:id/secret-bindings, GET /projects/:id/secret-sources. All respect approvals via handleApproval.
  • Output: bindings print as envName <- type/name.sourceName; sources print as type/name: secret1, secret2; empty lists show (no …). JSON mode returns arrays; bind/unbind return { ok: true }.

Written for commit 623956b. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 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:134">
P2: When a binding was created without `--source-name`, `b.sourceName` is absent and this line prints a literal `.undefined` suffix, e.g. `api <- postgres/mydb.undefined`. The `bind` command's own message guards the suffix with a ternary (`opts.sourceName ? `.${opts.sourceName}` : ''`), so the listing should too. Only append the source name when it is defined.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/commands/secrets.ts
const bindings = res.body.bindings ?? []
if (opts.json) return printJson(bindings)
if (!bindings.length) return info(`(no secret bindings for ${opts.target} on ${branch})`)
for (const b of bindings) info(`${b.envName} <- ${b.source.type}/${b.source.name}.${b.sourceName}`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a binding was created without --source-name, b.sourceName is absent and this line prints a literal .undefined suffix, e.g. api <- postgres/mydb.undefined. The bind command's own message guards the suffix with a ternary (opts.sourceName ? .${opts.sourceName} : ''), so the listing should too. Only append the source name when it is defined.

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 134:

<comment>When a binding was created without `--source-name`, `b.sourceName` is absent and this line prints a literal `.undefined` suffix, e.g. `api <- postgres/mydb.undefined`. The `bind` command's own message guards the suffix with a ternary (`opts.sourceName ? `.${opts.sourceName}` : ''`), so the listing should too. Only append the source name when it is defined.</comment>

<file context>
@@ -94,6 +94,58 @@ export async function secretsUnset(name: string, opts: { branch?: string }): Pro
+  const bindings = res.body.bindings ?? []
+  if (opts.json) return printJson(bindings)
+  if (!bindings.length) return info(`(no secret bindings for ${opts.target} on ${branch})`)
+  for (const b of bindings) info(`${b.envName} <- ${b.source.type}/${b.source.name}.${b.sourceName}`)
+}
+
</file context>
Suggested change
for (const b of bindings) info(`${b.envName} <- ${b.source.type}/${b.source.name}.${b.sourceName}`)
for (const b of bindings) info(`${b.envName} <- ${b.source.type}/${b.source.name}${b.sourceName ? `.${b.sourceName}` : ''}`)

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Adds four insta secrets subcommands (bind, unbind, bindings, sources) that wrap the platform secret-binding endpoints; the code is small, idiomatic, and consistent with the existing secrets.ts command family — no blocking issues.

Requirements context

No matching spec/plan found — this repo has no /docs/superpowers/ (or docs/specs/) directory, so I assessed against the PR description and the existing src/commands/secrets.ts conventions alone.

Findings

Critical

(none)

Suggestion

Software engineering — no tests for the new commands (src/commands/secrets.ts:97-147). Every other command area in this repo carries tests (test/services.test.ts, test/storage.test.ts, test/run-secrets.test.ts, …) and the repo follows TDD. The four new functions contain testable behavior worth pinning: the required-flag guards (die('--to …'), --from, --target), the query-string assembly in secretsUnbind/secretsBindings/secretsSources, the empty-list branches ((no secret bindings …) / (no credential sources …)), and the --json array output. A small unit test injecting a fake rawRequest (as run-secrets.test.ts does with fetchBundle) would lock these in. Non-blocking, but recommended before merge.

Functionality — sourceName can render as .undefined (src/commands/secrets.ts:134). bindings prints ${b.envName} <- ${b.source.type}/${b.source.name}.${b.sourceName}, but --source-name is documented as optional ("when the source exposes more than one"), so a binding without a sourceName will render as NAME <- postgres/db.undefined. Consider omitting the .${sourceName} suffix when it's absent, mirroring the conditional suffix you already use in the bind success message (${opts.sourceName ? \.${opts.sourceName}` : ''}`, line 110).

Functionality — platform contract coupling is unverified from this repo. These commands assume specific response envelopes/field names: res.body.bindings[] with { envName, source: { type, name }, sourceName } (line 131-134) and res.body.sources[] with { service: { type, name }, secrets: [] } (line 143-146), plus request fields target/source/sourceName on the PUT (line 102-107). Please confirm these match the merged platform secret-bindings/secret-sources endpoints (and the equivalent insta-mcp binding tools) — a mismatch would silently degrade to the empty-list path rather than error. If the platform endpoints aren't merged yet, land them first to avoid shipping a CLI that 404s.

Information

  • Flag naming differs across sibling subcommandsbind --to, unbind --from, bindings --target all denote the same "target compute service." The variation is semantically reasonable (reads naturally per verb) but is worth a note in --help/docs so users aren't surprised. (src/index.ts:165-186)
  • --json + approval interaction: when the platform gates the action (HTTP 202), handleApproval prints a human-readable line and returns before printJson, so --json callers get a non-JSON message on the approval path. This matches the existing secretsSet behavior, so it's consistent rather than a regression — just flagging for scriptability awareness. (src/commands/secrets.ts:108-109, util.ts:32-38)

Security

No security-relevant concerns. The commands operate on secret names and bindings, never values; sources prints only secret names (same as the existing tree). All path/query segments are encodeURIComponent-escaped, auth flows through the standard bearer client, and 202 approval gating is respected via handleApproval.

Performance

No concerns — each command issues a single control-plane request with trivial client-side formatting.

Verdict

approved (informational — a human still approves via the GitHub approve flow). Clean, well-scoped addition; the suggestions above (add tests, guard the .undefined render, confirm the platform contract) are worth addressing but none are blocking.

@Fermionic-Lyu Fermionic-Lyu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, Approved.

@jwfing
jwfing merged commit 76d239f into main Aug 19, 2026
2 checks passed
@jwfing jwfing mentioned this pull request Aug 19, 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.

2 participants