Skip to content

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

Open
jwfing wants to merge 15 commits into
mainfrom
feat/command-reorg
Open

jwfing wants to merge 15 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
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 plus one whole-branch review; every Critical/Important finding fixed in-branch (notably: billing upgrade no longer falls through to the overview; config autoupdate never persists a --api-url override; spawn tests carry explicit timeouts for the Windows job). npm run typecheck clean; npm test 185 files / 3449 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.

16 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="README.md">

<violation number="1" location="README.md:233">
P2: `compute exec` is the documented exception to this flag placement, so this sentence tells users an invocation that the CLI rejects. Document the exception and instruct users to place `--api-url` before `compute` for `compute exec`.</violation>
</file>

<file name="test/help-surface.test.ts">

<violation number="1" location="test/help-surface.test.ts:122">
P2: This test covers `--api-url` placement only for hidden `env`, so a leaf omitted from `addApiUrlEverywhere` can regress unnoticed. Add representative post-subcommand invocations for each command group, including nested leaves.</violation>
</file>

<file name="src/config.ts">

<violation number="1" location="src/config.ts:63">
P3: When the supplied `env` object omits `INSTA_ENV` but the process has one set, `pickApiUrl` reads the ambient variable through `envFromEnvVar`'s default parameter. Treat an omitted property as `null` before calling `envFromEnvVar` so the helper remains pure.</violation>

<violation number="2" 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="3" 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>

<violation number="2" location="src/commands/compute.ts:687">
P3: When deleting a managed-database volume, the success message claims that compute-only constraints—suspend fast-wake and scale-out—are restored. Render a type-specific message instead of reusing this compute-only text for Redis, MySQL, and MongoDB.</violation>
</file>

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

<violation number="1" location="src/commands/services.ts:116">
P2: When `insta service add redis|mysql|mongodb ... --always-on` is rejected, the error points to the Postgres command instead of the managed database's own `always-on` command. Give `postgres`, `redis`, `mysql`, and `mongodb` service-specific guidance, or omit the database hint for unsupported types.</violation>

<violation number="2" location="src/commands/services.ts:119">
P2: When `insta service add redis|mysql|mongodb ... --volume` is rejected, the error sends users to the Postgres volume command, which resolves the wrong service type. Point managed database users to their matching `redis|mysql|mongodb volume --size` command, and keep the Postgres hint only for Postgres.</violation>
</file>

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

<violation number="1" location="src/commands/setup.ts:1">
P3: The comment calls `insta agent setup` the `railway setup agent` pattern, but those command orders differ. Remove or correct the parenthetical so maintainers do not infer the wrong CLI convention.</violation>
</file>

<file name=".claude/skills/developing-insta-cli/SKILL.md">

<violation number="1" location=".claude/skills/developing-insta-cli/SKILL.md:52">
P2: The command-shape rule is false for managed-database `query`: it requires `<service>`, unlike the documented optional `[service]`. Qualify this exception so future commands do not copy the wrong positional contract.</violation>

<violation number="2" location=".claude/skills/developing-insta-cli/SKILL.md:62">
P3: This says `addApiUrlEverywhere()` covers every command, but `compute exec` is deliberately skipped and the root/login options are declared manually. State these exceptions here so the architecture rule does not contradict the implementation and the exec gotcha below.</violation>
</file>

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

<violation number="1" location="src/commands/upgrade.ts:377">
P2: When `insta config autoupdate off` runs with a newer cached release, startup can auto-upgrade before this handler changes the preference. Extend the update-check exemption to recognize the nested `config autoupdate` path.</violation>
</file>

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

<violation number="1" location="src/commands/domain.ts:201">
P3: `domain check`/`domain detach` now forward the hostname verbatim, while `domain attach` in this same change lowercases and trims before delegating to `setDomain`. `insta domain attach Docs.MyApp.com` binds `docs.myapp.com`, but `insta domain check Docs.MyApp.com` queries `Docs.MyApp.com` as typed. If the plane stores hostnames lowercase, a mixed-case check/detach misses the binding the attach just created. Normalize in the aliases to match the attach path.</violation>

