Skip to content

Re-organize the command tree: 24 noun-first groups, global --api-url - #252

Merged
jwfing merged 21 commits into
mainfrom
feat/command-reorg
Sep 18, 2026
Merged

jwfing merged 21 commits into
mainfrom
feat/command-reorg

Conversation

@jwfing

@jwfing jwfing commented Sep 17, 2026

Copy link
Copy Markdown
Member

Implements superproject docs/superpowers/specs/2026-09-17-cli-command-reorg-design.md (plan: docs/superpowers/plans/2026-09-17-cli-command-reorg.md, Tasks 1–8 and 17).

What changes

Hard cutover, 32 → 24 top-level commands. Retired paths answer unknown command (or too many arguments for the two groups that carry their own action): services scale|upgrade|set-access|secrets, compute set-domain|check-domain|remove-domain, db …, and top-level metrics|logs|usage|manifest|approvals|agent-policy|observe|events|mcp|regions|autoupdate|billing upgrade. services/svc stay as permanent aliases of service; env is hidden but unchanged; setup agent stays as a permanent hidden alias of agent setup (identical options) because npx -y insta@latest setup agent … is the onboarding one-liner printed by the console, the landing page and third-party docs.

New / moved surface (insta --help):

Group Verbs
service (services, svc) add list remove rename
domain attach (bought and bring-your-own, one verb) check detach + search buy list status records …
compute + scale <count> [service] logs metrics; domain verbs leave
build keeps [dir], gains logs <build-id> — main's top-level build-logs moves here (a hyphenated level-1 verb breaks rule 1); --source archive|github, --follow, --json
postgres url connect stats limits volume always-on logs metrics — every verb takes [service] (was db … --group)
redis mysql mongodb query status limits volume always-on logs metrics (status reads /projects/:id/runtime-health)
storage + set-access <public|private> --service <name>
billing subscribe (was upgrade) portal usage
agent setup manifest policy … approvals … observe … events
config install-mcp regions autoupdate

Every command accepts --api-url <url> (runtime only; beats INSTA_API_URL; a URL for another host runs logged-out). install.sh keeps calling setup agent, which works on every release thanks to the alias (no probe). The developer skill gains the command-level rules; test/help-surface.test.ts pins the visible surface.

Platform facts relied on (checked at insta-platform 56b6482)

/services/:id/{limits,volume,always-on} accept managed databases (openapi.yaml summaries); /services/:id/state is compute-only (src/provisioning/services.ts state), hence runtime-health for managed-DB status; the metrics/logs component enum has no storage (src/observability/service.ts COMPONENT_ENUM_MSG).

Review

Eight task-scoped reviews, one whole-branch review, then a full round against this PR's review comments — the human review's Critical plus cubic's 18 inline findings, all fixed in-branch. Highlights: a runtime --api-url could be persisted by logout and by a 401 refresh, permanently re-pointing the machine and dropping its login; logout under a foreign override skipped the server-side revoke while still deleting local tokens; domain detach would have half-released a bought hostname; billing upgrade fell through to the billing overview at exit 0; managed-database volume help contradicted the platform. main was merged twice along the way.

npm run typecheck clean; npm test 189 files / 3525 tests green.

Rollout — read before merging

Do not MERGE this PR yet. Its README documents npx -y insta@latest agent setup, which fails on every published binary until the release exists (the console's setup agent line keeps working). Sequence (design §9): get this PR approved and hold it → prepare the skills / e2e / frontend (one policy-hint string) / mcp / platform-copy PRs → merge this PR, the 0.1.0 version-bump PR, and tag in one sitting → merge the downstream PRs minutes after the tag (skills first: agents fetch that text at setup time).

🤖 Generated with Claude Code

jwfing and others added 13 commits September 17, 2026 14:49
…_API_URL

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
… of --group

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ver runtime-health, query engine guard

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ess/upgrade/secrets verbs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…h move under domain

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ics, agent + config groups, global --api-url

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…mmand paths

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…billing subscribe / service add

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…cture rules in the dev skill

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…y live paths

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…--api-url; spawn-test timeouts; wording

- billing/secrets group commands now set allowExcessArguments(false), so a
  retired/mistyped subcommand under either group (`billing upgrade pro`,
  `secrets lst`) fails instead of silently running the group's own default
  action (commander 12 defaults allowExcessArguments to true).
- `insta config autoupdate <on|off>` now reads via readPersistedGlobal()
  before its read-modify-write, instead of readGlobal() — readGlobal() folds
  in a runtime --api-url/INSTA_API_URL override and scrubs the stored session
  when it points elsewhere, so writing it back re-pointed ~/.insta/config.json
  and logged the user out just from toggling autoupdate.
- Add explicit 30s timeouts to the remaining spawnSync-backed vitest `it`
  cases in help-surface.test.ts and retired-policy.test.ts (CI's
  windows-latest spawns are 3-5x slower than the 5s vitest default).
- Wording: SKILL.md's observe hook reference, the root --api-url description
  (compute exec needs it before `compute`), volumeWriteLine and serviceLimits
  now say "database" instead of implying every managed type has a deploy step.
- Tests: pin `billing upgrade` as retired and `secrets lst` as loud-failing;
  broaden the retired-path stderr regex to also accept commander's "too many
  arguments"; add a managedStatus-level (not just statusLine-level) test for
  runtime-health omitting the resolved service.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@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.

3 issues found across 59 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/config.ts">

<violation number="1" location="src/config.ts:75">
P2: When a command using a same-deployment `--api-url` override refreshes an expired session, `ApiClient.refresh()` persists this returned config and rewrites the stored `apiUrl`. Keep refresh persistence tied to the persisted config so a runtime debugging override cannot alter the machine's login configuration.</violation>

<violation number="2" location="src/config.ts:85">
P1: When `logout` runs with `--api-url` for another deployment, `readGlobal()` returns the scrubbed runtime view and `logout()` persists it. This permanently switches the stored API URL and deletes the original session, violating the flag's runtime-only contract; persist the stored config instead.</violation>
</file>

<file name="src/commands/compute.ts">

<violation number="1" location="src/commands/compute.ts:660">
P3: The no-volume guidance for managed databases tells users `insta redis volume <name> --size <gi>` will attach a volume, but the redis/mysql/mongodb `volume` command's declared surface in src/index.ts only documents grow and delete ("--size grows it (paid plans; grow-only)") — no attach. One of the two surfaces is wrong for the user: if --size cannot attach a disk to a volumeless managed DB, this newly added line sends users into a failing command; if it can, the new command help is missing the attach semantics. Align the message and the --help text with the actual platform behavior for managed-DB volume attach.</violation>
</file>

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

Re-trigger cubic

Comment thread src/config.ts
return { apiUrl: override ?? DEFAULT_API }
parsed = null
}
return pickApiUrl(parsed, process.env, cliApiUrlOverride)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: When logout runs with --api-url for another deployment, readGlobal() returns the scrubbed runtime view and logout() persists it. This permanently switches the stored API URL and deletes the original session, violating the flag's runtime-only contract; persist the stored config instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/config.ts, line 85:

<comment>When `logout` runs with `--api-url` for another deployment, `readGlobal()` returns the scrubbed runtime view and `logout()` persists it. This permanently switches the stored API URL and deletes the original session, violating the flag's runtime-only contract; persist the stored config instead.</comment>

<file context>
@@ -33,41 +33,56 @@ export type ProjectConfig = { projectId: string; orgId: string; branch: string }
-    return { apiUrl: override ?? DEFAULT_API }
+    parsed = null
   }
+  return pickApiUrl(parsed, process.env, cliApiUrlOverride)
 }
 
</file context>

Comment thread README.md Outdated
Comment thread test/help-surface.test.ts
Comment thread src/config.ts
delete scrubbed.agentCredential
return scrubbed
}
return { ...parsed, apiUrl: override ?? persisted }

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 command using a same-deployment --api-url override refreshes an expired session, ApiClient.refresh() persists this returned config and rewrites the stored apiUrl. Keep refresh persistence tied to the persisted config so a runtime debugging override cannot alter the machine's login configuration.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/config.ts, line 75:

<comment>When a command using a same-deployment `--api-url` override refreshes an expired session, `ApiClient.refresh()` persists this returned config and rewrites the stored `apiUrl`. Keep refresh persistence tied to the persisted config so a runtime debugging override cannot alter the machine's login configuration.</comment>

<file context>
@@ -33,41 +33,56 @@ export type ProjectConfig = { projectId: string; orgId: string; branch: string }
+    delete scrubbed.agentCredential
+    return scrubbed
+  }
+  return { ...parsed, apiUrl: override ?? persisted }
+}
+
</file context>