<violation number="2" location="src/commands/domain.ts:202">
P2: `domain detach` is now the only detach verb for every hostname, but bought-domain attachments were created through a different surface that `removeDomain` never touches. `domain attach myapp.com` binds two hostnames (apex and www) via `POST /projects/{id}/domains/{owner}/attach` and its output points the user to `insta domain status`, while `domainDetach` sends `DELETE /projects/{id}/compute/domain` for a single `hostname` only. Verify the compute-plane DELETE covers hostnames that arrived via `attach`; if it does, the following still needs handling: detaching the apex leaves `www.myapp.com` bound, and a detach of a hostname never attached through the compute plane may silently no-op. If bought bindings need a different endpoint, route `domainDetach` accordingly or refuse with a clear message.</violation>
</file>

<file name="test/retired-policy.test.ts">

<violation number="1" location="test/retired-policy.test.ts:20">
P3: The first test now serializes 4 child-process spawns (second test 2 more), each still capped at 10s by `run`'s `timeout: 10000`, inside one 30s budget. On a cold Windows runner, a single tsx cold-start can exceed 10s (matching the PR's own note about needing explicit Windows spawn timeouts), and a per-spawn timeout yields `status: null` with empty `stderr`, failing the assertion in a way that hides the real result. Raise the per-call timeout in `run`, or count each spawn's elapsed time against the budget instead of relying on 30s for all of them.</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
| `insta upgrade` · `autoupdate` | Update the CLI; show or set auto-update |
| `insta upgrade` | Update the CLI |

Every command accepts `--api-url <url>` for this invocation only (internal debugging); `insta --help` documents it.

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: compute exec is the documented exception to this flag placement, so this sentence tells users an invocation that the CLI rejects. Document the exception and instruct users to place --api-url before compute for compute exec.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 233:

<comment>`compute exec` is the documented exception to this flag placement, so this sentence tells users an invocation that the CLI rejects. Document the exception and instruct users to place `--api-url` before `compute` for `compute exec`.</comment>

<file context>
@@ -211,28 +211,26 @@ build never reaches a production installer.
-| `insta upgrade` · `autoupdate` | Update the CLI; show or set auto-update |
+| `insta upgrade` | Update the CLI |
+
+Every command accepts `--api-url <url>` for this invocation only (internal debugging); `insta --help` documents it.
 
 ## Configuration
</file context>
Suggested change
Every command accepts `--api-url <url>` for this invocation only (internal debugging); `insta --help` documents it.
Every command accepts `--api-url <url>` for this invocation only (internal debugging); for `compute exec`, place it before `compute`. `insta --help` documents it.