Comment thread src/commands/services.ts Outdated
Comment thread .claude/skills/developing-insta-cli/SKILL.md
Comment thread src/commands/domain.ts Outdated
Comment thread test/retired-policy.test.ts Outdated
Comment thread src/config.ts Outdated
Comment thread src/commands/compute.ts
`compute ${name}: no volume attached (attach one: \`insta compute volume ${name} --size <gi>\` — it mounts at /data on the next deploy)`,
type === 'compute'
? `compute ${name}: no volume attached (attach one: \`insta compute volume ${name} --size <gi>\` — it mounts at /data on the next deploy)`
: `${type} ${name}: no volume attached (attach one: \`insta ${type} volume ${name} --size <gi>\` — it mounts at the image's data directory)`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The no-volume guidance for managed databases tells users insta redis volume <name> --size <gi> will attach a volume, but the redis/mysql/mongodb volume command's declared surface in src/index.ts only documents grow and delete ("--size grows it (paid plans; grow-only)") — no attach. One of the two surfaces is wrong for the user: if --size cannot attach a disk to a volumeless managed DB, this newly added line sends users into a failing command; if it can, the new command help is missing the attach semantics. Align the message and the --help text with the actual platform behavior for managed-DB volume attach.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/commands/compute.ts, line 660:

<comment>The no-volume guidance for managed databases tells users `insta redis volume <name> --size <gi>` will attach a volume, but the redis/mysql/mongodb `volume` command's declared surface in src/index.ts only documents grow and delete ("--size grows it (paid plans; grow-only)") — no attach. One of the two surfaces is wrong for the user: if --size cannot attach a disk to a volumeless managed DB, this newly added line sends users into a failing command; if it can, the new command help is missing the attach semantics. Align the message and the --help text with the actual platform behavior for managed-DB volume attach.</comment>

<file context>
@@ -627,31 +653,38 @@ export function parseCpu(raw: string): number {
-    `compute ${name}: no volume attached (attach one: \`insta compute volume ${name} --size <gi>\` — it mounts at /data on the next deploy)`,
+    type === 'compute'
+      ? `compute ${name}: no volume attached (attach one: \`insta compute volume ${name} --size <gi>\` — it mounts at /data on the next deploy)`
+      : `${type} ${name}: no volume attached (attach one: \`insta ${type} volume ${name} --size <gi>\` — it mounts at the image's data directory)`,
   ]
   return [
</file context>

…iner)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@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.

All reported issues were addressed across 5 files (changes from recent commits).

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

Re-trigger cubic

Comment thread src/index.ts
… — the volume is the data directory)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@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.

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/index.ts Outdated

@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
The command-tree reorganization is broadly coherent and includes useful surface, API override, and managed-database tests. One managed-database command violates the checked-in command-shape requirement: query requires the service and places query arguments after it, instead of supporting the documented optional trailing [service]. No security or production-performance regression was identified from the supplied diff.

Requirements context
Intent was derived from the PR description, README changes, and the command-architecture rules added to .claude/skills/developing-insta-cli/SKILL.md. The linked superproject design and plan are outside this standalone CLI checkout, so they could not be inspected directly; review therefore used the explicit requirements reproduced in the PR and checked-in documentation, including the hard cutover, 24 visible commands, permanent aliases, per-resource observability, and runtime-only --api-url.

Findings

Critical

  • src/index.ts:326: Managed database query breaks the documented common command shape - The new architecture rule states that compute|postgres|redis|mysql|mongodb <verb> [service] use an optional trailing service resolved with resolveSoleService when omitted. However, each managed database registers query <service> [args...], and dbQuery requires a service string. Consequently the sole-service convenience does not work for query, and the service is not trailing: for example, insta redis query GET key treats GET as the service name instead of selecting the sole Redis service. Align this verb with the documented shape (with an unambiguous query-argument convention) or explicitly amend the governing design if query is intended as an exception, and add CLI coverage for the decided behavior.

Suggestion
(none)

Information

  • Testing: Good coverage of the reorganized surface, but the query-shape conflict is not exercised - The added tests pin the 24 visible commands, retired paths, aliases, moved group verbs, API URL precedence, and managed-database status behavior. The help test only checks that managed groups contain a query verb; it does not compare its service-selection shape with the architecture rule or exercise omission of the service.
  • Security: API override avoids forwarding credentials across deployments - pickApiUrl removes access tokens, refresh tokens, user data, and agent credentials when an override selects a different normalized API URL. The changed telemetry allowlists continue to redact service names, query text, paths, and other free-form arguments, and the PR introduces no dependency or new shell/SQL interpolation surface.
  • Performance: No material production performance regression found - The new managed-database status path performs two bounded API reads—service resolution followed by runtime health—and selects one entry locally. The other moved handlers retain their prior request patterns; no unbounded loops, N+1 request patterns, or new synchronous production hot-path work were introduced.
  • Rollout: Merge must remain coordinated with the release and downstream documentation - The README switches to commands unavailable in currently published binaries, while setup agent remains compatible for installation. As the PR description notes, this branch should be held until the CLI release/tag and downstream skills, frontend, MCP, e2e, and platform-copy changes can be coordinated.

Tests
I could not execute npm run typecheck or npm test: every shell invocation failed before the command started because the environment could not create its read-only bwrap namespace (No permissions to create a new namespace). I performed a static review of the supplied full diff and surrounding repository documentation available through read-only interfaces; the PR author's reported 3,449 passing tests was not independently verified.

Verdict
request_changes: Changes are requested because the explicit common command-shape requirement has one correctness/specification violation in the managed-database query registration.

jwfing and others added 4 commits September 18, 2026 10:30
Two conflicts, both in the registration surface this branch rewrote:

- src/index.ts: the whole registration section is ours (the 24 noun-first
  groups), so main's block was dropped and its two additions ported into our
  tree instead. main's top-level `build-logs <build-id>` becomes
  `insta build logs <build-id>` — a hyphenated top-level verb would break the
  branch's level-1-is-a-resource rule and the 24-command surface; `build` now
  carries both its own action and a subcommand, so it gets the same
  allowExcessArguments(false) guard as `billing` and `secrets`. main's
  `--org <id>` on `domain list` / `domain status` (#251) is kept verbatim
  inside our `domain` group.
- README.md: our rewritten Commands table wins; main's `insta build-logs` row
  is folded into the `insta build` / `insta deploy` row as `insta build logs`.

test/help-surface.test.ts pins the move: `build-logs` joins RETIRED, and a new
case asserts `build --help` lists `logs` and `build logs --help` exits 0 with
--source/--follow/--json.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…on bought names, managed-DB volume help, shape-rule exception)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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.

3 issues found across 18 files (changes from recent commits).

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/api.ts">

<violation number="1" location="src/api.ts:52">
P1: When `env use` switches deployments while another process refreshes, `readPersistedGlobal()` can return staging while `this.cfg` contains freshly refreshed production tokens, so this write stores production credentials under staging. Compare the persisted URL with `this.apiUrl` before writing and skip persistence if the deployment changed.</violation>
</file>

<file name="src/commands/services.ts">

<violation number="1" location="src/commands/services.ts:33">
P2: When a project has multiple managed database services of this type, this suggested command omits the service selector and exits with an ambiguity error instead of changing a service. Tell users to add the service name after `on|off` when multiple services exist.</violation>