Comment thread test/help-surface.test.ts
const URL_A = 'http://127.0.0.1:1'
const URL_B = 'http://127.0.0.1:2'
// `env --json` reads config and prints the resolved apiUrl without touching the network.
it('is honoured after the subcommand, before it, and over INSTA_API_URL', () => {

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 covers --api-url placement only for hidden env, so a leaf omitted from addApiUrlEverywhere can regress unnoticed. Add representative post-subcommand invocations for each command group, including nested leaves.

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

<comment>This test covers `--api-url` placement only for hidden `env`, so a leaf omitted from `addApiUrlEverywhere` can regress unnoticed. Add representative post-subcommand invocations for each command group, including nested leaves.</comment>

<file context>
@@ -0,0 +1,127 @@
+  const URL_A = 'http://127.0.0.1:1'
+  const URL_B = 'http://127.0.0.1:2'
+  // `env --json` reads config and prints the resolved apiUrl without touching the network.
+  it('is honoured after the subcommand, before it, and over INSTA_API_URL', () => {
+    expect(JSON.parse(run(['env', '--json', '--api-url', URL_A]).stdout).apiUrl).toBe(URL_A)
+    expect(JSON.parse(run(['--api-url', URL_A, 'env', '--json']).stdout).apiUrl).toBe(URL_A)
</file context>

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
}
// Presence, not truthiness: `--no-always-on` is an explicit false and is just as compute-only.
if (opts.alwaysOn !== undefined && type !== 'compute') throw new Error('--always-on / --no-always-on is only valid for compute services (for postgres, use `insta db always-on on|off` after creation)')
if (opts.alwaysOn !== undefined && type !== 'compute') throw new Error('--always-on / --no-always-on is only valid for compute services (for postgres, use `insta postgres always-on on|off` after creation)')

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 insta service add redis|mysql|mongodb ... --always-on is rejected, the error points to the Postgres command instead of the managed database's own always-on command. Give postgres, redis, mysql, and mongodb service-specific guidance, or omit the database hint for unsupported types.

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

<comment>When `insta service add redis|mysql|mongodb ... --always-on` is rejected, the error points to the Postgres command instead of the managed database's own `always-on` command. Give `postgres`, `redis`, `mysql`, and `mongodb` service-specific guidance, or omit the database hint for unsupported types.</comment>

<file context>
@@ -113,10 +113,10 @@ export async function servicesAdd(type: string, name: string, opts: ServicesAddO
   }
   // Presence, not truthiness: `--no-always-on` is an explicit false and is just as compute-only.
-  if (opts.alwaysOn !== undefined && type !== 'compute') throw new Error('--always-on / --no-always-on is only valid for compute services (for postgres, use `insta db always-on on|off` after creation)')
+  if (opts.alwaysOn !== undefined && type !== 'compute') throw new Error('--always-on / --no-always-on is only valid for compute services (for postgres, use `insta postgres always-on on|off` after creation)')
   if (opts.mountPath !== undefined && (type !== 'compute' || opts.volume === undefined)) throw new Error('--mount-path requires --volume on a compute service')
   if (opts.volume !== undefined) {
</file context>
Suggested change
if (opts.alwaysOn !== undefined && type !== 'compute') throw new Error('--always-on / --no-always-on is only valid for compute services (for postgres, use `insta postgres always-on on|off` after creation)')
if (opts.alwaysOn !== undefined && type !== 'compute') throw new Error(type === 'postgres' || ['redis', 'mysql', 'mongodb'].includes(type) ? `--always-on / --no-always-on is only valid for compute services; use ${type} always-on on|off after creation` : '--always-on / --no-always-on is only valid for compute services')

order the design's §9 gives (docs/copy merge right after the CLI release, never before).

Where things go: settings (limits/volume/always-on/scale) live under the service type;
`logs`/`metrics` live under the service type via `addObservability()` in `index.ts`; anything

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: This says addApiUrlEverywhere() covers every command, but compute exec is deliberately skipped and the root/login options are declared manually. State these exceptions here so the architecture rule does not contradict the implementation and the exec gotcha below.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .claude/skills/developing-insta-cli/SKILL.md, line 62:

<comment>This says `addApiUrlEverywhere()` covers every command, but `compute exec` is deliberately skipped and the root/login options are declared manually. State these exceptions here so the architecture rule does not contradict the implementation and the exec gotcha below.</comment>

<file context>
@@ -37,6 +37,34 @@ npx tsx src/index.ts --help   # run the CLI from source
+   order the design's §9 gives (docs/copy merge right after the CLI release, never before).
+
+Where things go: settings (limits/volume/always-on/scale) live under the service type;
+`logs`/`metrics` live under the service type via `addObservability()` in `index.ts`; anything
+about this machine's agents or the project's agent governance lives under `agent`; anything about
+this machine's CLI configuration lives under `config`. `--api-url` is injected on every command by
</file context>

Comment thread src/commands/domain.ts
Comment on lines +201 to +202
export const domainCheck = checkDomain
export const domainDetach = removeDomain

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: domain check/domain detach now forward the hostname verbatim, while domain attach in this same change lowercases and trims before delegating to setDomain. insta domain attach Docs.MyApp.com binds docs.myapp.com, but insta domain check Docs.MyApp.com queries Docs.MyApp.com as typed. If the plane stores hostnames lowercase, a mixed-case check/detach misses the binding the attach just created. Normalize in the aliases to match the attach path.

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

<comment>`domain check`/`domain detach` now forward the hostname verbatim, while `domain attach` in this same change lowercases and trims before delegating to `setDomain`. `insta domain attach Docs.MyApp.com` binds `docs.myapp.com`, but `insta domain check Docs.MyApp.com` queries `Docs.MyApp.com` as typed. If the plane stores hostnames lowercase, a mixed-case check/detach misses the binding the attach just created. Normalize in the aliases to match the attach path.</comment>

<file context>
@@ -193,3 +195,8 @@ export async function domainRecordsRemove(domainName: string, id: string, opts:
+
+// `insta domain check|detach <hostname>` — the hostname-level reads and writes, for bought and
+// bring-your-own names alike (both are routed through the compute plane's custom-domain surface).
+export const domainCheck = checkDomain
+export const domainDetach = removeDomain
</file context>
Suggested change
export const domainCheck = checkDomain
export const domainDetach = removeDomain
export const domainCheck = (host: string, opts: { branch?: string; group?: string; json?: boolean }, deps?: DomainDeps) => checkDomain(host.trim().toLowerCase(), opts, deps)
export const domainDetach = (host: string, opts: { branch?: string; group?: string; json?: boolean }, deps?: DomainDeps) => removeDomain(host.trim().toLowerCase(), opts, deps)

expect(r.stderr).toContain(`unknown command '${retired[0]}'`)
}
expect(run('agent', 'policy', 'get', '--help').status).toBe(0)
}, 30_000)

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 first test now serializes 4 child-process spawns (second test 2 more), each still capped at 10s by run's timeout: 10000, inside one 30s budget. On a cold Windows runner, a single tsx cold-start can exceed 10s (matching the PR's own note about needing explicit Windows spawn timeouts), and a per-spawn timeout yields status: null with empty stderr, failing the assertion in a way that hides the real result. Raise the per-call timeout in run, or count each spawn's elapsed time against the budget instead of relying on 30s for all of them.

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

<comment>The first test now serializes 4 child-process spawns (second test 2 more), each still capped at 10s by `run`'s `timeout: 10000`, inside one 30s budget. On a cold Windows runner, a single tsx cold-start can exceed 10s (matching the PR's own note about needing explicit Windows spawn timeouts), and a per-spawn timeout yields `status: null` with empty `stderr`, failing the assertion in a way that hides the real result. Raise the per-call timeout in `run`, or count each spawn's elapsed time against the budget instead of relying on 30s for all of them.</comment>