<violation number="2" location="src/commands/services.ts:38">
P2: When a project has multiple managed database services of this type, this suggested command omits the service selector and fails with an ambiguity error. Tell users to add the service name after `volume` when multiple services exist.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/api.ts
// authenticated against — may move the stored URL; everything else keeps what is on disk.
async persist(): Promise<void> {
if (this.apiUrlExplicit) return writeGlobal(this.cfg)
await writeGlobal({ ...this.cfg, apiUrl: (await readPersistedGlobal()).apiUrl })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: When env use switches deployments while another process refreshes, readPersistedGlobal() can return staging while this.cfg contains freshly refreshed production tokens, so this write stores production credentials under staging. Compare the persisted URL with this.apiUrl before writing and skip persistence if the deployment changed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/api.ts, line 52:

<comment>When `env use` switches deployments while another process refreshes, `readPersistedGlobal()` can return staging while `this.cfg` contains freshly refreshed production tokens, so this write stores production credentials under staging. Compare the persisted URL with `this.apiUrl` before writing and skip persistence if the deployment changed.</comment>

<file context>
@@ -40,9 +40,23 @@ export class ApiClient {
+  // authenticated against — may move the stored URL; everything else keeps what is on disk.
+  async persist(): Promise<void> {
+    if (this.apiUrlExplicit) return writeGlobal(this.cfg)
+    await writeGlobal({ ...this.cfg, apiUrl: (await readPersistedGlobal()).apiUrl })
+  }
 
</file context>
Suggested change
await writeGlobal({ ...this.cfg, apiUrl: (await readPersistedGlobal()).apiUrl })
const persisted = await readPersistedGlobal()
if (persisted.apiUrl.replace(/\/+$/, '') !== this.apiUrl.replace(/\/+$/, '')) return
await writeGlobal({ ...this.cfg, apiUrl: persisted.apiUrl })

Comment thread src/commands/services.ts Outdated

export function volumeTypeError(type: string): string {
const base = '--volume is only valid for compute services'
return hasOwnGroup(type) ? `${base} (${type} has one by default — grow it with \`insta ${type} volume --size <gi>\`)` : base

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 project has multiple managed database services of this type, this suggested command omits the service selector and fails with an ambiguity error. Tell users to add the service name after volume when multiple services exist.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/commands/services.ts, line 38:

<comment>When a project has multiple managed database services of this type, this suggested command omits the service selector and fails with an ambiguity error. Tell users to add the service name after `volume` when multiple services exist.</comment>

<file context>
@@ -21,6 +21,23 @@ export function assertServiceName(name: string): void {
+
+export function volumeTypeError(type: string): string {
+  const base = '--volume is only valid for compute services'
+  return hasOwnGroup(type) ? `${base} (${type} has one by default — grow it with \`insta ${type} volume --size <gi>\`)` : base
+}
+
</file context>
Suggested change
return hasOwnGroup(type) ? `${base} (${type} has one by default — grow it with \`insta ${type} volume --size <gi>\`)` : base
return hasOwnGroup(type) ? `${base} (${type} has one by default — grow it with \`insta ${type} volume --size <gi>\`; add the service name after volume when multiple services exist)` : base

Comment thread src/commands/services.ts

export function alwaysOnTypeError(type: string): string {
const base = '--always-on / --no-always-on is only valid for compute services'
return hasOwnGroup(type) ? `${base} (for ${type}, use \`insta ${type} always-on on|off\` after creation)` : base

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 project has multiple managed database services of this type, this suggested command omits the service selector and exits with an ambiguity error instead of changing a service. Tell users to add the service name after on|off when multiple services exist.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/commands/services.ts, line 33:

<comment>When a project has multiple managed database services of this type, this suggested command omits the service selector and exits with an ambiguity error instead of changing a service. Tell users to add the service name after `on|off` when multiple services exist.</comment>

<file context>
@@ -21,6 +21,23 @@ export function assertServiceName(name: string): void {
+
+export function alwaysOnTypeError(type: string): string {
+  const base = '--always-on / --no-always-on is only valid for compute services'
+  return hasOwnGroup(type) ? `${base} (for ${type}, use \`insta ${type} always-on on|off\` after creation)` : base
+}
+
</file context>
Suggested change
return hasOwnGroup(type) ? `${base} (for ${type}, use \`insta ${type} always-on on|off\` after creation)` : base
return hasOwnGroup(type) ? `${base} (for ${type}, use \`insta ${type} always-on on|off\` after creation; add the service name after on|off when multiple services exist)` : base

@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 22 files (changes from recent commits).

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="test/api-url-override.test.ts">

<violation number="1" location="test/api-url-override.test.ts:102">
P2: This test makes every run contact the live production API because `logout()` intentionally uses the persisted host. Use a localhost server or an injected/mock transport so the persistence assertion remains deterministic and does not send fake credentials externally.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

mkdirSync(join(home, '.insta'), { recursive: true })
const file = join(home, '.insta', 'config.json')
writeFileSync(file, JSON.stringify(stored, null, 2))
const r = spawnSync(process.execPath, ['--import', 'tsx', entry, 'logout', '--api-url', STAGING], {

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: This test makes every run contact the live production API because logout() intentionally uses the persisted host. Use a localhost server or an injected/mock transport so the persistence assertion remains deterministic and does not send fake credentials externally.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/api-url-override.test.ts, line 102:

<comment>This test makes every run contact the live production API because `logout()` intentionally uses the persisted host. Use a localhost server or an injected/mock transport so the persistence assertion remains deterministic and does not send fake credentials externally.</comment>

<file context>
@@ -38,3 +69,94 @@ describe('pickApiUrl', () => {
+      mkdirSync(join(home, '.insta'), { recursive: true })
+      const file = join(home, '.insta', 'config.json')
+      writeFileSync(file, JSON.stringify(stored, null, 2))
+      const r = spawnSync(process.execPath, ['--import', 'tsx', entry, 'logout', '--api-url', STAGING], {
+        encoding: 'utf8',
+        timeout: 30_000,
</file context>

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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
No blocking correctness, security, or performance defect was identified in the supplied diff. The command reorganization is internally consistent and includes strong surface-level regression coverage, though several generalized mutation paths would benefit from direct handler tests. Per the PR’s own rollout warning, approval should be held and the PR must not be merged until the coordinated release and downstream documentation changes are ready.

Requirements context
Intent was derived from the PR title and description, the README changes, and the command-architecture requirements included in .claude/skills/developing-insta-cli/SKILL.md. These establish the noun-first 24-command surface, hard cutover rules, permanent service aliases and hidden setup agent compatibility path, managed-database command shapes, and runtime-only --api-url behavior. The referenced superproject design and plan are outside this checkout and could not be independently inspected; the review therefore relies on the detailed PR description and repository guidance supplied in the diff.

Findings

Critical
(none)

Suggestion

  • src/commands/compute.ts: serviceAlwaysOn, serviceLimits, serviceVolume, computeScale; src/commands/storage.ts: storageSetAccess: Add direct flow coverage for newly generalized mutation handlers - The help-surface tests verify registration, and existing tests cover parsers and some rendering, but the new managed-database variants and moved scale/access handlers are not all directly exercised through an injected API seam. Focused tests should assert service-type resolution, branch propagation, HTTP method/path/body, approval handling, and JSON output, especially for redis/mysql/mongodb limits, volume, and always-on.

Information

  • Functionality: Command tree matches the described hard cutover - The visible 24-command surface, permanent services|svc aliases, hidden setup agent alias, resource-scoped observability commands, database-specific groups, and retired paths are consistently wired and documented in the supplied changes. The unified domain attach/check/detach behavior also preserves the distinct purchased-domain and bring-your-own flows.
  • Security: Runtime API overrides avoid cross-deployment credential reuse - A foreign --api-url produces an in-memory configuration with stored user and agent credentials removed, while persistence paths retain the on-disk control-plane URL unless login explicitly selects a new one. Logout deliberately revokes the sole stored session against its persisted deployment, preventing a runtime override from discarding the local token without revoking the corresponding server session. No new dependency or newly exposed secret logging was introduced.
  • Performance: No material performance regression identified - The new handlers perform bounded control-plane calls appropriate for a CLI. Managed-database status adds a services lookup followed by one runtime-health lookup, and persistence may add one small configuration-file read; neither is a hot-path or unbounded operation.
  • Testing: Surface and API override regressions are well represented - The added tests pin the visible and retired command paths, group argument shapes, compatibility aliases, --api-url precedence and non-persistence, cross-deployment session scrubbing, managed-database status, and domain routing behavior. The child-process tests also include explicit timeouts for slower platforms.
  • Rollout: Merge remains intentionally gated on coordinated release work - The PR updates README examples to the new canonical commands even though released binaries do not yet support them. The description explicitly requires holding this PR until the version bump, tag, skills/e2e/frontend/MCP/platform-copy changes, and release sequence are ready; this operational gate remains necessary despite the code-review verdict.

Tests
Attempted read-only shell inspection with sed, git, rg, and pwd, but the command runner could not start because its sandbox failed to create a namespace (bwrap: No permissions to create a new namespace). Consequently, no tests or typecheck could be run in this review. Assessment used the supplied full diff; the PR description reports npm run typecheck and 3,449 tests across 185 files passing, but that claim was not independently verified.

Verdict
approved: There are no Critical findings, so the strict verdict rule yields approved; the testing suggestion and rollout hold are non-blocking review observations.

src/build-logs.ts conflicted: main enriched the failure message with a
reason (timeout / HTTP status) while this branch renamed the command to
the `build logs` subcommand. Took both — main's detail, this branch's
spelling. Main's new work also reintroduced `insta build-logs` in
src/deploy-archive.ts and three test assertions; re-pointed all of them,
since that path now exits with `unknown command`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jwfing

jwfing commented Sep 18, 2026

Copy link
Copy Markdown
Member Author

All review findings are addressed and pushed — the human review's Critical, cubic's 18 inline comments, and two problems the review surfaced indirectly. main was also merged twice (the PR had gone CONFLICTING); it is MERGEABLE at b8400ad with CI green, 189 test files / 3525 tests.

The Critical: managed-database query breaks the documented shape

You are right that the code and the checked-in rule disagreed, and the rule was the wrong one. query keeps <service> required and leading, and rule 4 now says so.

A trailing optional service cannot be told apart from the query argv. insta redis query GET key would have to decide whether GET is a service name or the start of the command, and the only way out is a -- separator, which compute exec already pays for with argv surgery ahead of commander. The governing design recorded this as a deliberate exception (§8); the skill rule simply failed to carry it. Now .claude/skills/developing-insta-cli/SKILL.md states the exception and the reason, and test/help-surface.test.ts pins it: each managed group's usage line must be query [options] <service> [args...], and a bare redis query must exit non-zero. If we ever want the sole-service convenience here, it is a -- design change, not a registration tweak.

Your Information note on rollout is also now enforced in the PR body: this branch is held until the release, and the downstream skills, console, MCP, e2e and platform-copy PRs are open and held with it.

cubic's findings

P1 — a runtime --api-url was persisted. Fixed at the choke point: ApiClient.persist() keeps the stored URL unless setApiUrl marked the change explicit, so logout and a 401 refresh() can no longer re-point the machine. login --api-url X still persists X.

Two things came out of that one. logout now ignores the override entirely and acts on the stored session — previously, under a foreign override, the refresh token was scrubbed from the in-memory view, so the server-side revoke was skipped while the local tokens were deleted anyway, leaving the session alive on the server. And when an override is present but ignored, logout says so instead of pretending. The guard has its own unit test (test/persist-guard.test.ts), verified red against the old writeGlobal(this.cfg); the logout test stands up a real listener and asserts the revoke reaches the persisted host, not the override.

P2s. README now documents the compute exec placement exception. The --api-url placement test covers a leaf in every visible group, not just hidden env. service add's --always-on and --volume refusals name the user's own type instead of sending redis/mysql/mongodb users to Postgres commands. insta config autoupdate no longer trips the background update check (the nested path was invisible to the old argv[2] guard). setup agent --api-url feeds the override into setup's environment planner. Managed-database volume help now says --size attaches as well as grows — platform's setVolumeSize gates on isFlyRuntimeType, so a volumeless managed database can be attached to; only --delete is compute-only.

domain detach on a bought hostname was the most interesting one. Bought attach writes hostnames into the domains record and a reconciler binds them to the compute plane; the platform exposes no detach route for them, so DELETE /compute/domain would have left the record still claiming the binding, and detaching an apex would strand its www. detach now refuses for bought names and points at domain attach --group <other service>, which is the path that actually releases. The ownership lookup fails open: if that org route errors (older plane, insta-oss, 403, transient 5xx), a bring-your-own detach still goes straight through, as it did before.

P3s. check and detach normalize the hostname like attach does. volumeLines and volumeDeleteLine no longer offer --delete or claim compute-only effects for managed databases. pickApiUrl no longer reads the ambient INSTA_ENV when the caller passes none. retired-policy.test.ts's per-spawn timeout was raised so a cold Windows runner cannot mask a real failure as an empty stderr. The stale railway setup agent parenthetical and the "covers every command" claim about addApiUrlEverywhere are corrected.

One suggestion I did not take: applying the same "attach or grow" wording to postgres volume. That evidence does not transfer — postgres is insta-db backed through PATCH /database/settings, not the Fly path, and an instance is provisioned with its disk, so "show or grow, grow-only" is accurate.

Merges

main shipped twice into this PR's files. The first merge brought the build-log reader and the org-scoped domain verbs; the second brought 0.0.83 and two build-log fixes. Both times main reintroduced insta build-logs, which this branch retires in favour of the insta build logs <id> subcommand — a hyphenated top-level verb would break the level-1 rule this PR codifies. Every hint, test and doc now names the subcommand, and build-logs is in the retired list so it fails loudly rather than silently. main's richer failure message (timeout / HTTP status) was kept.

@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 mentioned this pull request Sep 18, 2026
@jwfing
jwfing merged commit 7fc6789 into main Sep 18, 2026
3 checks passed
jwfing added a commit that referenced this pull request Sep 18, 2026
The command re-organization (#252) is a hard cutover of the whole command
surface, so it earns the minor rather than another patch.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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