<file context>
@@ -5,21 +5,25 @@ import { expect, it } from 'vitest'
+    expect(r.stderr).toContain(`unknown command '${retired[0]}'`)
+  }
+  expect(run('agent', 'policy', 'get', '--help').status).toBe(0)
+}, 30_000)
 
 it('rejects approval --always instead of promising a permanent grant', () => {
</file context>

Comment thread src/config.ts
* the file keeps the real login, so unsetting the override restores it. A custom host (insta-oss,
* a preview) is treated the same way — its session is equally foreign. */
export function pickApiUrl(parsed: GlobalConfig | null, env: NodeJS.ProcessEnv, cliOverride?: string): GlobalConfig {
const named = envFromEnvVar(env.INSTA_ENV)

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: When the supplied env object omits INSTA_ENV but the process has one set, pickApiUrl reads the ambient variable through envFromEnvVar's default parameter. Treat an omitted property as null before calling envFromEnvVar so the helper remains pure.

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

<comment>When the supplied `env` object omits `INSTA_ENV` but the process has one set, `pickApiUrl` reads the ambient variable through `envFromEnvVar`'s default parameter. Treat an omitted property as `null` before calling `envFromEnvVar` so the helper remains pure.</comment>

<file context>
@@ -33,41 +33,56 @@ export type ProjectConfig = { projectId: string; orgId: string; branch: string }
+ *  the file keeps the real login, so unsetting the override restores it. A custom host (insta-oss,
+ *  a preview) is treated the same way — its session is equally foreign. */
+export function pickApiUrl(parsed: GlobalConfig | null, env: NodeJS.ProcessEnv, cliOverride?: string): GlobalConfig {
+  const named = envFromEnvVar(env.INSTA_ENV)
+  const override = cliOverride ?? env.INSTA_API_URL ?? (named ? ENVS[named].api : undefined)
+  if (!parsed) return { apiUrl: override ?? DEFAULT_API }
</file context>
Suggested change
const named = envFromEnvVar(env.INSTA_ENV)
const named = env.INSTA_ENV === undefined ? null : envFromEnvVar(env.INSTA_ENV)

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.

1 issue found across 5 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/index.ts">

<violation number="1" location="src/index.ts:494">
P2: When `setup agent --api-url` runs on a machine persisted to staging, `setupAgent` can switch and persist production before setup because its environment planner cannot see the runtime URL. Pass the CLI override into setup’s environment planning, or prevent this alias from accepting the global flag.</violation>
</file>

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

Re-trigger cubic

Comment thread src/index.ts
// and third-party docs; it must keep working on every release. Permanent, like `services|svc`;
// hidden so the canonical `agent setup` is the only one help advertises.
const setupCompat = program.command('setup', { hidden: true }).description('Compatibility alias: `insta setup agent` is `insta agent setup`')
withSetupAgentOptions(setupCompat.command('agent').description('Alias of `insta agent setup`, kept for the console one-liner'))

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 setup agent --api-url runs on a machine persisted to staging, setupAgent can switch and persist production before setup because its environment planner cannot see the runtime URL. Pass the CLI override into setup’s environment planning, or prevent this alias from accepting the global flag.

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

<comment>When `setup agent --api-url` runs on a machine persisted to staging, `setupAgent` can switch and persist production before setup because its environment planner cannot see the runtime URL. Pass the CLI override into setup’s environment planning, or prevent this alias from accepting the global flag.</comment>

<file context>
@@ -480,6 +486,13 @@ cfg.command('install-mcp').description('Register the remote MCP server with codi
+// and third-party docs; it must keep working on every release. Permanent, like `services|svc`;
+// hidden so the canonical `agent setup` is the only one help advertises.
+const setupCompat = program.command('setup', { hidden: true }).description('Compatibility alias: `insta setup agent` is `insta agent setup`')
+withSetupAgentOptions(setupCompat.command('agent').description('Alias of `insta agent setup`, kept for the console one-liner'))
+
 // ---- feedback (agent + human hurdle reports → the InstaCloud team) ----
</file context>

… — 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.

1 issue found across 2 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/index.ts">

<violation number="1" location="src/index.ts:355">
P2: After this change, managed database volume reads still tell users to delete with `--delete`, but Commander rejects that option because it was removed here. Update `volumeLines()` to omit the delete instruction for managed database types while retaining it for compute.</violation>
</file>

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

Re-trigger cubic

Comment thread src/index.ts
.option('--memory <size>', 'memory ceiling, e.g. 512mb or 1gb').option('--cpu <n>', 'vCPU ceiling override (provider sizes: 1, 2, 4, 6, 8)')
.option('--json').option('--branch <branch>', 'branch (default: current)')
.action(guard((service, o) => computeCmd.serviceLimits(type, service, o)))
g.command('volume [service]').description(`Show or grow a ${type} service's data volume (the image's data directory). No flag: size and the plan cap (any plan). --size grows it (paid plans; grow-only). A managed database's volume cannot be deleted — remove the service instead. Billing is actual data stored — the size is a cap, not a price`)

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: After this change, managed database volume reads still tell users to delete with --delete, but Commander rejects that option because it was removed here. Update volumeLines() to omit the delete instruction for managed database types while retaining it for compute.

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

<comment>After this change, managed database volume reads still tell users to delete with `--delete`, but Commander rejects that option because it was removed here. Update `volumeLines()` to omit the delete instruction for managed database types while retaining it for compute.</comment>

<file context>
@@ -352,9 +352,8 @@ for (const type of ['redis', 'mysql', 'mongodb'] as const) {
     .option('--json').option('--branch <branch>', 'branch (default: current)')
     .action(guard((service, o) => computeCmd.serviceLimits(type, service, o)))
-  g.command('volume [service]').description(`Show, grow, or delete a ${type} service's data volume (mounted at the image's data directory). No flag: size and the plan cap (any plan). --size grows it (paid plans; grow-only). --delete DESTROYS the disk and ALL its data immediately (no undo). Billing is actual data stored — the size is a cap, not a price`)
+  g.command('volume [service]').description(`Show or grow a ${type} service's data volume (the image's data directory). No flag: size and the plan cap (any plan). --size grows it (paid plans; grow-only). A managed database's volume cannot be deleted — remove the service instead. Billing is actual data stored — the size is a cap, not a price`)
     .option('--size <gi>', 'new size in whole Gi, e.g. 10 (must be ≥ the current size)')
-    .option('--delete', 'destroy the volume and ALL its data (irreversible; back up first)')
</file context>

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