@mutagent/cli 0.1.336 → 0.1.338

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin/cli.js +681 -1015
  2. package/package.json +2 -2
package/dist/bin/cli.js CHANGED
@@ -1233,7 +1233,7 @@ var init_sdk_client = __esm(() => {
1233
1233
 
1234
1234
  // src/bin/cli.ts
1235
1235
  import { Command as Command17 } from "commander";
1236
- import chalk35 from "chalk";
1236
+ import chalk36 from "chalk";
1237
1237
  import { readFileSync as readFileSync15 } from "fs";
1238
1238
  import { join as join16, dirname as dirname4 } from "path";
1239
1239
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -2188,21 +2188,23 @@ async function loginWithPastedKey(initialEndpoint, output, wasFirstLogin) {
2188
2188
 
2189
2189
  // src/commands/auth.ts
2190
2190
  function createAuthCommand() {
2191
- const auth = new Command("auth").description("Authenticate with Mutagent platform").addHelpText("after", `
2191
+ const auth = new Command("auth").description("Sign in, show the sign-in state, or sign out on this machine").addHelpText("after", `
2192
2192
  Examples:
2193
2193
  ${chalk4.dim("$")} mutagent auth login
2194
2194
  ${chalk4.dim("$")} mutagent auth login --browser
2195
2195
  ${chalk4.dim("$")} mutagent auth status
2196
2196
  ${chalk4.dim("$")} mutagent auth logout
2197
+
2198
+ ${chalk4.dim("mutagent auth login is the same command as mutagent login.")}
2197
2199
  `);
2198
- auth.command("login").description("Authenticate and store API key").option("--browser", "Force browser-based authentication").option("--non-interactive", "Disable interactive prompts (auto-selects browser auth)").option("--endpoint <url>", "API endpoint", "https://api.mutagent.io").addHelpText("after", `
2200
+ auth.command("login").description("Same as mutagent login: sign in and store the API key on this machine").option("--browser", "Sign in with the browser (skip the method prompt)").option("--non-interactive", "Skip the method prompt and use browser sign-in (with --json it also needs --browser)").option("--endpoint <url>", "API endpoint", "https://api.mutagent.io").addHelpText("after", `
2199
2201
  Examples:
2200
- ${chalk4.dim("$")} mutagent auth login ${chalk4.dim("# Interactive (choose method)")}
2201
- ${chalk4.dim("$")} mutagent auth login --browser ${chalk4.dim("# Browser OAuth flow")}
2202
- ${chalk4.dim("$")} mutagent auth login --non-interactive ${chalk4.dim("# Auto browser flow (AI agents)")}
2202
+ ${chalk4.dim("$")} mutagent auth login ${chalk4.dim("# In a terminal, choose browser or API key")}
2203
+ ${chalk4.dim("$")} mutagent auth login --browser ${chalk4.dim("# Sign in with the browser")}
2204
+ ${chalk4.dim("$")} mutagent auth login --non-interactive ${chalk4.dim("# Browser sign-in without the method prompt (a person approves in the browser)")}
2203
2205
 
2204
- ${chalk4.dim("Note: this command is an alias for `mutagent login`.")}
2205
- ${chalk4.dim("See `mutagent login --help` for full documentation.")}
2206
+ This runs the same sign-in as mutagent login, with the same options and --json output.
2207
+ See mutagent login --help for the full description.
2206
2208
  `).action(async (options) => {
2207
2209
  const isJson = getJsonFlag(auth);
2208
2210
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -2236,7 +2238,7 @@ ${chalk4.dim("See `mutagent login --help` for full documentation.")}
2236
2238
  throw error;
2237
2239
  }
2238
2240
  });
2239
- auth.command("status").description("Check authentication status").addHelpText("after", `
2241
+ auth.command("status").description("Show the sign-in state, endpoint and active workspace").addHelpText("after", `
2240
2242
  Examples:
2241
2243
  ${chalk4.dim("$")} mutagent auth status
2242
2244
  ${chalk4.dim("$")} mutagent auth status --json
@@ -2322,37 +2324,29 @@ import { Command as Command2 } from "commander";
2322
2324
  import chalk5 from "chalk";
2323
2325
  init_errors();
2324
2326
  function createLoginCommand() {
2325
- const login = new Command2("login").description("Login to Mutagent (browser OAuth — signup, onboard, authorize CLI in one flow)").option("--browser", "Force browser-based authentication").option("--non-interactive", "Disable interactive prompts (auto-selects browser auth)").option("--endpoint <url>", "API endpoint", "https://api.mutagent.io").addHelpText("after", `
2327
+ const login = new Command2("login").description("Sign in to Mutagent and store the API key on this machine").option("--browser", "Sign in with the browser (skip the method prompt)").option("--non-interactive", "Skip the method prompt and use browser sign-in (with --json it also needs --browser)").option("--endpoint <url>", "API endpoint", "https://api.mutagent.io").addHelpText("after", `
2326
2328
  Examples:
2327
- ${chalk5.dim("$")} mutagent login ${chalk5.dim("# Browser OAuth (recommended — handles signup + onboarding)")}
2328
- ${chalk5.dim("$")} mutagent login --browser ${chalk5.dim("# Force browser flow (skip method prompt)")}
2329
- ${chalk5.dim("$")} mutagent login --non-interactive ${chalk5.dim("# Auto-browser for AI agents and CI")}
2330
-
2331
- ${chalk5.yellow("Browser OAuth flow (recommended for end users):")}
2332
- Opens app.mutagent.io in your browser. You will:
2333
- 1. Sign in or sign up with your account
2334
- 2. Complete onboarding (workspace, provider config)
2335
- 3. Authorize this CLI session
2336
- The CLI polls for completion and saves credentials when you authorize.
2337
-
2338
- ${chalk5.yellow("Automation / CI / AI Agents:")}
2339
- Set MUTAGENT_API_KEY environment variable before running:
2340
- ${chalk5.dim("$")} export MUTAGENT_API_KEY=mt_...
2341
- ${chalk5.dim("$")} mutagent login --json
2342
- When MUTAGENT_API_KEY is set, login skips the browser flow entirely and
2343
- validates the key directly. This is the canonical CI / agent path.
2344
-
2345
- Environment Variables:
2346
- MUTAGENT_API_KEY Pre-existing API key (skips interactive flow)
2347
- MUTAGENT_ENDPOINT Custom API endpoint
2348
- MUTAGENT_NON_INTERACTIVE=true Treat environment as non-interactive
2349
- CI=true Automatically enables non-interactive mode
2350
-
2351
- Verify after login:
2352
- ${chalk5.dim("$")} mutagent auth status ${chalk5.dim("# Show session, workspace, org")}
2353
- ${chalk5.dim("$")} mutagent workspaces list --json ${chalk5.dim("# List available workspaces")}
2354
-
2355
- ${chalk5.dim("Note: `mutagent auth login` is a back-compat alias and behaves identically.")}
2329
+ ${chalk5.dim("$")} mutagent login ${chalk5.dim("# In a terminal, choose browser or API key")}
2330
+ ${chalk5.dim("$")} mutagent login --browser ${chalk5.dim("# Sign in with the browser")}
2331
+ ${chalk5.dim("$")} mutagent login --non-interactive ${chalk5.dim("# Browser sign-in without the method prompt (a person approves in the browser)")}
2332
+ ${chalk5.dim("$")} MUTAGENT_API_KEY=mt_... mutagent login --json ${chalk5.dim("# Sign in with a key (CI)")}
2333
+
2334
+ Browser sign-in opens app.mutagent.io, where you sign in or sign up, finish onboarding
2335
+ and approve this CLI. The CLI waits up to 5 minutes and saves the credentials.
2336
+
2337
+ When MUTAGENT_API_KEY is set, login skips the browser and validates the key.
2338
+
2339
+ Environment:
2340
+ MUTAGENT_API_KEY Use this key; no browser
2341
+ MUTAGENT_ENDPOINT API endpoint (overrides --endpoint)
2342
+ MUTAGENT_NON_INTERACTIVE=true Same as --non-interactive
2343
+ CI=true Same as --non-interactive
2344
+
2345
+ Then:
2346
+ ${chalk5.dim("$")} mutagent auth status
2347
+ ${chalk5.dim("$")} mutagent workspaces list --json
2348
+
2349
+ mutagent auth login runs the same sign-in.
2356
2350
  `).action(async (options) => {
2357
2351
  const isJson = getJsonFlag(login);
2358
2352
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -2425,6 +2419,7 @@ Examples:
2425
2419
  ${chalk6.dim("$")} mutagent config get apiKey --json
2426
2420
 
2427
2421
  ${chalk6.dim("Keys: apiKey, endpoint, format, timeout, defaultWorkspace, defaultOrganization")}
2422
+ ${chalk6.dim("A key with no value set is reported as unknown.")}
2428
2423
  `).action((key) => {
2429
2424
  const isJson = getJsonFlag(config);
2430
2425
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -2453,7 +2448,7 @@ ${chalk6.dim("Keys: apiKey, endpoint, format, timeout, defaultWorkspace, default
2453
2448
  Examples:
2454
2449
  ${chalk6.dim("$")} mutagent config set workspace <workspace-id>
2455
2450
 
2456
- ${chalk6.dim("Persists workspace ID so you don't need to pass headers on every request.")}
2451
+ ${chalk6.dim("Commands use this workspace until you change it.")}
2457
2452
  `).action((id) => {
2458
2453
  const isJson = getJsonFlag(config);
2459
2454
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -2594,10 +2589,8 @@ Examples:
2594
2589
  ${chalk7.dim("$")} mutagent workspaces list
2595
2590
  ${chalk7.dim("$")} mutagent workspaces get <workspace-id>
2596
2591
 
2597
- Subcommands:
2598
- list, get
2599
-
2600
- Note: Workspace management (create, update, delete) is available in the Admin Panel only.
2592
+ Choose the active workspace with: mutagent config set workspace <workspace-id>
2593
+ Create or change workspaces in the dashboard: https://app.mutagent.io
2601
2594
  `);
2602
2595
  workspaces.command("list").description("List all workspaces").option("-l, --limit <n>", "Limit results", "50").option("-o, --offset <n>", "Offset for pagination").addHelpText("after", `
2603
2596
  Examples:
@@ -3149,32 +3142,30 @@ function collectCredentialFields(options) {
3149
3142
  return fields;
3150
3143
  }
3151
3144
  function registerAddCommand(parent) {
3152
- parent.command("add").description("Add a new provider configuration").requiredOption("-p, --provider <type>", "Provider type (openai, anthropic, azure, ...)").requiredOption("-n, --name <name>", "Display name for this provider").option("-k, --api-key <key>", "API key for the provider (every entry except Google Vertex)").option("--service-account-key <json>", "Google Vertex service-account credential JSON").option("--hosted-family <family>", `Hosted model family for gateway providers: ${HOSTED_FAMILIES.join(", ")}`).option("--base-url <url>", "Custom base URL for the provider API").option("--api-format <format>", apiFormatHelpText()).option("--resource-endpoint <url>", "Azure resource endpoint").option("--deployment-name <name>", "Azure deployment name").option("--api-version <version>", "Azure API version (e.g. 2024-08-01-preview)").option("--project-id <id>", "Google Vertex project ID").option("--region <region>", "Region (Vertex location, or AWS Bedrock region)").option("--set-default", "Set as the default provider of its kind").addHelpText("after", `
3145
+ parent.command("add").description("Add an LLM provider to the workspace").requiredOption("-p, --provider <type>", "LLM provider type (openai, anthropic, azure, ...)").requiredOption("-n, --name <name>", "Display name").option("-k, --api-key <key>", "API key (every type except vertex)").option("--service-account-key <json>", "Google Vertex service-account credential JSON").option("--hosted-family <family>", `Model family a gateway (azure, vertex, bedrock) serves: ${HOSTED_FAMILIES.join(", ")}`).option("--base-url <url>", "Base URL of the API").option("--api-format <format>", apiFormatHelpText()).option("--resource-endpoint <url>", "Azure resource endpoint").option("--deployment-name <name>", "Azure deployment name").option("--api-version <version>", "Azure API version (e.g. 2024-08-01-preview)").option("--project-id <id>", "Google Vertex project ID").option("--region <region>", "Region (Vertex location, or AWS Bedrock region)").option("--set-default", "Make it the workspace's default LLM provider of its type").addHelpText("after", `
3153
3146
  Examples:
3154
3147
  ${chalk8.dim("$")} mutagent providers add --provider openai --name "My OpenAI" --api-key $OPENAI_API_KEY
3155
3148
  ${chalk8.dim("$")} mutagent providers add --provider azure --name "Azure GPT" --api-key $KEY \\
3156
3149
  --hosted-family openai --resource-endpoint https://my.openai.azure.com --deployment-name gpt-5
3157
- ${chalk8.dim("$")} mutagent providers add --provider bedrock --name "Bedrock Claude" --api-key $KEY \\
3158
- --hosted-family anthropic --region us-east-1
3159
- ${chalk8.dim("$")} mutagent providers add --provider custom --name "Ollama" --api-key none --base-url https://ollama.internal --json
3160
- ${chalk8.dim("$")} mutagent providers add --provider custom --name "Claude-compatible" --api-key $KEY \\
3161
- --base-url https://llm.internal --api-format anthropic
3162
-
3163
- Gateway providers (${[...GATEWAY_PROVIDERS].join(", ")}) serve another vendor's models and
3164
- require --hosted-family. Direct providers must not set it.
3165
-
3166
- ${apiFormatHelpText()}. An endpoint speaking the Anthropic request shape needs
3167
- --api-format anthropic; left unset, the server's default applies. Passing it to a
3168
- provider that speaks its own API is an error, not a silent no-op.
3169
-
3170
- Providers are configured for your workspace. Set the target with:
3171
- ${chalk8.dim("$")} mutagent config set workspace <workspace-id>
3172
-
3173
- The API key is encrypted server-side and never returned in plain text.
3150
+ ${chalk8.dim("$")} mutagent providers add --provider bedrock --name "Bedrock Claude" --api-key $KEY --region us-east-1
3151
+ ${chalk8.dim("$")} mutagent providers add --provider custom --name "My gateway" --api-key $KEY \\
3152
+ --base-url https://llm.example.com/v1 --api-format anthropic
3153
+
3154
+ azure and vertex serve another vendor's models and need --hosted-family; bedrock serves
3155
+ anthropic and needs none. Other LLM provider types must not set it.
3156
+ --api-format applies only to the LLM provider types its option line lists. Use
3157
+ anthropic for an endpoint that speaks the Anthropic API; unset, the server default applies.
3158
+ The base URL must be a public address: hosts ending in .internal, .local, .localhost or
3159
+ .home.arpa are refused.
3160
+
3161
+ Adds the LLM provider to the configured workspace (mutagent config set workspace <id>).
3162
+ The key is encrypted and never returned.
3163
+ Workspace default model: when the workspace has none, adding a keyed LLM provider sets
3164
+ one on the server. An existing workspace default model is never changed.
3174
3165
 
3175
3166
  AI Agent Directive:
3176
- Run 'mutagent providers list --json' first to check whether the provider already exists.
3177
- This command writes a credential — confirm with the user before running it.
3167
+ Run 'mutagent providers list --json' first to check whether the LLM provider already exists.
3168
+ This command writes a credential: confirm with the user before running it.
3178
3169
  `).action(async (options) => {
3179
3170
  const isJson = getJsonFlag(parent);
3180
3171
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -3285,14 +3276,14 @@ init_sdk_client();
3285
3276
  import chalk9 from "chalk";
3286
3277
  init_errors();
3287
3278
  function registerUpdateCommand(parent) {
3288
- parent.command("update").description("Update an existing provider configuration").argument("<id>", "Provider ID (from: mutagent providers list)").option("-n, --name <name>", "Updated display name").option("-k, --api-key <key>", "Updated API key (will be re-encrypted)").option("--active <bool>", "Activate or deactivate (true|false)").option("--set-default", "Set as the default provider of its kind").option("--base-url <url>", 'Updated base URL (use "" to clear)').addHelpText("after", `
3279
+ parent.command("update").description("Update an LLM provider").argument("<id>", "LLM provider ID (from: mutagent providers list)").option("-n, --name <name>", "Updated display name").option("-k, --api-key <key>", "New API key (stored encrypted)").option("--active <bool>", "Activate or deactivate (true|false)").option("--set-default", "Make it the workspace's default LLM provider of its type").option("--base-url <url>", 'Updated base URL (use "" to clear)').addHelpText("after", `
3289
3280
  Examples:
3290
- ${chalk9.dim("$")} mutagent providers update <id> --name "New Name"
3291
- ${chalk9.dim("$")} mutagent providers update <id> --api-key $NEW_KEY --json
3292
- ${chalk9.dim("$")} mutagent providers update <id> --active false
3293
- ${chalk9.dim("$")} mutagent providers update <id> --set-default --json
3281
+ ${chalk9.dim("$")} mutagent providers update <provider-id> --name "New Name"
3282
+ ${chalk9.dim("$")} mutagent providers update <provider-id> --api-key $NEW_KEY --json
3283
+ ${chalk9.dim("$")} mutagent providers update <provider-id> --active false
3284
+ ${chalk9.dim("$")} mutagent providers update <provider-id> --set-default --json
3294
3285
 
3295
- PATCH semantics — only provided fields are updated.
3286
+ Only the fields you pass change. An update never sets the workspace default model.
3296
3287
  `).action(async (id, options) => {
3297
3288
  const isJson = getJsonFlag(parent);
3298
3289
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -3386,14 +3377,16 @@ init_sdk_client();
3386
3377
  import chalk10 from "chalk";
3387
3378
  init_errors();
3388
3379
  function registerDeleteCommand(parent) {
3389
- parent.command("delete").description("Delete a provider configuration").argument("<id>", "Provider ID (from: mutagent providers list)").option("-f, --force", "Skip confirmation prompt").addHelpText("after", `
3380
+ parent.command("delete").description("Delete an LLM provider").argument("<id>", "LLM provider ID (from: mutagent providers list)").option("-f, --force", "Confirm the delete (required unless --json)").addHelpText("after", `
3390
3381
  Examples:
3391
- ${chalk10.dim("$")} mutagent providers delete <id>
3392
- ${chalk10.dim("$")} mutagent providers delete <id> --force
3393
- ${chalk10.dim("$")} mutagent providers delete <id> --force --json
3382
+ ${chalk10.dim("$")} mutagent providers delete <provider-id> --force
3383
+ ${chalk10.dim("$")} mutagent providers delete <provider-id> --force --json
3384
+
3385
+ Without --force (and without --json) the command refuses; it never prompts.
3386
+ The stored key is deleted and cannot be recovered. Deleting an unknown ID succeeds.
3394
3387
 
3395
- ${chalk10.dim("Note: --force is required. The CLI is non-interactive — confirm with the user via your native flow, then pass --force.")}
3396
- ${chalk10.dim("Warning: API keys are AES-256-GCM encrypted and irrecoverable after deletion. Agents referencing this provider will lose their model config.")}
3388
+ AI Agent Directive:
3389
+ Confirm with the user before running it, then pass --force (or --json). The stored key cannot be recovered.
3397
3390
  `).action(async (id, options) => {
3398
3391
  const isJson = getJsonFlag(parent);
3399
3392
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -4115,44 +4108,42 @@ function statusGlyph(result) {
4115
4108
  }
4116
4109
  }
4117
4110
  function registerMirrorCommand(parent) {
4118
- parent.command("mirror").description("Copy the local Helix provider logins into the workspace, on request").option("-y, --yes", "Consent without prompting (required when non-interactive)").option("--no-overwrite", "Leave providers the workspace already has untouched").option("--workspace <id>", "Target workspace (default: the configured one)").option("--channel <channel>", `Helix channel whose store is read: ${HELIX_CHANNELS.join(" | ")}`, "stable").option("--provider <id>", "Only this local provider id (repeatable)", (value, prev = []) => [...prev, value]).addHelpText("after", `
4111
+ parent.command("mirror").description("Copy the API keys from your local Helix login store into the workspace as LLM providers").option("-y, --yes", "Consent without prompting (required when non-interactive)").option("--no-overwrite", "Leave LLM providers the workspace already has untouched").option("--workspace <id>", "Target workspace for this run (default: the configured one)").option("--channel <channel>", `Helix channel whose store is read: ${HELIX_CHANNELS.join(" | ")}`, "stable").option("--provider <id>", "Only this local LLM provider id (repeatable)", (value, prev = []) => [...prev, value]).addHelpText("after", `
4119
4112
  Examples:
4120
4113
  ${chalk11.dim("$")} mutagent providers mirror
4114
+ ${chalk11.dim("$")} mutagent providers mirror --json ${chalk11.dim("Show the plan without sending anything")}
4121
4115
  ${chalk11.dim("$")} mutagent providers mirror --yes --json
4122
4116
  ${chalk11.dim("$")} mutagent providers mirror --provider zai --provider openrouter
4123
- ${chalk11.dim("$")} mutagent providers mirror --no-overwrite --workspace <workspace-id>
4117
+ ${chalk11.dim("$")} mutagent providers mirror --no-overwrite
4124
4118
  ${chalk11.dim("$")} mutagent providers mirror --channel rc
4125
4119
 
4126
- Reads the durable Helix agent dir ($HELIX_CODING_AGENT_DIR, else ~/.mutagent/agent;
4127
- ~/.mutagent/agent-rc with --channel rc): api_key entries in auth.json and custom
4128
- providers in models.json. OAuth-shaped logins are listed as unsupported, not copied.
4129
- Exception: the pinned OpenRouter permanent-key shape is treated as a literal
4130
- API key; other OAuth and Foundry companion credentials remain unsupported.
4131
- Only literal keys are mirrored: a key stored as a reference (!command, $VAR,
4132
- \${VAR}, or a bare ENV_NAME) is listed as skipped and is never evaluated.
4133
-
4134
- On an existing row the plan states what changes before consent: an endpoint
4135
- moved (old → new), an endpoint override removed locally (reset to default),
4136
- and the wire format a Custom provider is written with.
4137
- Supported native model declarations, capabilities and compatibility fields are
4138
- copied too. Headers, companion credentials and unsupported fields block that
4139
- provider. Mirroring is not proof of Cloud execution; no model roster is invented.
4140
-
4141
- anthropic, openai, google, xai and deepseek land under their own provider id.
4142
- zai, zai-coding-cn, moonshotai, moonshotai-cn, kimi-coding, openrouter and groq
4143
- land as Custom providers with the harness's endpoint and harnessProvider metadata.
4144
-
4145
- Requires \`mutagent auth login\` first — nothing is read from disk without a
4146
- platform identity and a workspace. Keys go only into the request body to the
4147
- configured endpoint; they are never printed, logged or written anywhere else.
4148
-
4149
- Consent is asked once per run. Non-interactive callers must pass --yes;
4150
- without it the run refuses (exit 2). Exit 3: no platform identity.
4151
- Exit 1: at least one entry failed — see the per-entry report.
4152
-
4153
- In --json, each written result carries "endpoint" {from, to} (a URL or
4154
- "default") and, for Custom providers, "apiFormat" {from, to}; the refusal
4155
- without --yes carries the same per-entry "plan". Names and URLs only.
4120
+ Reads the API keys in your local Helix login store (~/.mutagent/agent, ~/.mutagent/agent-rc
4121
+ with --channel rc, or $HELIX_CODING_AGENT_DIR when set) and creates or updates the
4122
+ matching LLM providers in the workspace. OAuth logins and keys stored as references
4123
+ ($VAR, \${VAR}, !command or a bare ENV_NAME) are listed and skipped; a reference is never
4124
+ evaluated. Exception: an openrouter login that holds a permanent key is copied as an
4125
+ API key. Before anything is sent, the plan shows every create and update, endpoint
4126
+ changes and the API format a custom LLM provider is written with.
4127
+
4128
+ anthropic, openai, google, xai and deepseek become LLM providers of the same type.
4129
+ zai, zai-coding-cn, moonshotai, moonshotai-cn, kimi-coding, openrouter and groq become
4130
+ custom LLM providers. Declared model lists are copied. An entry with headers or other
4131
+ fields that cannot be copied is skipped.
4132
+
4133
+ Workspace default model: when the workspace has none, the server sets one from the
4134
+ first LLM provider this run creates, and the output names it. An existing workspace
4135
+ default model is never changed. Change it with
4136
+ \`mutagent helix models default <provider/model>\`.
4137
+
4138
+ Mirroring does not test the keys: check \`mutagent helix models\`, then run a model.
4139
+
4140
+ Requires \`mutagent login\` and a workspace. Keys are sent only to the configured
4141
+ endpoint and are never printed. Consent is asked once per run; without a terminal,
4142
+ --yes is required. Without it the run exits 2 (with --json it returns the plan).
4143
+ Exit 3: not logged in or no workspace. Exit 1: at least one entry failed.
4144
+
4145
+ JSON: each written result carries "endpoint" {from, to} and, for custom LLM providers,
4146
+ "apiFormat" {from, to}; workspaceDefaultModelSet names a default the run set.
4156
4147
 
4157
4148
  AI Agent Directive:
4158
4149
  This command sends credentials off the machine. Run it with --json first
@@ -4281,24 +4272,20 @@ function validateProviderType(type) {
4281
4272
  function createProvidersCommand() {
4282
4273
  const providers = new Command5("providers").description("Manage LLM provider configurations (BYOK)").addHelpText("after", `
4283
4274
  Examples:
4275
+ ${chalk12.dim("$")} mutagent providers mirror
4284
4276
  ${chalk12.dim("$")} mutagent providers list
4285
4277
  ${chalk12.dim("$")} mutagent providers get <provider-id>
4286
4278
  ${chalk12.dim("$")} mutagent providers add --provider openai --name "My OpenAI" --api-key $KEY
4287
- ${chalk12.dim("$")} mutagent providers update <id> --name "New Name"
4288
- ${chalk12.dim("$")} mutagent providers delete <id> --force
4289
4279
  ${chalk12.dim("$")} mutagent providers test <provider-id>
4290
- ${chalk12.dim("$")} mutagent providers mirror --yes --json
4291
-
4292
- Provider Types:
4293
- ${VALID_PROVIDER_TYPES.join(", ")}
4280
+ ${chalk12.dim("$")} mutagent providers update <provider-id> --name "New Name"
4281
+ ${chalk12.dim("$")} mutagent providers delete <provider-id> --force
4294
4282
 
4295
- Subcommands:
4296
- list, get, add, update, delete, test, mirror
4283
+ LLM providers hold the keys Helix uses to call models in the cloud. See what they
4284
+ unlock with \`mutagent helix models\`.
4297
4285
 
4298
- Tip:
4299
- Use --models on list to see available models per provider.
4286
+ LLM provider types: ${VALID_PROVIDER_TYPES.join(", ")}
4300
4287
  `);
4301
- providers.command("list").description("List all providers").option("-l, --limit <n>", "Limit results", "50").option("-o, --offset <n>", "Offset for pagination").option("-t, --type <type>", "Filter by provider type").option("-m, --models", "Show available models per provider (calls /providers/catalog)").addHelpText("after", `
4288
+ providers.command("list").description("List the workspace's LLM providers").option("-l, --limit <n>", "Limit results", "50").option("-o, --offset <n>", "Offset for pagination").option("-t, --type <type>", "Filter by LLM provider type").option("-m, --models", "Show catalogue models for each LLM provider type (for Helix cloud models: mutagent helix models)").addHelpText("after", `
4302
4289
  Examples:
4303
4290
  ${chalk12.dim("$")} mutagent providers list
4304
4291
  ${chalk12.dim("$")} mutagent providers list --type openai
@@ -4377,7 +4364,7 @@ Examples:
4377
4364
  handleError(error, isJson);
4378
4365
  }
4379
4366
  });
4380
- providers.command("get").description("Get provider details").argument("<id>", "Provider ID (from: mutagent providers list)").addHelpText("after", `
4367
+ providers.command("get").description("Show one LLM provider").argument("<id>", "LLM provider ID (from: mutagent providers list)").addHelpText("after", `
4381
4368
  Examples:
4382
4369
  ${chalk12.dim("$")} mutagent providers get <provider-id>
4383
4370
  ${chalk12.dim("$")} mutagent providers get <provider-id> --json
@@ -4407,12 +4394,12 @@ Examples:
4407
4394
  handleError(error, isJson);
4408
4395
  }
4409
4396
  });
4410
- providers.command("test").description("Test provider connectivity").argument("<id>", "Provider ID (from: mutagent providers list)").addHelpText("after", `
4397
+ providers.command("test").description("Test an LLM provider's connection").argument("<id>", "LLM provider ID (from: mutagent providers list)").addHelpText("after", `
4411
4398
  Examples:
4412
4399
  ${chalk12.dim("$")} mutagent providers test <provider-id>
4413
4400
  ${chalk12.dim("$")} mutagent providers test <provider-id> --json
4414
4401
 
4415
- ${chalk12.dim("Tests connectivity and lists available models for the provider.")}
4402
+ ${chalk12.dim("Tests the connection and lists the models the LLM provider reports.")}
4416
4403
  `).action(async (id) => {
4417
4404
  const isJson = getJsonFlag(providers);
4418
4405
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -5281,13 +5268,14 @@ that teaches coding agents how to use the Mutagent CLI effectively.
5281
5268
  // src/commands/init.ts
5282
5269
  var LOGIN_GUIDANCE = "Run: mutagent login — sign-up happens in the browser. Then re-run mutagent init.";
5283
5270
  function createInitCommand(deps = {}) {
5284
- const init = new Command7("init").description("Initialize Mutagent in your project").option("--non-interactive", "No-op kept for back-compat (init never prompts)").addHelpText("after", `
5271
+ const init = new Command7("init").description("Initialize Mutagent in your project").option("--non-interactive", "Has no effect: init never prompts").addHelpText("after", `
5285
5272
  Examples:
5286
5273
  ${chalk14.dim("$")} mutagent init # Write .mutagentrc.json + install the CLI skill
5287
5274
 
5288
5275
  What it does:
5289
5276
  1. Writes .mutagentrc.json (endpoint / workspace / org) — skipped if present
5290
5277
  2. Installs the Mutagent CLI skill into ${SKILL_DIR}/
5278
+ It never prompts. Without a sign-in it prints what to run and exits.
5291
5279
  `).action(async (options) => {
5292
5280
  const isJson = getJsonFlag(init);
5293
5281
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -5405,7 +5393,7 @@ init_errors();
5405
5393
  init_sdk_client();
5406
5394
  var PROVIDERS_URL = "https://app.mutagent.io/settings/providers";
5407
5395
  function createUsageCommand() {
5408
- const usage = new Command8("usage").description("Show account resource counts (providers, workspaces) and provider status").addHelpText("after", `
5396
+ const usage = new Command8("usage").description("Count the workspace's LLM providers and the workspaces you can see").addHelpText("after", `
5409
5397
  Examples:
5410
5398
  ${chalk15.dim("$")} mutagent usage
5411
5399
  ${chalk15.dim("$")} mutagent usage --json
@@ -5463,6 +5451,7 @@ Examples:
5463
5451
  import { readFileSync as readFileSync9 } from "node:fs";
5464
5452
  import { basename } from "node:path";
5465
5453
  import { Command as Command9 } from "commander";
5454
+ import chalk16 from "chalk";
5466
5455
 
5467
5456
  // src/commands/hooks/import-transcript.ts
5468
5457
  var MAX_SPAN_TEXT = 4000;
@@ -6372,32 +6361,17 @@ function installHooks(cwd) {
6372
6361
  // src/commands/hooks/index.ts
6373
6362
  function createHooksCommand() {
6374
6363
  const hooks = new Command9("hooks").description("Hook handlers for AI coding assistants").addHelpText("after", `
6375
- Claude Code Session Telemetry:
6376
- Sends lightweight session activity to the Mutagent traces API for observability.
6364
+ Claude Code session telemetry:
6365
+ Sends session activity (turns, tool calls, subagents, compactions) to the Mutagent
6366
+ traces API.
6377
6367
 
6378
- Install by adding to .claude/settings.local.json:
6368
+ ${chalk16.dim("$")} mutagent hooks install ${chalk16.dim("Add the Mutagent hooks to .claude/settings.local.json (11 events)")}
6379
6369
 
6380
- {
6381
- "hooks": {
6382
- "SessionStart": [{"matcher": "startup", "hooks": [{"type": "command", "command": "mutagent hooks claude-code session-start"}]}],
6383
- "Stop": [{"hooks": [{"type": "command", "command": "mutagent hooks claude-code session-end"}]}],
6384
- "PreToolUse": [{"hooks": [{"type": "command", "command": "mutagent hooks claude-code pre-tool-use"}]}],
6385
- "PostToolUse": [{"hooks": [{"type": "command", "command": "mutagent hooks claude-code post-tool-use"}]}],
6386
- "UserPromptSubmit": [{"hooks": [{"type": "command", "command": "mutagent hooks claude-code user-prompt-submit"}]}],
6387
- "SubagentStart": [{"hooks": [{"type": "command", "command": "mutagent hooks claude-code subagent-start"}]}],
6388
- "SubagentStop": [{"hooks": [{"type": "command", "command": "mutagent hooks claude-code subagent-stop"}]}],
6389
- "PreCompact": [{"hooks": [{"type": "command", "command": "mutagent hooks claude-code pre-compact"}]}],
6390
- "PostCompact": [{"hooks": [{"type": "command", "command": "mutagent hooks claude-code post-compact"}]}],
6391
- "PostToolUseFailure": [{"hooks": [{"type": "command", "command": "mutagent hooks claude-code post-tool-use-failure"}]}]
6392
- }
6393
- }
6394
-
6395
- Or run: mutagent hooks install
6370
+ The claude-code subcommands are what those hooks run; Claude Code calls them.
6396
6371
  `);
6397
- hooks.command("import").argument("<files...>", "Session transcript files (.jsonl)").description("Import harness session transcripts as traces").option("--max-spans <n>", "Cap spans per session", "200").option("--dry-run", "Convert and report, but do not send").addHelpText("after", `
6398
- Sources traces from transcripts already on disk, rather than capturing them
6399
- live. mutagent-pi discovers these same files for its own /trace viewer; this
6400
- ships them to the backend instead.
6372
+ hooks.command("import").argument("<files...>", "Session transcript files (.jsonl)").description("Upload Helix session transcripts as traces").option("--max-spans <n>", "Cap spans per session", "200").option("--dry-run", "Convert and report, but do not send").addHelpText("after", `
6373
+ Uploads Helix session transcripts that are already on disk, instead of capturing
6374
+ them live.
6401
6375
 
6402
6376
  Where they live:
6403
6377
  ~/.mutagent/sessions* Helix
@@ -6494,11 +6468,16 @@ unparseable lines without sending anything.
6494
6468
  `);
6495
6469
  emit(batch.length, false);
6496
6470
  });
6497
- hooks.command("install").description("Install Mutagent hooks into .claude/settings.local.json (safe merge — never overwrites existing hooks)").option("--cwd <dir>", "Target directory (defaults to cwd)", process.cwd()).addHelpText("after", `
6498
- Reads existing .claude/settings.local.json (if present) and deep-merges
6499
- Mutagent telemetry hooks into each event array (all 10 events). Skips any
6500
- hook already present (checked by command string) so running this multiple
6501
- times is safe.
6471
+ hooks.command("install").description("Add the Mutagent hooks to .claude/settings.local.json (merges; existing hooks stay)").option("--cwd <dir>", "Target directory", process.cwd()).addHelpText("after", `
6472
+ Examples:
6473
+ ${chalk16.dim("$")} mutagent hooks install
6474
+ ${chalk16.dim("$")} mutagent hooks install --cwd ../other-project
6475
+
6476
+ Merges the Mutagent hooks into all 11 Claude Code events of
6477
+ .claude/settings.local.json (created if missing). Hooks already present are left
6478
+ alone. An old Stop → claude-code session-end entry is removed.
6479
+ Stop runs 'claude-code stop' and SessionEnd runs 'claude-code session-end'.
6480
+ Safe to run again.
6502
6481
  `).action((opts) => {
6503
6482
  const targetDir = opts.cwd ?? process.cwd();
6504
6483
  const isJson = Boolean(opts.json);
@@ -6597,7 +6576,7 @@ times is safe.
6597
6576
 
6598
6577
  // src/commands/feedback.ts
6599
6578
  import { Command as Command10 } from "commander";
6600
- import chalk18 from "chalk";
6579
+ import chalk19 from "chalk";
6601
6580
  import { type as osType, release as osRelease } from "os";
6602
6581
  import { readFileSync as readFileSync11 } from "fs";
6603
6582
  import { join as join13, dirname as dirname2 } from "path";
@@ -6618,7 +6597,7 @@ import {
6618
6597
  readSync,
6619
6598
  closeSync
6620
6599
  } from "fs";
6621
- import chalk16 from "chalk";
6600
+ import chalk17 from "chalk";
6622
6601
  var TAIL_BYTES = 200000;
6623
6602
  function defaultReadTail(path, tailBytes) {
6624
6603
  const { size } = fsStatSync(path);
@@ -6694,7 +6673,7 @@ Or omit the path to auto-detect the newest coding-agent session.`);
6694
6673
  }
6695
6674
  }
6696
6675
  if (!newest) {
6697
- warn(chalk16.yellow("⚠ No coding-agent transcript found; sending feedback without one."));
6676
+ warn(chalk17.yellow("⚠ No coding-agent transcript found; sending feedback without one."));
6698
6677
  return null;
6699
6678
  }
6700
6679
  const { content, truncated } = readTail(newest.path, TAIL_BYTES);
@@ -6708,7 +6687,7 @@ Or omit the path to auto-detect the newest coding-agent session.`);
6708
6687
  }
6709
6688
 
6710
6689
  // src/lib/feedback-safety.ts
6711
- import chalk17 from "chalk";
6690
+ import chalk18 from "chalk";
6712
6691
 
6713
6692
  // src/lib/secret-scan.ts
6714
6693
  var PATTERNS = [
@@ -6903,9 +6882,9 @@ function buildPreview(transcript) {
6903
6882
  function buildTranscriptWarning(preview) {
6904
6883
  const kb = (preview.bytes / 1024).toFixed(1);
6905
6884
  return [
6906
- chalk17.yellow("⚠ About to upload your coding-agent session transcript."),
6907
- ` ${chalk17.bold("Source:")} ${preview.sourcePath}`,
6908
- ` ${chalk17.bold("Sending:")} ${String(preview.fileCount)} file, ${kb} KB${preview.truncated ? " (tail only)" : ""}`,
6885
+ chalk18.yellow("⚠ About to upload your coding-agent session transcript."),
6886
+ ` ${chalk18.bold("Source:")} ${preview.sourcePath}`,
6887
+ ` ${chalk18.bold("Sending:")} ${String(preview.fileCount)} file, ${kb} KB${preview.truncated ? " (tail only)" : ""}`,
6909
6888
  "",
6910
6889
  " A session transcript typically contains:",
6911
6890
  " • source code and file contents you or the agent opened",
@@ -6914,9 +6893,9 @@ function buildTranscriptWarning(preview) {
6914
6893
  " • internal hostnames, service names, and URLs",
6915
6894
  " • business context from your prompts and the agent replies",
6916
6895
  "",
6917
- ` ${chalk17.dim("Credentials are scanned for and redacted automatically, but detection")}`,
6918
- ` ${chalk17.dim("is best-effort — review the source file if the session touched secrets.")}`,
6919
- ` ${chalk17.dim("Omit --attach-transcript to send feedback without it.")}`
6896
+ ` ${chalk18.dim("Credentials are scanned for and redacted automatically, but detection")}`,
6897
+ ` ${chalk18.dim("is best-effort — review the source file if the session touched secrets.")}`,
6898
+ ` ${chalk18.dim("Omit --attach-transcript to send feedback without it.")}`
6920
6899
  ].join(`
6921
6900
  `);
6922
6901
  }
@@ -6928,12 +6907,12 @@ function buildRedactionReport(redactions) {
6928
6907
  byKind.set(match.label, (byKind.get(match.label) ?? 0) + 1);
6929
6908
  }
6930
6909
  const lines = [
6931
- chalk17.green(`✓ Redacted ${String(redactions.length)} credential${redactions.length === 1 ? "" : "s"} from the transcript before upload:`)
6910
+ chalk18.green(`✓ Redacted ${String(redactions.length)} credential${redactions.length === 1 ? "" : "s"} from the transcript before upload:`)
6932
6911
  ];
6933
6912
  for (const [label, count] of byKind) {
6934
6913
  lines.push(` • ${label} ×${String(count)}`);
6935
6914
  }
6936
- lines.push(chalk17.dim(" These were replaced with [REDACTED:…] markers and never left your machine."));
6915
+ lines.push(chalk18.dim(" These were replaced with [REDACTED:…] markers and never left your machine."));
6937
6916
  return lines.join(`
6938
6917
  `);
6939
6918
  }
@@ -6941,17 +6920,17 @@ function buildByDesignNotice(entries) {
6941
6920
  if (entries.length === 0)
6942
6921
  return "";
6943
6922
  const lines = [
6944
- chalk17.cyan("ℹ Heads up — this may describe intended behaviour:"),
6923
+ chalk18.cyan("ℹ Heads up — this may describe intended behaviour:"),
6945
6924
  ""
6946
6925
  ];
6947
6926
  for (const entry of entries) {
6948
- lines.push(` ${chalk17.bold(entry.title)}`);
6927
+ lines.push(` ${chalk18.bold(entry.title)}`);
6949
6928
  for (const line of wrap(entry.reality, 76)) {
6950
6929
  lines.push(` ${line}`);
6951
6930
  }
6952
6931
  lines.push("");
6953
6932
  }
6954
- lines.push(chalk17.dim(" Sending anyway — if this is a regression rather than the design,"), chalk17.dim(" your report is exactly what we need. Nothing was withheld."));
6933
+ lines.push(chalk18.dim(" Sending anyway — if this is a regression rather than the design,"), chalk18.dim(" your report is exactly what we need. Nothing was withheld."));
6955
6934
  return lines.join(`
6956
6935
  `);
6957
6936
  }
@@ -7062,18 +7041,18 @@ function buildPayload(feedback, category, title, session, transcript) {
7062
7041
  }
7063
7042
  function createFeedbackCommand() {
7064
7043
  const feedback = new Command10("feedback").description("Send product feedback to Mutagent").addHelpText("after", `
7065
- ${chalk18.bold("Examples:")}
7066
- ${chalk18.cyan('mutagent feedback send "Optimizer results were great"')}
7067
- ${chalk18.cyan('mutagent feedback send "Eval gate was confusing" --category stage:evaluate')}
7068
- ${chalk18.cyan('mutagent feedback send "CLI crashed on export" --category cli --json')}
7044
+ ${chalk19.bold("Examples:")}
7045
+ ${chalk19.cyan('mutagent feedback send "helix session attach was easy to follow"')}
7046
+ ${chalk19.cyan('mutagent feedback send "Eval gate was confusing" --category stage:evaluate')}
7047
+ ${chalk19.cyan('mutagent feedback send "CLI crashed on export" --category cli --json')}
7069
7048
 
7070
- ${chalk18.yellow("AI Agent (MANDATORY):")}
7049
+ AI Agent Directive:
7071
7050
  ALWAYS use --json: mutagent feedback send "..." --category cli --json
7072
- ${chalk18.yellow("Do NOT attach a transcript by default.")} --attach-transcript uploads the
7051
+ ${chalk19.yellow("Do NOT attach a transcript by default.")} --attach-transcript uploads the
7073
7052
  coding-agent SESSION JSONL — source code, absolute paths, branch and repository
7074
7053
  names, internal hostnames, and the business context of the operator's prompts.
7075
7054
  Use it only when the operator explicitly asks for it. See:
7076
- ${chalk18.dim("mutagent feedback send --help")}
7055
+ ${chalk19.dim("mutagent feedback send --help")}
7077
7056
  `).action(() => {
7078
7057
  feedback.help();
7079
7058
  });
@@ -7081,42 +7060,34 @@ ${chalk18.yellow("AI Agent (MANDATORY):")}
7081
7060
  return feedback;
7082
7061
  }
7083
7062
  function registerFeedbackSend(feedback) {
7084
- feedback.command("send").description("Send product feedback about the Mutagent platform or CLI").argument("<feedback>", "The feedback body (content), max 10000 characters").option("--title <string>", "Optional 5–8 word summary of the session timeline").option("--category <value>", `Feedback category: 'cli', 'helix', or 'stage:<${VALID_STAGES.join("|")}>'`, "cli").option("--session <id>", "Link this feedback to a session id (maps to server sessionId)").option("--attach-transcript [path]", "Attach the coding-agent session JSONL. Bare = auto-detect newest; or pass an explicit path.").addHelpText("after", `
7085
- ${chalk18.bold("Arguments & flags:")}
7086
- ${chalk18.bold("<feedback>")} Feedback body / content (required, ≤10000 chars)
7087
- ${chalk18.bold("--title <string>")} Optional 5–8 word summary of the session timeline
7088
- ${chalk18.bold("--category <value>")} ${chalk18.bold("cli")} (default) | ${chalk18.bold("helix")} | ${chalk18.bold("stage:<")}${VALID_STAGES.join("|")}${chalk18.bold(">")}
7089
- ${chalk18.dim("stage = the lifecycle skill (evaluator/diagnostics live under stage:*)")}
7090
- ${chalk18.bold("--session <id>")} Link feedback to a session id (server sessionId)
7091
- ${chalk18.bold("--attach-transcript")} ${chalk18.dim("[path]")} Attach the coding-agent session JSONL (bare = auto-detect newest)
7092
- ${chalk18.bold("--json")} Structured output (MANDATORY for AI agents)
7093
-
7094
- ${chalk18.bold("Examples:")}
7095
- ${chalk18.dim("$")} mutagent feedback send "The setup flow could show progress better"
7096
- ${chalk18.dim("$")} mutagent feedback send "Eval gate was confusing" --category stage:evaluate --title "eval gate unclear"
7097
- ${chalk18.dim("$")} mutagent feedback send "Diagnose loop stalled" --category stage:diagnose --session sess_abc123 --json
7098
- ${chalk18.dim("$")} mutagent feedback send "CLI crashed mid-run" --category cli --attach-transcript --json
7099
- ${chalk18.dim("$")} mutagent feedback send "Repro attached" --category cli --attach-transcript /tmp/session.jsonl
7100
-
7101
- ${chalk18.bold("Category:")}
7102
- ${chalk18.bold("cli")} Feedback about the CLI itself (default)
7103
- ${chalk18.bold("helix")} Feedback about Helix
7104
- ${chalk18.bold("stage:<x>")} Feedback about a lifecycle stage: ${VALID_STAGES.join(", ")}
7105
-
7106
- ${chalk18.bold("Transcript (--attach-transcript):")}
7107
- Uploads your coding-agent SESSION JSONL as context (raw last-200KB tail) — a
7108
- SEPARATE artifact from the feedback body. Bare flag auto-detects the newest
7109
- session across claude-code, codex, and omp; explicit path overrides.
7110
-
7111
- ${chalk18.yellow("A session transcript contains source code, absolute paths (including your")}
7112
- ${chalk18.yellow("username), branch and repository names, internal hostnames, and the business")}
7113
- ${chalk18.yellow("context of your prompts.")} Credentials are scanned for and redacted before
7114
- upload, and the exact size + source path are printed before the network call.
7115
- Detection is best-effort — omit the flag if the session handled secrets.
7116
-
7117
- ${chalk18.yellow("AI Agent (MANDATORY):")}
7118
- ALWAYS use --json: mutagent feedback send "..." --json
7119
- Auto-captured context (harness, CLI version, platform, OS, node version) is included automatically.
7063
+ feedback.command("send").description("Send product feedback about the Mutagent platform or CLI").argument("<feedback>", "The feedback body (content), max 10000 characters").option("--title <string>", "Short title").option("--category <value>", `Feedback category: 'cli', 'helix', or 'stage:<${VALID_STAGES.join("|")}>'`, "cli").option("--session <id>", "Link the feedback to a session").option("--attach-transcript [path]", "Attach the coding-agent session JSONL. Bare = auto-detect newest; or pass an explicit path.").addHelpText("after", `
7064
+ ${chalk19.bold("Examples:")}
7065
+ ${chalk19.dim("$")} mutagent feedback send "The setup flow could show progress better"
7066
+ ${chalk19.dim("$")} mutagent feedback send "Eval gate was confusing" --category stage:evaluate --title "eval gate unclear"
7067
+ ${chalk19.dim("$")} mutagent feedback send "Diagnose loop stalled" --category stage:diagnose --session sess_abc123 --json
7068
+ ${chalk19.dim("$")} mutagent feedback send "CLI crashed mid-run" --category cli --attach-transcript --json
7069
+ ${chalk19.dim("$")} mutagent feedback send "Repro attached" --category cli --attach-transcript /tmp/session.jsonl
7070
+
7071
+ ${chalk19.bold("Category:")}
7072
+ ${chalk19.bold("cli")} Feedback about the CLI itself (default)
7073
+ ${chalk19.bold("helix")} Feedback about Helix
7074
+ ${chalk19.bold("stage:<x>")} Feedback about a lifecycle stage: ${VALID_STAGES.join(", ")}
7075
+
7076
+ ${chalk19.bold("Transcript (--attach-transcript):")}
7077
+ Uploads the last 200 KB of a coding-agent session transcript, separately from the
7078
+ feedback text. Without a path it picks the newest session from Claude Code, Codex
7079
+ or omp; a path picks that file.
7080
+
7081
+ ${chalk19.yellow("A session transcript contains source code, absolute paths (including your")}
7082
+ ${chalk19.yellow("username), branch and repository names, internal hostnames, and the business")}
7083
+ ${chalk19.yellow("context of your prompts.")} Credentials are scanned for and redacted before
7084
+ upload, and the exact size and source path are printed before anything is sent.
7085
+ Detection is best-effort: omit the flag if the session handled secrets.
7086
+
7087
+ AI Agent Directive:
7088
+ Use --json: mutagent feedback send "..." --json
7089
+ Do not pass --attach-transcript unless the user explicitly asks for it.
7090
+ Every report includes the CLI version, platform, OS and Node version; the coding agent's name is added when a transcript is attached.
7120
7091
  `).action(async (feedbackBody, options) => {
7121
7092
  const isJson = getJsonFlag(feedback);
7122
7093
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -7190,34 +7161,19 @@ Or set an API key: mutagent config set apiKey <key>`);
7190
7161
 
7191
7162
  // src/commands/install/index.ts
7192
7163
  import { Command as Command11 } from "commander";
7193
- import chalk19 from "chalk";
7164
+ import chalk20 from "chalk";
7194
7165
  init_errors();
7195
7166
  function createInstallCommand(deps) {
7196
- const install = new Command11("install").description("Install a Mutagent package (helix)").argument("<package>", `Package to install: ${VALID_PACKAGES.join(", ")}`).option("--harness <harness>", `Target harness: ${VALID_HARNESSES.join(", ")}`, "claude-code").option("--global", "(retired) Install scope is fixed per package").option("--version <version>", "Package version to install (default: latest)").addHelpText("after", `
7197
- ${chalk19.bold("Arguments & flags:")}
7198
- ${chalk19.bold("<package>")} ${VALID_PACKAGES.join(" | ")}
7199
- ${chalk19.bold("--harness <h>")} ${VALID_HARNESSES.join(" | ")} ${chalk19.dim("(default: claude-code)")}
7200
- ${chalk19.bold("--version <v>")} Pin a package version ${chalk19.dim("(default: latest)")}
7201
- ${chalk19.bold("--json")} Structured output
7202
-
7203
- ${chalk19.bold("Install scope")} ${chalk19.dim("(fixed per package — not selectable)")}:
7204
- ${chalk19.bold("helix")} → installed into the current directory
7205
- ${chalk19.dim("--global")} is retired: scope is no longer a choice.
7206
-
7207
- ${chalk19.bold("Examples:")}
7208
- ${chalk19.dim("$")} mutagent install helix
7209
- ${chalk19.dim("$")} mutagent install helix --harness codex
7210
- ${chalk19.dim("$")} mutagent install helix --version 1.2.3
7211
- ${chalk19.dim("$")} mutagent install helix --json
7212
-
7213
- ${chalk19.bold("Packages:")}
7214
- ${chalk19.bold("helix")} ${chalk19.green("(available)")} — the ADLC (Agent Development Life Cycle Orchestrator). Downloaded from a private
7215
- registry via a login-brokered signed URL, sha256-verified, then
7216
- initialized into your project. No static secret ships in the CLI.
7217
- ${chalk19.dim("Installs every harness itself (Claude + Codex) — --harness does not apply.")}
7218
- ${chalk19.dim("Carries the diagnostics and evaluator skills, plus the helix-cli binary.")}
7219
-
7220
- ${chalk19.yellow("Note:")} install is login-gated. Run ${chalk19.cyan("mutagent login")} first (else exits with a login directive).
7167
+ const install = new Command11("install").description("Install a Mutagent package on this machine (helix)").argument("<package>", `Package to install: ${VALID_PACKAGES.join(", ")}`).option("--harness <harness>", `Harness named in the output and the install report: ${VALID_HARNESSES.join(", ")}`, "claude-code").option("--global", "Refused: helix always installs into the current directory").option("--version <version>", "Package version to install (default: latest)").addHelpText("after", `
7168
+ ${chalk20.bold("Examples:")}
7169
+ ${chalk20.dim("$")} mutagent install helix
7170
+ ${chalk20.dim("$")} mutagent install helix --version <version>
7171
+ ${chalk20.dim("$")} mutagent install helix --json
7172
+
7173
+ ${chalk20.bold("helix")}: the Helix agent-development toolkit, set up in the current directory for
7174
+ Claude Code and Codex (includes the diagnostics and evaluator skills and helix-cli).
7175
+ Requires \`mutagent login\`. This installs Helix locally; \`mutagent helix\` runs it in the cloud.
7176
+ --harness does not change what is installed. --global exits with an error.
7221
7177
  `).action(async (pkg, options) => {
7222
7178
  const isJson = getJsonFlag(install);
7223
7179
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -7242,9 +7198,9 @@ ${chalk19.yellow("Note:")} install is login-gated. Run ${chalk19.cyan("mutagent
7242
7198
  output.success(`Installed ${result.package}@${result.version} for ${result.harness} in ${result.projectPath ?? "this project"}`);
7243
7199
  } else {
7244
7200
  output.success(`Installed ${result.package}@${result.version}, but the skill is not in place yet`);
7245
- console.log(`${chalk19.yellow("⚠")} No skill was installed yet — a setup step still has to run.`);
7201
+ console.log(`${chalk20.yellow("⚠")} No skill was installed yet — a setup step still has to run.`);
7246
7202
  if (result.nextStep) {
7247
- console.log(`${chalk19.blue("→")} Run next: ${chalk19.cyan(result.nextStep)}`);
7203
+ console.log(`${chalk20.blue("→")} Run next: ${chalk20.cyan(result.nextStep)}`);
7248
7204
  }
7249
7205
  }
7250
7206
  } catch (error) {
@@ -7260,13 +7216,12 @@ init_errors();
7260
7216
  var TRACE_RELOCATION_HINT = "trace lives in the `helix-cli` binary, which ships inside helix (install it with: mutagent install helix)";
7261
7217
  function createTraceCommand() {
7262
7218
  const trace = new Command12("trace").description("Not in this binary — trace lives in helix-cli").allowUnknownOption(true).allowExcessArguments(true).argument("[args...]", "Arguments intended for `helix-cli trace`").addHelpText("after", `
7263
- This binary (mutagent) does not provide trace.
7264
-
7265
- ${TRACE_RELOCATION_HINT}
7266
-
7267
- Install one of those, then run:
7219
+ mutagent does not run trace. trace is a command of helix-cli, which
7220
+ mutagent install helix installs. Run:
7268
7221
 
7269
7222
  helix-cli trace <args>
7223
+
7224
+ This command only prints where trace is and exits with an error.
7270
7225
  `).action((args) => {
7271
7226
  const isJson = getJsonFlag(trace);
7272
7227
  const forwarded = args.length > 0 ? ` ${args.join(" ")}` : " <args>";
@@ -9083,39 +9038,29 @@ async function runCloudLaunch(plan, mode) {
9083
9038
  var MISSING_TASK_MESSAGE = 'cloud runs are non-interactive: pass the task with -p "<task>"';
9084
9039
  function registerAgentCommand(parent, registration, deps = defaultPlanDeps()) {
9085
9040
  const jsonSource = registration?.jsonSource ?? parent;
9086
- parent.command("agent").description("Run a native Helix agent in Cloud").argument("[argv...]", "Native definition, task (-p or JSON-mode positional), and harness flags").allowUnknownOption(true).allowExcessArguments(true).option("--sandbox <id>", "Run in this existing sandbox instead of a fresh preset box").option("--preset <name>", "Preset for automatic placement (not combined with --sandbox)").option("--sandbox-provider <name>", "Cloud placement: run this launch on the named configured sandbox provider").option("--env <name>", "Workspace Environment (mutagent env ls) to load for this run").option("--rpc", "Bridge native JSONL commands on stdin and responses/events on stdout").option("--cwd <path>", "Working directory inside the Cloud session").addHelpText("after", [
9041
+ parent.command("agent").description("Run a Helix agent in the cloud").argument("[argv...]", "Agent definition, then the task and Helix flags").allowUnknownOption(true).allowExcessArguments(true).option("--sandbox <id>", "(internal) Run in an existing sandbox, by sandbox id").option("--preset <name>", "Sandbox preset (not with --sandbox)").option("--sandbox-provider <name>", "Run on this configured sandbox provider").option("--env <name>", "Workspace Environment to load for this run (see mutagent env ls)").option("--rpc", "Read JSON commands on stdin; write events on stdout").option("--cwd <path>", "Working directory in the sandbox").addHelpText("after", [
9087
9042
  "",
9088
9043
  "Examples:",
9089
9044
  ' mutagent helix agent "You are a test reviewer." -p "review the diff"',
9090
- ' mutagent helix agent --file runtime-agent.md --mode json "run the suite"',
9091
- " mutagent helix agent --file runtime-agent.md --prime --mode rpc",
9045
+ ' mutagent helix agent --file reviewer.md --mode json "run the suite"',
9046
+ " mutagent helix agent --file reviewer.md --prime --mode rpc",
9092
9047
  ' mutagent helix agent --name reviewer --env staging -p "check the deploy"',
9093
9048
  "",
9094
- "Native grammar:",
9095
- " The leading positional is the DEFINITION; later positionals are native task messages.",
9096
- " Text/JSON runs preserve every message and option in relative order.",
9097
- " --file reads a native runtime brief locally. Its body travels to the harness.",
9098
- " --name/--agent resolves local agent roots first, otherwise the image roster.",
9099
- " Deployment frontmatter, tools, skills and assets are not compiled by this command.",
9100
- " Deployment packages belong to the separate mutagent agent compiler surface.",
9101
- " Package deployment and native brief execution complement each other on the same runtime.",
9102
- " --prime preserves the agent definition and selects the native Prime scaffold.",
9049
+ "Definition and task:",
9050
+ " The first argument (or --prompt, --file, --name) defines the agent; -p gives the task.",
9051
+ " With --mode json, a later positional is the task (JSON-mode positional).",
9052
+ " --file is read on this machine and its body is sent as the definition; tools, skills and assets its frontmatter names are not installed.",
9053
+ " --name looks in your local agents first, then the ones built into Helix.",
9054
+ " Flags that point at other files on this machine are refused, with the reason.",
9055
+ " --prime keeps the definition and runs it on the lean Prime agent.",
9103
9056
  "",
9104
9057
  "Modes:",
9105
- " -p processes the complete model/tool loop; it imposes no one-turn cap.",
9106
- " --mode json emits native JSONL; --mode rpc bridges JSONL stdin/stdout.",
9107
- " The last valid --mode wins; RPC overrides -p and ignores positional messages.",
9108
- " --mode=rpc is a Cloud convenience, normalized to native --mode rpc.",
9109
- " RPC stdin EOF closes remote input; stream disconnect only detaches.",
9110
- " Default native launch output stays native even with global --json.",
9111
- " Native session disconnect detaches; box retention follows the server idle policy.",
9112
- " Ctrl-C on a native launch sends SIGINT to that execution and reports its exit status.",
9113
- "",
9114
- "Placement:",
9115
- " The server selects the configured preset/destination; --preset overrides the preset.",
9116
- " --sandbox-provider <name> places a fresh-box launch on that configured provider.",
9117
- " --env selects a workspace Environment, independently of LLM --provider/--model.",
9118
- " --sandbox selects an existing internal test box.",
9058
+ " -p runs the whole model and tool loop for this run and prints the final answer.",
9059
+ " --mode json streams events as JSON lines; --mode rpc reads JSON commands on stdin.",
9060
+ " With --mode rpc, closing stdin ends the session's input; a dropped connection only detaches.",
9061
+ " Output is Helix's own, even with --json.",
9062
+ " Ctrl-C on a native launch sends SIGINT to the session, which stops it; the exit status is reported.",
9063
+ " A sandbox idle for 15 minutes is checkpointed and stopped.",
9119
9064
  "",
9120
9065
  "AI Agent Directive:",
9121
9066
  " Preserve the definition/task distinction and select an explicit headless mode.",
@@ -9331,7 +9276,7 @@ function planLauncherArgv(argv, deps = defaultPlanDeps()) {
9331
9276
 
9332
9277
  // src/commands/helix/session-commands.ts
9333
9278
  import { Command as Command13 } from "commander";
9334
- import chalk25 from "chalk";
9279
+ import chalk26 from "chalk";
9335
9280
  init_errors();
9336
9281
 
9337
9282
  // src/lib/sandbox-checkpoints.ts
@@ -9357,7 +9302,7 @@ async function restoreCheckpoint(id, body) {
9357
9302
  }
9358
9303
 
9359
9304
  // src/lib/sandbox-session.ts
9360
- import chalk21 from "chalk";
9305
+ import chalk22 from "chalk";
9361
9306
 
9362
9307
  // src/lib/tty.ts
9363
9308
  function isPlainOutput() {
@@ -9378,7 +9323,7 @@ init_errors();
9378
9323
  init_known_secrets();
9379
9324
 
9380
9325
  // src/lib/sandbox-frames.ts
9381
- import chalk20 from "chalk";
9326
+ import chalk21 from "chalk";
9382
9327
  var SILENT2 = new Set([
9383
9328
  "message_start",
9384
9329
  "message_update",
@@ -9470,7 +9415,7 @@ class FrameFeed {
9470
9415
  const arg = started?.arg ?? identifyingArg(frame.toolName, frame.args);
9471
9416
  const failed = isFailure(frame.result);
9472
9417
  const elapsed = started === undefined ? "" : formatDuration(this.#now() - started.startedAt);
9473
- const name = this.#plain ? toolName : failed ? chalk20.red(toolName) : chalk20.cyan(toolName);
9418
+ const name = this.#plain ? toolName : failed ? chalk21.red(toolName) : chalk21.cyan(toolName);
9474
9419
  const visible = arg === "" ? toolName.padEnd(TOOL_COL) : `${toolName.padEnd(TOOL_COL)} ${arg}`;
9475
9420
  const head = arg === "" ? pad(name, toolName, TOOL_COL) : `${pad(name, toolName, TOOL_COL)} ${arg}`;
9476
9421
  const gap = " ".repeat(Math.max(2, DURATION_COL - visible.length));
@@ -9487,7 +9432,7 @@ class FrameFeed {
9487
9432
  const method = typeof frame.method === "string" ? frame.method : "request";
9488
9433
  const key = typeof frame.statusKey === "string" ? ` ${frame.statusKey}` : "";
9489
9434
  const body = `hitl ${method}${key}`;
9490
- return this.#line(this.#sym("◆", "?"), this.#plain ? body : chalk20.yellow(body));
9435
+ return this.#line(this.#sym("◆", "?"), this.#plain ? body : chalk21.yellow(body));
9491
9436
  }
9492
9437
  #response(frame) {
9493
9438
  const command = typeof frame.command === "string" ? frame.command : "command";
@@ -9496,7 +9441,7 @@ class FrameFeed {
9496
9441
  const body = `${command}${id} ${ok ? "accepted" : "rejected"}`;
9497
9442
  if (this.#plain)
9498
9443
  return this.#line(ok ? "+" : "x", body);
9499
- return this.#line(ok ? "✓" : "✗", ok ? chalk20.green(body) : chalk20.red(body));
9444
+ return this.#line(ok ? "✓" : "✗", ok ? chalk21.green(body) : chalk21.red(body));
9500
9445
  }
9501
9446
  #line(symbol, body) {
9502
9447
  return ` ${symbol} ${body}`;
@@ -9505,7 +9450,7 @@ class FrameFeed {
9505
9450
  return this.#plain ? plain : styled;
9506
9451
  }
9507
9452
  #dim(text) {
9508
- return this.#plain ? text : chalk20.dim(text);
9453
+ return this.#plain ? text : chalk21.dim(text);
9509
9454
  }
9510
9455
  #rule(label) {
9511
9456
  const dash = this.#plain ? "-" : "─";
@@ -9595,7 +9540,7 @@ function formatDuration(ms) {
9595
9540
 
9596
9541
  // src/lib/sandbox-session.ts
9597
9542
  function dim(text) {
9598
- return isPlainOutput() ? text : chalk21.dim(text);
9543
+ return isPlainOutput() ? text : chalk22.dim(text);
9599
9544
  }
9600
9545
  function reattachHint(id, lastSeq) {
9601
9546
  const since = lastSeq > 0 ? ` --since ${String(lastSeq)}` : "";
@@ -9760,66 +9705,38 @@ function parseSince(raw) {
9760
9705
  }
9761
9706
 
9762
9707
  // src/commands/helix/restore-command.ts
9763
- import chalk22 from "chalk";
9708
+ import chalk23 from "chalk";
9764
9709
  init_errors();
9765
9710
  var DRIFT_MODES = ["warn", "refuse", "skip"];
9766
9711
  function registerRestore(session, root) {
9767
- session.command("restore").description("Rebuild a sandbox from a checkpoint, with its workspace and settings").argument("<id>", "Sandbox ID to rebuild. It does NOT have to still be running").option("--snapshot <id>", "Which checkpoint. Defaults to the newest one from this sandbox").option("--on-drift <mode>", `What to do when the transcript references files this box lacks: ${DRIFT_MODES.join(" | ")} (default: warn)`).option("--partial", "Also restore the incremental transcript prefix captured after the last consistency point; may end on an unanswered tool call").addHelpText("after", `
9712
+ session.command("restore").description("(internal) Continue a session from a checkpoint in a rebuilt sandbox, by sandbox id").argument("<sandbox-id>", "Sandbox the checkpoint came from (it may be stopped)").option("--snapshot <id>", "Checkpoint to use (default: the newest complete one from this sandbox)").option("--on-drift <mode>", `If files the conversation names are missing: ${DRIFT_MODES.join(" | ")} (default: warn)`).option("--partial", "Also restore turns saved after the checkpoint (may end mid tool call)").addHelpText("after", `
9768
9713
  Examples:
9769
- ${chalk22.dim("$")} mutagent helix session restore sbx_456
9770
- ${chalk22.dim("$")} mutagent helix session restore sbx_456 --snapshot snap_1
9771
- ${chalk22.dim("$")} mutagent helix session restore sbx_456 --on-drift refuse --json
9772
- ${chalk22.dim("$")} mutagent helix session restore sbx_456 --partial
9773
-
9774
- THE BOX DOES NOT HAVE TO BE ALIVE. This is the cloud equivalent of a local
9775
- '/resume', and a reaped or torn-down sandbox is the ORDINARY input — it is what a
9776
- cloud session normally ends as. There is no --session flag any more: the old
9777
- route restored into a session you had already started, which assumed a live box,
9778
- which is exactly what a hard kill does not leave.
9779
-
9780
- WHAT HAPPENS, IN ORDER:
9781
- 1. a fresh box for the SAME sandbox id at generation+1, same secret names
9782
- 2. the seq counter is seeded from what is already stored, so your ?since=
9783
- cursor keeps working across the seam
9784
- 3. the workspace and ~/.mutagent are extracted BEFORE Helix starts
9785
- 4. a session starts in the checkpoint's cwd
9786
- 5. the conversation is written in, its sha256 checked, and only then switched
9787
- onto — a mismatch is a 422 and the agent is never pointed at it
9788
- 6. the runtime settings and pending queue are replayed
9789
- 7. a 'restored' marker goes on the stream with a real seq
9790
-
9791
- READ 'notRestored'. Restoring only the conversation is a legitimate outcome — a
9792
- deployment with no object store, or a checkpoint taken before continuous capture
9793
- existed — and it must not look identical to a full-state restore. Every leg that
9794
- did not come back names itself there.
9795
-
9796
- --on-drift, for files that are STILL missing after the workspace was extracted:
9797
- ${chalk22.bold("warn")} (default) restore, and report them in workspaceDrift.
9798
- ${chalk22.bold("refuse")} fail with 409 BEFORE switching.
9799
- ${chalk22.bold("skip")} do not probe at all; status comes back 'unchecked'.
9800
-
9801
- --partial restores the incremental transcript prefix captured AFTER the last
9802
- consistency point — the turns a mid-turn kill would otherwise lose — when it
9803
- extends the checkpoint. ACCEPTED RISK: the prefix is cut at its last complete
9804
- line and may end on a tool call whose result never landed; the harness answers
9805
- it with a synthetic error result on the next turn, and any side effect the
9806
- original call had is not undone. When no prefix exists, or it does not extend
9807
- the checkpoint, the checkpoint is restored and 'notRestored' says why; 'partial'
9808
- on the result says which happened.
9809
-
9810
- A DEFAULTED restore (no --snapshot) may pass over newer checkpoints whose
9811
- manifest never landed to reach one it can restore in full. Each one skipped is
9812
- listed in 'skippedSnapshots' with why.
9714
+ ${chalk23.dim("$")} mutagent helix session restore sbx_456
9715
+ ${chalk23.dim("$")} mutagent helix session restore sbx_456 --snapshot snap_1
9716
+ ${chalk23.dim("$")} mutagent helix session restore sbx_456 --on-drift refuse --json
9717
+
9718
+ Rebuilds the sandbox (the old one may be stopped or gone), restores the workspace
9719
+ and settings where the checkpoint has them, and starts a new session that
9720
+ continues the conversation. notRestored lists anything not carried over.
9721
+ Find the new session's reference with 'mutagent helix session ls' (STATUS restored).
9722
+
9723
+ --on-drift, for files still missing after the workspace is restored:
9724
+ warn restore, and list them in workspaceDrift
9725
+ refuse fail before switching to the restored conversation
9726
+ skip do not check
9727
+ --partial may end on a tool call whose result never arrived; Helix answers it
9728
+ with an error on the next turn, and that call's side effects are not undone.
9729
+ Without --snapshot, newer checkpoints that cannot be restored in full are passed
9730
+ over and listed in skippedSnapshots.
9813
9731
 
9814
9732
  AI Agent Directive:
9815
9733
  --json returns { success, restored, sandboxId, generation, snapshotId,
9816
9734
  sessionId, harnessSessionId, cwd, bytes, sha256, seq, settingsReplayed,
9817
9735
  queueReplayed, workspace, workspaceDrift, notRestored, partial, partialBytes,
9818
- skippedSnapshots }. 'sessionId' is a NEW session in the rebuilt box — send the
9819
- next turn to that one, not to the id you held before. 'seq' is where the
9820
- stream continues; pass it as ?since=. 'partial' is true only when the
9821
- incremental prefix was restored; 'skippedSnapshots' names newer checkpoints a
9822
- defaulted restore passed over.
9736
+ skippedSnapshots }. The restore starts a NEW session and returns no reference:
9737
+ find it with 'mutagent helix session ls --json' (status restored) and use that
9738
+ reference for attach, send, signal and checkpoint. Read notRestored before
9739
+ relying on the restored state.
9823
9740
  `).action(async (id, options) => {
9824
9741
  const isJson = getJsonFlag(root);
9825
9742
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -9847,21 +9764,21 @@ AI Agent Directive:
9847
9764
  return;
9848
9765
  }
9849
9766
  output.success(`Sandbox ${id} rebuilt at generation ${String(result.generation)} from snapshot ${result.snapshotId}`);
9850
- console.error(chalk22.dim(` New session ${result.sessionId} · stream continues at seq ${String(result.seq)}`));
9767
+ console.error(chalk23.dim(` New session ${result.sessionId} · stream continues at seq ${String(result.seq)}`));
9851
9768
  if (result.workspace !== null) {
9852
- console.error(chalk22.dim(` Workspace: ${String(result.workspace.files)} files into ${result.cwd ?? "the default cwd"}`));
9769
+ console.error(chalk23.dim(` Workspace: ${String(result.workspace.files)} files into ${result.cwd ?? "the default cwd"}`));
9853
9770
  }
9854
9771
  if (result.settingsReplayed.length > 0) {
9855
- console.error(chalk22.dim(` Replayed: ${result.settingsReplayed.join(", ")}`));
9772
+ console.error(chalk23.dim(` Replayed: ${result.settingsReplayed.join(", ")}`));
9856
9773
  }
9857
9774
  if (result.partial) {
9858
- console.error(chalk22.dim(` Partial: ${String(result.partialBytes)} bytes beyond the checkpoint`));
9775
+ console.error(chalk23.dim(` Partial: ${String(result.partialBytes)} bytes beyond the checkpoint`));
9859
9776
  }
9860
9777
  for (const skipped of result.skippedSnapshots) {
9861
- console.error(chalk22.dim(` skipped ${skipped.id}: ${skipped.reason}`));
9778
+ console.error(chalk23.dim(` skipped ${skipped.id}: ${skipped.reason}`));
9862
9779
  }
9863
9780
  for (const gap of result.notRestored)
9864
- console.error(chalk22.yellow(` ! ${gap}`));
9781
+ console.error(chalk23.yellow(` ! ${gap}`));
9865
9782
  } catch (error) {
9866
9783
  handleError(error, isJson);
9867
9784
  }
@@ -9890,54 +9807,45 @@ function planSessionStartArgs(argv, deps) {
9890
9807
  }
9891
9808
 
9892
9809
  // src/commands/helix/send.ts
9893
- import chalk23 from "chalk";
9810
+ import chalk24 from "chalk";
9894
9811
  import { randomUUID as randomUUID3 } from "crypto";
9895
9812
  init_errors();
9896
9813
  var COMMAND_TYPES = ["prompt", "steer", "follow_up", "abort"];
9897
9814
  var NEEDS_MESSAGE = new Set(["prompt", "steer", "follow_up"]);
9898
9815
  function registerSendCommand(parent, registration) {
9899
9816
  const jsonSource = registration?.jsonSource ?? parent;
9900
- parent.command("send").description("Send a command line into a running session").argument("<id>", "Live execution reference (or internal sandbox ID with --session)").argument("[message...]", "What to say to the agent").option("--session <id>", "Internal operation: session ID inside the named sandbox").option("--type <type>", `Command verb: ${COMMAND_TYPES.join(" | ")} (default: prompt)`).option("--line <json>", "Send this exact JSON line instead of building one").option("--message-id <id>", "Correlation id to use instead of a generated one").addHelpText("after", `
9817
+ parent.command("send").description("Send a prompt or command to a session").argument("<reference>", "Session reference (hs1_...); with --session, a sandbox id (internal)").argument("[message...]", "What to say to the agent").option("--session <id>", "(internal) The session id, when the first argument is a sandbox id").option("--type <type>", `${COMMAND_TYPES.join(" | ")} (default: prompt)`).option("--line <json>", "Send this exact JSON command instead of building one").option("--message-id <id>", "Use this command id instead of a generated one").addHelpText("after", `
9901
9818
  Examples:
9902
- ${chalk23.dim("$")} mutagent helix session send <reference> "also check the refresh path"
9903
- ${chalk23.dim("$")} mutagent helix session send <reference> --type steer "stop and re-read the spec"
9904
- ${chalk23.dim("$")} mutagent helix session send <reference> --type abort
9905
- ${chalk23.dim("$")} mutagent helix session send <reference> --line '{"type":"prompt","id":"p7","message":"go"}'
9906
- ${chalk23.dim("$")} mutagent helix session send <reference> "run the tests" --json
9907
-
9908
- TWO FORMS:
9909
- ${chalk23.bold("The friendly form")} — a --type and a message. This command builds the JSON:
9910
- {"type":"prompt","id":"<generated>","message":"<your message>"}. Use it for
9911
- everything the four verbs cover.
9912
- ${chalk23.bold("--line")} — one raw JSON object, passed through byte for byte. It exists for
9913
- a command shape this CLI does not know about yet. It is validated as a single
9914
- JSON object before anything is sent, but its CONTENTS are the harness's
9915
- business, not this CLI's. Do not combine it with --type or a message.
9916
-
9917
- THE VERBS:
9918
- prompt A new instruction to a settled agent.
9919
- steer Redirect the turn already running.
9920
- follow_up Continue from what the agent just said.
9921
- abort Stop the current turn. Takes no message.
9922
-
9923
- Sending is not watching. A 200 means the line was written to the session; the
9924
- agent's reply arrives on the event stream as a 'response' frame carrying the id
9925
- printed here. Attach in another terminal to see it:
9926
- ${chalk23.dim("$")} mutagent helix session attach <reference>
9927
-
9928
- A session that is no longer running is refused rather than silently accepted —
9929
- 'mutagent sandbox ls' shows what is still up.
9930
-
9931
- For internal box operations, use send <sandbox-id> --session <session-id>.
9932
- The live reference is execution-scoped, not durable conversation identity.
9819
+ ${chalk24.dim("$")} mutagent helix session send <reference> "also check the refresh path"
9820
+ ${chalk24.dim("$")} mutagent helix session send <reference> --type steer "stop and re-read the spec"
9821
+ ${chalk24.dim("$")} mutagent helix session send <reference> --type abort
9822
+ ${chalk24.dim("$")} mutagent helix session send <reference> --line '{"type":"prompt","id":"p7","message":"go"}'
9823
+ ${chalk24.dim("$")} mutagent helix session send <reference> "run the tests" --json
9824
+
9825
+ Types:
9826
+ prompt A new instruction to an agent that has finished its turn
9827
+ steer Redirect the turn in progress
9828
+ follow_up Continue from what the agent just said
9829
+ abort Stop the current turn (no message)
9830
+ --line sends one JSON object as written, for a command the four types do not
9831
+ cover. It cannot be combined with --type or a message.
9832
+
9833
+ Only rpc sessions (started with --mode rpc or --rpc) accept input, and a session
9834
+ that has ended refuses it; 'mutagent helix session ls' shows which are live.
9835
+ With a sandbox id and --session, if the sandbox is being stopped for idling, send
9836
+ waits the time the server asks and retries once. If the session's sandbox was stopped for idling,
9837
+ 'mutagent helix session ls --json' shows its sandboxId; 'mutagent helix session
9838
+ restore <sandbox-id>' (internal) continues it in a new session.
9839
+ Send does not wait for the reply. The reply arrives on the session's stream as a
9840
+ 'response' event carrying the command id (in --json output). Watch it with:
9841
+ ${chalk24.dim("$")} mutagent helix session attach <reference>
9933
9842
 
9934
9843
  AI Agent Directive:
9935
- --json returns ONE object: { success, reference, type, id, line,
9936
- accepted, _links }. 'accepted' means DELIVERED, not answered — the answer is a
9937
- 'response' frame with the same id on 'mutagent helix session attach <reference> --json'.
9938
- Attach BEFORE sending if you need the reply; this call does not wait for one.
9939
- Use the friendly form (--type + message). Reserve --line for a command shape
9940
- the four verbs cannot express.
9844
+ --json returns ONE object: { success, reference, type, id, line, accepted,
9845
+ _links }. 'accepted' means delivered, not answered: the answer is a 'response'
9846
+ event with the same id on 'mutagent helix session attach <reference>'. This
9847
+ command does not wait for it. Use --type and a message; use --line only for a
9848
+ command the four types cannot express.
9941
9849
  `).action(async (id, messageParts, options) => {
9942
9850
  const isJson = getJsonFlag(jsonSource);
9943
9851
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -9978,8 +9886,8 @@ AI Agent Directive:
9978
9886
  return;
9979
9887
  }
9980
9888
  output.success(`Sent ${built.type} to session ${sessionId}`);
9981
- console.error(chalk23.dim(` ${built.line}`));
9982
- console.error(chalk23.dim(` The reply arrives on the stream, not here: mutagent sandbox attach ${id}`));
9889
+ console.error(chalk24.dim(` ${built.line}`));
9890
+ console.error(chalk24.dim(` The reply arrives on the stream, not here: mutagent sandbox attach ${id}`));
9983
9891
  } catch (error) {
9984
9892
  handleError(error, isJson);
9985
9893
  }
@@ -10041,56 +9949,33 @@ function readType(raw) {
10041
9949
  }
10042
9950
 
10043
9951
  // src/commands/helix/signal.ts
10044
- import chalk24 from "chalk";
9952
+ import chalk25 from "chalk";
10045
9953
  init_errors();
10046
9954
  var SIGNAL_TYPES = ["SIGINT", "SIGTERM"];
10047
9955
  var DEFAULT_SIGNAL = "SIGINT";
10048
9956
  function registerSignalCommand(parent, registration) {
10049
9957
  const jsonSource = registration?.jsonSource ?? parent;
10050
- parent.command("signal").description("Stop a running session, leaving its sandbox up").argument("<id>", "Live execution reference (or internal sandbox ID with --session)").option("--session <id>", "Internal operation: session ID inside the named sandbox").option("--type <signal>", `${SIGNAL_TYPES.join(" | ")} (default: ${DEFAULT_SIGNAL})`).addHelpText("after", `
9958
+ parent.command("signal").description("Stop a session with SIGINT or SIGTERM; its sandbox stays up").argument("<reference>", "Session reference (hs1_...); with --session, a sandbox id (internal)").option("--session <id>", "(internal) The session id, when the argument is a sandbox id").option("--type <signal>", `${SIGNAL_TYPES.join(" | ")} (default: ${DEFAULT_SIGNAL})`).addHelpText("after", `
10051
9959
  Examples:
10052
- ${chalk24.dim("$")} mutagent helix session signal <reference> ${chalk24.dim("SIGINT — like Ctrl-C")}
10053
- ${chalk24.dim("$")} mutagent helix session signal <reference> --type SIGTERM ${chalk24.dim("Ask it to shut down")}
10054
- ${chalk24.dim("$")} mutagent helix session signal <reference> --json
10055
-
10056
- THE SANDBOX SURVIVES. This stops the SESSION — the agent process — and leaves
10057
- the box, its filesystem and its event log exactly where they are, so you can
10058
- attach, exec against it, or read what the agent did before you stopped it.
10059
- 'mutagent sandbox rm <id>' is still the only thing that destroys a sandbox.
10060
-
10061
- THREE WAYS TO STOP AN AGENT, WEAKEST FIRST:
10062
- ${chalk24.bold("send --type abort")} Asks the agent to end the turn. Needs a cooperating agent
10063
- that is still reading its stdin. Try this first.
10064
- ${chalk24.bold("signal")} Does not ask. Reaches a wedged or looping process.
10065
- The session ends; the box stays.
10066
- ${chalk24.bold("rm")} Destroys the sandbox and everything in it.
10067
-
10068
- THE SIGNALS:
10069
- SIGINT Interrupts the current turn, the way Ctrl-C does in a local terminal.
10070
- SIGTERM Asks the process to shut down.
10071
- SIGKILL is not accepted: it would end the session before it could flush the
10072
- output you came back to read.
10073
-
10074
- A 200 here means a LIVE process was signalled — not just that the request was
10075
- understood. A session that had already finished is reported as such rather than
10076
- being counted as a success, so this never tells you that you stopped an agent
10077
- that had in fact been done for a while.
10078
-
10079
- 'mutagent sandbox ls' shows which sandboxes are up; 'mutagent helix session attach <reference>'
10080
- shows what the session was doing.
10081
-
10082
- For internal box operations, use signal <sandbox-id> --session <session-id>.
10083
- The live reference is execution-scoped, not durable conversation identity.
9960
+ ${chalk25.dim("$")} mutagent helix session signal <reference>
9961
+ ${chalk25.dim("$")} mutagent helix session signal <reference> --type SIGTERM
9962
+ ${chalk25.dim("$")} mutagent helix session signal <reference> --json
9963
+
9964
+ SIGINT (the signal Ctrl-C sends) and SIGTERM both stop the session's Helix process.
9965
+ SIGKILL is not supported.
9966
+ To end only the current turn and keep the session, use
9967
+ 'mutagent helix session send <reference> --type abort' instead; signal works even
9968
+ when the agent has stopped reading its input.
9969
+ Exits non-zero if the session had already ended.
9970
+ The sandbox is not deleted: its files and event log stay, and
9971
+ 'mutagent helix session attach <reference>' shows what the session did. A sandbox
9972
+ idle for 15 minutes is checkpointed and stopped.
10084
9973
 
10085
9974
  AI Agent Directive:
10086
- --json returns ONE object: { success, reference, signal, signalled,
10087
- _links }. 'signalled' is an EFFECT, not an acknowledgement — it is true only
10088
- when a running process was actually stopped.
10089
- A session that has already ended exits non-zero rather than reporting success.
10090
- Treat that as information, not as a failure to retry: the agent is already
10091
- stopped, which is what you wanted.
10092
- This does NOT destroy the sandbox. It stays billable until 'mutagent sandbox rm
10093
- <id>'. Confirm with the user before that.
9975
+ --json returns ONE object: { success, reference, signal, signalled, _links }.
9976
+ 'signalled' is true only when a running process was stopped. A session that had
9977
+ already ended exits non-zero; the agent is already stopped, so treat it as
9978
+ information and do not retry.
10094
9979
  `).action(async (id, options) => {
10095
9980
  const isJson = getJsonFlag(jsonSource);
10096
9981
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -10129,8 +10014,8 @@ AI Agent Directive:
10129
10014
  return;
10130
10015
  }
10131
10016
  output.success(`Sent ${signal} to session ${sessionId}`);
10132
- console.error(chalk24.dim(` The sandbox is still up: mutagent sandbox attach ${id}`));
10133
- console.error(chalk24.dim(` Destroy it with: mutagent sandbox rm ${id}`));
10017
+ console.error(chalk25.dim(` The sandbox is still up: mutagent sandbox attach ${id}`));
10018
+ console.error(chalk25.dim(` Destroy it with: mutagent sandbox rm ${id}`));
10134
10019
  } catch (error) {
10135
10020
  handleError(error, isJson);
10136
10021
  }
@@ -10148,46 +10033,26 @@ function resolveSignal(raw) {
10148
10033
 
10149
10034
  // src/commands/helix/session-commands.ts
10150
10035
  function registerSessionCommand(parent) {
10151
- const session = new Command13("session").description("Control live Helix executions; start/ls are internal existing-box operations").addHelpText("after", `
10036
+ const session = new Command13("session").description("List, watch and control Helix sessions").addHelpText("after", `
10152
10037
  Examples:
10153
- ${chalk25.dim("$")} mutagent helix session start sbx_123 --rpc
10154
- ${chalk25.dim("$")} mutagent helix session ls sbx_123
10155
- ${chalk25.dim("$")} mutagent helix session attach <reference>
10156
- ${chalk25.dim("$")} mutagent helix session send <reference> "and now the tests"
10157
- ${chalk25.dim("$")} mutagent helix session signal <reference>
10158
- ${chalk25.dim("$")} mutagent helix session checkpoint <reference>
10159
- ${chalk25.dim("$")} mutagent helix session checkpoints sbx_123 --json
10160
- ${chalk25.dim("$")} mutagent helix session restore sbx_456
10161
-
10162
- A sandbox and a session are not the same thing:
10163
- ${chalk25.bold("sandbox spawn")} gives you a BOX — a machine with nothing running in it.
10164
- ${chalk25.bold("session start")} gives you an AGENT inside that box.
10165
- ${chalk25.bold("session attach")} WATCHES it.
10166
- ${chalk25.bold("session send")} TYPES INTO it.
10167
- ${chalk25.bold("session signal")} STOPS it, and leaves the box standing.
10168
- ${chalk25.bold("sandbox rm")} destroys the box. Only that.
10169
- A box with no session is a normal state — that is what 'sandbox exec' runs against.
10170
-
10171
- EXPLICIT TRANSCRIPT TRANSFER (NOT A DURABLE CONVERSATION ID):
10172
- ${chalk25.bold("checkpoint")} copies the HARNESS's own transcript off the box. That is what a
10173
- new box needs to CONTINUE the conversation — it is not the same as replaying
10174
- the stream, which shows what you watched and restores no agent state at all.
10175
- ${chalk25.bold("restore")} rebuilds from the checkpoint and starts a new runtime session,
10176
- verifying captured data before using it. Workspace/settings capture depends
10177
- on deployment support and the checkpoint. Read notCaptured and notRestored;
10178
- a stored transcript alone is not a full-state checkpoint or durable identity.
10179
-
10180
- Moved here from 'mutagent helix' (BREAKING):
10181
- 'helix send' and 'helix signal' are now 'helix session send' and
10182
- 'helix session signal'. There is no alias; the old spellings are gone.
10038
+ ${chalk26.dim("$")} mutagent helix session ls
10039
+ ${chalk26.dim("$")} mutagent helix session attach <reference>
10040
+ ${chalk26.dim("$")} mutagent helix session send <reference> "and now the tests"
10041
+ ${chalk26.dim("$")} mutagent helix session signal <reference>
10042
+ ${chalk26.dim("$")} mutagent helix session checkpoint <reference>
10043
+
10044
+ A session reference (hs1_...) is printed when a run starts and listed by 'session ls'.
10045
+ attach, send, signal and checkpoint take it.
10046
+ start, 'ls <sandbox-id>', checkpoints and restore are internal and take a sandbox id.
10047
+ checkpoint saves a session's conversation; restore continues it in a rebuilt sandbox.
10048
+ A sandbox idle for 15 minutes is checkpointed and stopped; restore rebuilds it.
10183
10049
 
10184
10050
  AI Agent Directive:
10185
- Launch with 'mutagent helix --mode rpc'; use its live execution reference for
10186
- attach/send/signal/checkpoint. The reference expires with its live execution.
10187
- Internal box operations remain available via start/ls and --session <id> on
10188
- send/signal/checkpoint; these require an explicit box ID and session ID.
10189
- Use --rpc to send commands into a live session; a one-shot refuses input.
10190
- Restore addresses the checkpoint's sandbox ID and starts a NEW session.
10051
+ Start work with 'mutagent helix --mode rpc' and keep the session reference it
10052
+ prints; 'mutagent helix session ls --json' lists references too. Use the
10053
+ reference for attach, send, signal and checkpoint; never guess one. Only an rpc
10054
+ session accepts send. start, checkpoints and restore are internal and take a
10055
+ sandbox id; restore starts a NEW session, found with 'session ls'.
10191
10056
  `);
10192
10057
  registerStart(session, parent);
10193
10058
  registerSessionLs(session, parent);
@@ -10210,50 +10075,25 @@ function withSessionFlags(options, own) {
10210
10075
  };
10211
10076
  }
10212
10077
  function registerStart(session, root) {
10213
- session.command("start").description("Start a Helix session inside an existing sandbox").argument("<id>", "Sandbox ID").argument("[args...]", "Arguments for the Helix binary inside the box").allowUnknownOption(true).allowExcessArguments(true).option("--rpc", "Long-lived session you can send into (default: one-shot)").option("--cwd <path>", "Working directory for the agent (default: the box's $HOME in rpc mode)").option("--env <name>", "Workspace Environment (mutagent env ls) to load into this session").addHelpText("after", `
10078
+ session.command("start").description("(internal) Start a Helix session in an existing sandbox, by sandbox id").argument("<sandbox-id>", "Sandbox to start the session in").argument("[args...]", "Helix flags (put -- before them)").allowUnknownOption(true).allowExcessArguments(true).option("--rpc", "Keep the session open for 'session send' (default: run once and end)").option("--cwd <path>", "Working directory (default: $HOME for an rpc session)").option("--env <name>", "Workspace Environment to load (see mutagent env ls)").addHelpText("after", `
10214
10079
  Examples:
10215
- ${chalk25.dim("$")} mutagent helix session start sbx_123 --rpc
10216
- ${chalk25.dim("$")} mutagent helix session start sbx_123 --rpc --cwd /workspace
10217
- ${chalk25.dim("$")} mutagent helix session start sbx_123 --rpc --env staging
10218
- ${chalk25.dim("$")} mutagent helix session start sbx_123 --rpc --json
10219
- ${chalk25.dim("$")} mutagent helix session start sbx_123 -- --model gpt-5 --thinking high
10220
-
10221
- TWO MODES, AND ONLY ONE OF THEM LISTENS:
10222
- ${chalk25.bold("--rpc")} A long-lived agent reading commands on its stdin. This is
10223
- the mode 'session send' writes into; without it there is
10224
- nothing to write to and every send is refused with a 409.
10225
- ${chalk25.dim("(default)")} One-shot. The session completes its run and ends. Watchable
10226
- with 'session attach', not steerable.
10227
-
10228
- ${chalk25.bold("--cwd")} IS NOT COSMETIC. The in-box server runs from /app, which is root-owned
10229
- while the agent runs as 'sandbox'. An rpc child started there loses its subagent
10230
- scheduler to EACCES — and then answers normally, because the failure goes to
10231
- stderr and the core loop survives. A half-crippled agent that looks healthy. So
10232
- an rpc session with no --cwd runs in $HOME, writable by construction; name a
10233
- directory only for the real case that cannot serve, a mounted work directory.
10234
-
10235
- ${chalk25.bold("--env <name>")} loads a WORKSPACE ENVIRONMENT — the variables and secrets the
10236
- agent's tools need, stored with 'mutagent env set' — into this session. It is
10237
- resolved on the server; no value passes through this machine. It is NOT a model
10238
- key: providers come from the workspace's provider configuration, and an
10239
- Environment variable named like one is refused at 'env set' for that reason.
10240
-
10241
- TRAILING ARGUMENTS are classified exactly as on 'mutagent helix agent --sandbox':
10242
- forwarded, translated or REFUSED with a reason. A flag naming a path on THIS
10243
- machine (--extension, --session <file>, --skill, …) is refused, because it would
10244
- point at nothing inside the box. A leading positional, --prompt, --file or
10245
- --name is the agent DEFINITION, resolved here first. Put ${chalk25.bold("--")} before them so
10246
- they are not read as flags for this command. A ${chalk25.bold("--")} does NOT stop the
10247
- classification: the harness has no end-of-options marker, so neither does this.
10248
-
10249
- The box must already exist — 'mutagent sandbox spawn --image <ref>' makes one,
10250
- and 'mutagent sandbox ls' shows which are running. One sandbox can host several
10251
- sessions, so starting one never replaces another.
10080
+ ${chalk26.dim("$")} mutagent helix session start sbx_123 --rpc
10081
+ ${chalk26.dim("$")} mutagent helix session start sbx_123 --rpc --env staging
10082
+ ${chalk26.dim("$")} mutagent helix session start sbx_123 --rpc --json
10083
+ ${chalk26.dim("$")} mutagent helix session start sbx_123 -- --model <provider/model> --thinking high
10084
+
10085
+ Prints the session id. Address the session as <sandbox-id> --session <session-id>
10086
+ for send, signal and checkpoint, and watch it with 'mutagent sandbox attach <sandbox-id>'.
10087
+ Only an --rpc session accepts 'session send'; a one-shot session runs and ends.
10088
+ Flags after -- go to Helix. A flag that names a file on this machine is refused,
10089
+ because the sandbox cannot read it. A leading positional, --prompt, --file or
10090
+ --name is the agent definition, resolved on this machine first.
10091
+ One sandbox can host several sessions; starting one never replaces another.
10252
10092
 
10253
10093
  AI Agent Directive:
10254
10094
  --json returns ONE object: { success, sandboxId, sessionId, status, mode,
10255
- _links }. Capture 'sessionId' for internal send, signal and checkpoint.
10256
- Restore takes the sandbox ID and returns a NEW sessionId; it needs no live session.
10095
+ _links, _directive }. Address the session as <sandbox-id> --session <sessionId>
10096
+ for send, signal and checkpoint.
10257
10097
  `).action(async (id, rawArgs, options) => {
10258
10098
  const isJson = getJsonFlag(root);
10259
10099
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -10293,9 +10133,9 @@ AI Agent Directive:
10293
10133
  return;
10294
10134
  }
10295
10135
  output.success(`Session ${sessionId} started in ${id} (${mode})`);
10296
- console.error(chalk25.dim(` Watch it: mutagent sandbox attach ${id}`));
10136
+ console.error(chalk26.dim(` Watch it: mutagent sandbox attach ${id}`));
10297
10137
  if (mode === "rpc") {
10298
- console.error(chalk25.dim(` Talk to it: mutagent helix session send ${id} --session ${sessionId} "<message>"`));
10138
+ console.error(chalk26.dim(` Talk to it: mutagent helix session send ${id} --session ${sessionId} "<message>"`));
10299
10139
  }
10300
10140
  } catch (error) {
10301
10141
  handleError(error, isJson);
@@ -10308,24 +10148,18 @@ function requireSessionId(result, sandboxId) {
10308
10148
  throw new MutagentError("INVALID_RESPONSE", "The server started a session but did not return a session id.", `The session may be running — watch it with: mutagent sandbox attach ${sandboxId}`);
10309
10149
  }
10310
10150
  function registerSessionLs(session, root) {
10311
- session.command("ls").description("List Helix sessions — no argument: the whole workspace; with a box id: that one box").argument("[id]", "Sandbox ID (omit for the workspace-wide listing)").addHelpText("after", `
10151
+ session.command("ls").description("List this workspace's Helix sessions").argument("[sandbox-id]", "(internal) List the session ids in one sandbox instead").addHelpText("after", `
10312
10152
  Examples:
10313
- ${chalk25.dim("$")} mutagent helix session ls ${chalk25.dim("Every session in the workspace, all boxes")}
10314
- ${chalk25.dim("$")} mutagent helix session ls --json
10315
- ${chalk25.dim("$")} mutagent helix session ls sbx_123 ${chalk25.dim("One box (internal): ids only")}
10316
- ${chalk25.dim("$")} mutagent helix session ls sbx_123 --json
10317
-
10318
- NO ARGUMENT — the workspace listing. Rows across ALL of your sandboxes, live
10319
- and ended, newest activity first: reference (the address attach/send/signal
10320
- take), box, arm, mode, status and last activity. Status tells you what
10321
- happened: 'live' is running now; 'ended' finished (exitCode in --json names
10322
- how); 'stopped-for-idling' died with its box, reaped by the idle policy;
10323
- 'restored' is running on a box rebuilt from a checkpoint. Sessions older than
10324
- the persisted arm/mode fields show those columns blank rather than a guess.
10325
-
10326
- WITH A BOX ID — the internal box listing, unchanged: bare session ids that one
10327
- sandbox is hosting. An empty list is a normal result: a box with no session in
10328
- it is what 'mutagent sandbox exec' runs against.
10153
+ ${chalk26.dim("$")} mutagent helix session ls
10154
+ ${chalk26.dim("$")} mutagent helix session ls --json
10155
+
10156
+ Every session in the workspace, live and ended, newest activity first.
10157
+ STATUS is live (running now), ended (finished; exitCode in --json), stopped-for-idling
10158
+ (its sandbox was idle for 15 minutes, then checkpointed and stopped) or restored
10159
+ (running in a sandbox rebuilt from a checkpoint). ARM and MODE are blank for
10160
+ sessions older than those fields.
10161
+ Pass the REFERENCE to attach, send, signal or checkpoint.
10162
+ With a sandbox id, lists the bare session ids in that one sandbox (internal).
10329
10163
 
10330
10164
  AI Agent Directive:
10331
10165
  Run 'mutagent helix session ls --json' to discover live work — { workspaceId,
@@ -10342,7 +10176,7 @@ AI Agent Directive:
10342
10176
  return;
10343
10177
  }
10344
10178
  if (sessions2.length === 0) {
10345
- console.log(chalk25.gray(`No sessions running in ${id}.`));
10179
+ console.log(chalk26.gray(`No sessions running in ${id}.`));
10346
10180
  console.log("");
10347
10181
  console.log(` Start one: mutagent helix session start ${id} --rpc`);
10348
10182
  return;
@@ -10361,7 +10195,7 @@ AI Agent Directive:
10361
10195
  return;
10362
10196
  }
10363
10197
  if (sessions.length === 0) {
10364
- console.log(chalk25.gray("No Helix sessions in this workspace yet."));
10198
+ console.log(chalk26.gray("No Helix sessions in this workspace yet."));
10365
10199
  console.log("");
10366
10200
  console.log(' Launch one: mutagent helix launch --task "..." (or session start <box>)');
10367
10201
  return;
@@ -10375,7 +10209,7 @@ AI Agent Directive:
10375
10209
  "LAST ACTIVITY": s.lastActivityAt
10376
10210
  })));
10377
10211
  if (nextCursor !== undefined) {
10378
- console.log(chalk25.gray("\nMore sessions — re-run with the cursor from --json `nextCursor`."));
10212
+ console.log(chalk26.gray("\nMore sessions — re-run with the cursor from --json `nextCursor`."));
10379
10213
  }
10380
10214
  } catch (error) {
10381
10215
  handleError(error, isJson);
@@ -10383,17 +10217,16 @@ AI Agent Directive:
10383
10217
  });
10384
10218
  }
10385
10219
  function registerSessionAttach(session) {
10386
- session.command("attach").description("Watch one live Helix execution").argument("<reference>", "Reference returned when the session started").option("--since <seq>", "Resume after this transport sequence number").addHelpText("after", [
10220
+ session.command("attach").description("Watch a session").argument("<reference>", "Session reference (hs1_...) from 'mutagent helix' or 'session ls'").option("--since <n>", "Replay from after event number n").addHelpText("after", [
10387
10221
  "",
10388
10222
  "Examples:",
10389
10223
  " mutagent helix session attach <reference>",
10390
10224
  " mutagent helix session attach <reference> --since 412",
10391
10225
  "",
10392
10226
  "AI Agent Directive:",
10393
- " Output is native harness text/JSONL. Diagnostics stay on stderr.",
10394
- " Ctrl-C detaches without signalling or destroying the session.",
10395
- " A live execution reference does not identify a conversation across restore.",
10396
- " Use mutagent sandbox attach for the internal box-wide event stream."
10227
+ " Output is Helix's own text or JSON lines on stdout; CLI messages go to stderr. --json does not change it.",
10228
+ " Ctrl-C stops watching without signalling the session.",
10229
+ " A reference names one session; after a restore, take the new reference from session ls."
10397
10230
  ].join(`
10398
10231
  `)).action(async (reference, options) => {
10399
10232
  try {
@@ -10411,28 +10244,20 @@ function registerSessionAttach(session) {
10411
10244
  });
10412
10245
  }
10413
10246
  function registerCheckpoint(session, root) {
10414
- session.command("checkpoint").description("Capture a running session's own transcript into a snapshot").argument("<id>", "Live reference (or internal sandbox ID with --session)").option("--session <id>", "Internal operation: session ID inside the named sandbox").addHelpText("after", `
10247
+ session.command("checkpoint").description("Save a session's conversation as a checkpoint").argument("<reference>", "Session reference (hs1_...); with --session, a sandbox id (internal)").option("--session <id>", "(internal) The session id, when the argument is a sandbox id").addHelpText("after", `
10415
10248
  Examples:
10416
- ${chalk25.dim("$")} mutagent helix session checkpoint <reference>
10417
- ${chalk25.dim("$")} mutagent helix session checkpoint <reference> --json
10249
+ ${chalk26.dim("$")} mutagent helix session checkpoint <reference>
10250
+ ${chalk26.dim("$")} mutagent helix session checkpoint <reference> --json
10418
10251
 
10419
- WHAT IS CAPTURED: the HARNESS's own session file, verbatim. That is what a new
10420
- box needs to CONTINUE the conversation. It is NOT the same as replaying the
10421
- stream with --since, which shows what a client observed and restores no agent
10422
- state at all.
10423
-
10424
- Workspace and settings may also be captured when supported. Read notCaptured
10425
- before treating a result as full-state capture: an existing transcript snapshot
10426
- can be reused without replacing its original manifest or capturing new state.
10427
-
10428
- A 409 means the session has not completed a turn yet: the harness writes its
10429
- file lazily, so there is genuinely nothing to capture, and storing an empty
10430
- snapshot would produce a restore that succeeds and restores nothing.
10252
+ Saves Helix's own session file, and the workspace and settings where the
10253
+ deployment supports it. notCaptured lists anything left out.
10254
+ Fails until the session has finished its first turn: before that there is no
10255
+ conversation to save.
10431
10256
 
10432
10257
  AI Agent Directive:
10433
- --json returns { success, snapshotId, bytes, sha256, harnessSessionId,
10258
+ --json returns { success, reference, snapshotId, bytes, sha256, harnessSessionId,
10434
10259
  messageCount, sourcePath, manifestKey, workspaceBytes, workspaceFiles, notCaptured }.
10435
- Capture snapshotId and inspect notCaptured before relying on a restore.
10260
+ Keep snapshotId and read notCaptured before relying on a restore.
10436
10261
  `).action(async (id, options) => {
10437
10262
  const isJson = getJsonFlag(root);
10438
10263
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -10471,22 +10296,20 @@ AI Agent Directive:
10471
10296
  if (Array.isArray(result.notCaptured))
10472
10297
  for (const reason of result.notCaptured)
10473
10298
  output.warn("Not captured: " + reason);
10474
- console.error(chalk25.dim(` ${String(result.messageCount)} messages, ${String(result.bytes)} bytes, from ${result.sourcePath}`));
10299
+ console.error(chalk26.dim(` ${String(result.messageCount)} messages, ${String(result.bytes)} bytes, from ${result.sourcePath}`));
10475
10300
  } catch (error) {
10476
10301
  handleError(error, isJson);
10477
10302
  }
10478
10303
  });
10479
10304
  }
10480
10305
  function registerCheckpoints(session, root) {
10481
- session.command("checkpoints").description("List the checkpoints captured from a sandbox").argument("<id>", "Sandbox ID").addHelpText("after", `
10306
+ session.command("checkpoints").description("(internal) List a sandbox's checkpoints, by sandbox id").argument("<sandbox-id>", "Sandbox the checkpoints were captured from").addHelpText("after", `
10482
10307
  Examples:
10483
- ${chalk25.dim("$")} mutagent helix session checkpoints sbx_123
10484
- ${chalk25.dim("$")} mutagent helix session checkpoints sbx_123 --json
10308
+ ${chalk26.dim("$")} mutagent helix session checkpoints sbx_123
10309
+ ${chalk26.dim("$")} mutagent helix session checkpoints sbx_123 --json
10485
10310
 
10486
- Newest first, and WITHOUT the stored bytes: a list route returning every
10487
- transcript in full would be the most expensive query in the system and would
10488
- spread conversations into logs and caches nobody chose. 'messageCount' is how
10489
- much conversation each one holds.
10311
+ Newest first, without the stored bytes. MESSAGES is how much conversation each holds.
10312
+ Pass an id to 'mutagent helix session restore <sandbox-id> --snapshot <id>'.
10490
10313
 
10491
10314
  AI Agent Directive:
10492
10315
  --json returns { snapshots: [...], count: N, sandboxId }. Read it to find a
@@ -10501,7 +10324,7 @@ AI Agent Directive:
10501
10324
  return;
10502
10325
  }
10503
10326
  if (snapshots.length === 0) {
10504
- console.log(chalk25.gray(`No checkpoints captured from ${id}.`));
10327
+ console.log(chalk26.gray(`No checkpoints captured from ${id}.`));
10505
10328
  console.log("");
10506
10329
  console.log(` Capture one: mutagent helix session checkpoint ${id} --session <session-id>`);
10507
10330
  return;
@@ -10526,11 +10349,11 @@ function requireSession(raw, example) {
10526
10349
  }
10527
10350
 
10528
10351
  // src/commands/helix/models.ts
10529
- import chalk26 from "chalk";
10352
+ import chalk27 from "chalk";
10530
10353
  init_errors();
10531
10354
  function printView(view, output) {
10532
10355
  if (view.models.length === 0) {
10533
- console.log(chalk26.gray("No Helix Cloud models: this workspace has no active LLM provider the box can use."));
10356
+ console.log(chalk27.gray("No Helix Cloud models: this workspace has no active LLM provider the box can use."));
10534
10357
  console.log("");
10535
10358
  console.log(" Add one: mutagent providers add --provider <id> --name <name> --api-key <key>");
10536
10359
  } else {
@@ -10540,27 +10363,26 @@ function printView(view, output) {
10540
10363
  name: m.displayName
10541
10364
  })));
10542
10365
  console.log("");
10543
- console.log(view.default === null ? chalk26.yellow("No default set — a run without --model answers 428. Set one: mutagent helix models default <provider/model>") : `Default: ${view.default}`);
10366
+ console.log(view.default === null ? chalk27.yellow("No default set — a run without --model answers 428. Set one: mutagent helix models default <provider/model>") : `Default: ${view.default}`);
10544
10367
  }
10545
10368
  if (view.unmapped.length > 0) {
10546
- console.log(chalk26.yellow(`Not usable in a box (no harness credential mapping): ${view.unmapped.join(", ")}`));
10369
+ console.log(chalk27.yellow(`Not usable in a box (no harness credential mapping): ${view.unmapped.join(", ")}`));
10547
10370
  }
10548
10371
  }
10549
10372
  function registerModelsCommand(helix) {
10550
- const models = helix.command("models").description("List this workspace's Helix Cloud models and its default").addHelpText("after", `
10373
+ const models = helix.command("models").description("List the models Helix can use in the cloud, and the workspace default model").addHelpText("after", `
10551
10374
  Examples:
10552
- ${chalk26.dim("$")} mutagent helix models
10553
- ${chalk26.dim("$")} mutagent helix models --json
10554
- ${chalk26.dim("$")} mutagent helix models default zai-coding-cn/glm-5.2
10555
- ${chalk26.dim("$")} mutagent helix models default zai-coding-cn/glm-5.2 anthropic/claude-sonnet-4-5
10375
+ ${chalk27.dim("$")} mutagent helix models
10376
+ ${chalk27.dim("$")} mutagent helix models --json
10377
+ ${chalk27.dim("$")} mutagent helix models default zai-coding-cn/glm-5.2
10378
+ ${chalk27.dim("$")} mutagent helix models default zai-coding-cn/glm-5.2 openai/gpt-5
10556
10379
 
10557
- The list is the platform catalog restricted to this workspace's active LLM
10558
- providers ('mutagent providers'). Each id is provider/model in the harness's
10559
- spelling — pass it as --model to 'helix session start' or 'helix agent'.
10380
+ Lists the models your workspace's active LLM providers ('mutagent providers') give
10381
+ Helix in the cloud. Each id is provider/model: pass it as --model to 'mutagent helix'
10382
+ or 'mutagent helix agent'.
10560
10383
 
10561
- A cloud run that names no model uses the workspace DEFAULT (★). With none set
10562
- it answers 428 before any sandbox is spawned. Local 'helix' is unaffected: it
10563
- keeps its own default.
10384
+ A cloud run that names no model uses the workspace default model (★). With none
10385
+ set, such runs fail until you set one. Your local Helix keeps its own default.
10564
10386
 
10565
10387
  AI Agent Directive:
10566
10388
  --json returns { workspaceId, models: [{ id, provider, harnessProvider, model,
@@ -10580,15 +10402,15 @@ AI Agent Directive:
10580
10402
  handleError(error, isJson);
10581
10403
  }
10582
10404
  });
10583
- models.command("default").description("Set the workspace default Helix Cloud model (then ordered fallbacks)").argument("[models...]", "provider/model ids from `mutagent helix models`, first = the default").option("--clear", "Remove every default (runs without --model then answer 428)").addHelpText("after", `
10405
+ models.command("default").description("Set the workspace default model for Helix in the cloud, and its fallbacks").argument("[models...]", "Model ids from 'mutagent helix models'; the first is the default").option("--clear", "Remove the default model and every fallback").addHelpText("after", `
10584
10406
  Examples:
10585
- ${chalk26.dim("$")} mutagent helix models default zai-coding-cn/glm-5.2
10586
- ${chalk26.dim("$")} mutagent helix models default zai-coding-cn/glm-5.2 openai/gpt-5
10587
- ${chalk26.dim("$")} mutagent helix models default --clear
10407
+ ${chalk27.dim("$")} mutagent helix models default zai-coding-cn/glm-5.2
10408
+ ${chalk27.dim("$")} mutagent helix models default zai-coding-cn/glm-5.2 openai/gpt-5
10409
+ ${chalk27.dim("$")} mutagent helix models default --clear
10588
10410
 
10589
- REPLACES the list. The first id is the default; the rest are fallbacks used in
10590
- order when an earlier one's provider has since been deactivated. Every id must
10591
- be in 'mutagent helix models' — the server rejects the rest with the list.
10411
+ Replaces the list. The first id is the default; later ids are used in order when
10412
+ an earlier model's LLM provider has been turned off. Every id must appear in
10413
+ 'mutagent helix models'; the server refuses the rest.
10592
10414
 
10593
10415
  AI Agent Directive:
10594
10416
  --json returns the same shape as 'mutagent helix models --json', after the
@@ -10616,7 +10438,7 @@ AI Agent Directive:
10616
10438
  }
10617
10439
 
10618
10440
  // src/commands/helix/doctor.ts
10619
- import chalk27 from "chalk";
10441
+ import chalk28 from "chalk";
10620
10442
  init_errors();
10621
10443
 
10622
10444
  // src/lib/sandbox-catalog.ts
@@ -10651,23 +10473,14 @@ function refreshRuntimeRefusal() {
10651
10473
  }
10652
10474
  function registerDoctorCommand(parent, registration) {
10653
10475
  const jsonSource = registration?.jsonSource ?? parent;
10654
- parent.command("doctor").description("Run the Helix binary's own diagnostics inside a sandbox").option("--sandbox <id>", "Run in this existing sandbox instead of a fresh preset box").option("--preset <name>", "Preset for the fresh sandbox (ignored with --sandbox)").option("--strict", "Pass the binary's --strict").option("--welcome", "Pass the binary's --welcome").option("--refresh-runtime", "Refused: the box's harness is baked into the image").allowUnknownOption(true).addHelpText("after", `
10476
+ parent.command("doctor").description("Check the Helix build in the cloud image").option("--sandbox <id>", "(internal) Run in an existing sandbox, by sandbox id").option("--preset <name>", "Sandbox preset (not with --sandbox)").option("--strict", "Also launch Helix's embedded harness with --version and fail if it fails").option("--welcome", "Print the Helix welcome screen instead of the report").option("--refresh-runtime", "Refused: the cloud image has no Helix runtime to refresh").allowUnknownOption(true).addHelpText("after", `
10655
10477
  Examples:
10656
- ${chalk27.dim("$")} mutagent helix doctor
10657
- ${chalk27.dim("$")} mutagent helix doctor --strict --json
10658
- ${chalk27.dim("$")} mutagent helix doctor --sandbox sbx_123
10659
-
10660
- This runs the BOX's Helix, not yours. It answers "is the image healthy", which
10661
- is a different question from 'mutagent doctor' (this CLI's own environment) and
10662
- from a local 'helix doctor' (your machine's extracted runtime).
10478
+ ${chalk28.dim("$")} mutagent helix doctor
10479
+ ${chalk28.dim("$")} mutagent helix doctor --strict --json
10663
10480
 
10664
- --refresh-runtime is REFUSED, with the reason, before any box is touched. The
10665
- box's harness is baked into the image; there is no runtime cache to refresh,
10666
- and the flag would report success against a layer it cannot write.
10667
-
10668
- Without --sandbox this spawns a fresh box from a preset, runs, and tears it
10669
- down. That costs a box for a diagnostic, so pass --sandbox when you already have
10670
- one up.
10481
+ Runs Helix's own diagnostics in a temporary sandbox that is removed afterwards,
10482
+ and exits with Helix's exit code. It checks the cloud image, not your machine:
10483
+ for your local Helix run 'helix doctor'.
10671
10484
 
10672
10485
  AI Agent Directive:
10673
10486
  --json returns { success, command, result: { sandboxId, exitCode, stdout,
@@ -10697,15 +10510,13 @@ AI Agent Directive:
10697
10510
  }
10698
10511
  function registerSmokeCommand(parent, registration) {
10699
10512
  const jsonSource = registration?.jsonSource ?? parent;
10700
- parent.command("smoke").description("Run the Helix binary's own smoke test inside a sandbox").option("--sandbox <id>", "Run in this existing sandbox instead of a fresh preset box").option("--preset <name>", "Preset for the fresh sandbox (ignored with --sandbox)").allowUnknownOption(true).addHelpText("after", `
10513
+ parent.command("smoke").description("Run Helix's smoke test in the cloud").option("--sandbox <id>", "(internal) Run in an existing sandbox, by sandbox id").option("--preset <name>", "Sandbox preset (not with --sandbox)").allowUnknownOption(true).addHelpText("after", `
10701
10514
  Examples:
10702
- ${chalk27.dim("$")} mutagent helix smoke
10703
- ${chalk27.dim("$")} mutagent helix smoke --sandbox sbx_123 --json
10515
+ ${chalk28.dim("$")} mutagent helix smoke
10516
+ ${chalk28.dim("$")} mutagent helix smoke --json
10704
10517
 
10705
- The binary's own end-to-end check, run in the box. It takes a real turn, so it
10706
- needs a box with egress — a preset box has it; a box you spawned with the
10707
- default deny policy does not, and the failure will present as an unreachable
10708
- model provider.
10518
+ Runs one real model turn in a temporary sandbox that is removed afterwards, and
10519
+ exits with Helix's exit code.
10709
10520
 
10710
10521
  AI Agent Directive:
10711
10522
  --json returns the same shape as 'mutagent helix doctor --json'. The binary's
@@ -10729,15 +10540,9 @@ AI Agent Directive:
10729
10540
  }
10730
10541
  function registerUpdateCommand2(parent, registration) {
10731
10542
  const jsonSource = registration?.jsonSource ?? parent;
10732
- parent.command("update").description("Not available in the cloud — the image pins the Helix build").allowUnknownOption(true).allowExcessArguments(true).argument("[argv...]", "The local binary's update flags, accepted so the refusal can name them").addHelpText("after", `
10733
- Locally, 'helix update' replaces the extracted runtime. A cloud sandbox has no
10734
- extracted runtime: the harness is BAKED into the image, and its version is a
10735
- property of the image rather than something a session can change.
10736
-
10737
- To move the cloud version you change which image the preset points at, which is
10738
- a backend/release operation, not a CLI one.
10739
- ${chalk27.dim("$")} mutagent sandbox presets --json ${chalk27.dim("what is available, and which is default")}
10740
- ${chalk27.dim("$")} mutagent helix --version ${chalk27.dim("this CLI, and the version the image pins")}
10543
+ parent.command("update").description("Not available in the cloud").allowUnknownOption(true).allowExcessArguments(true).argument("[argv...]", "Ignored").addHelpText("after", `
10544
+ The cloud Helix version is set by the release. Check it with 'mutagent helix version'.
10545
+ To update the Helix on this machine, run 'helix update'.
10741
10546
  `).action(async () => {
10742
10547
  const isJson = getJsonFlag(jsonSource);
10743
10548
  try {
@@ -10750,21 +10555,14 @@ a backend/release operation, not a CLI one.
10750
10555
  }
10751
10556
  function registerVersionCommand(parent, registration) {
10752
10557
  const jsonSource = registration?.jsonSource ?? parent;
10753
- parent.command("version").description("This CLI’s version, and the Helix build the cloud image pins").addHelpText("after", `
10558
+ parent.command("version").description("Show this CLI's version and the Helix version the cloud uses").addHelpText("after", `
10754
10559
  Examples:
10755
- ${chalk27.dim("$")} mutagent helix version
10756
- ${chalk27.dim("$")} mutagent helix version --json
10757
-
10758
- TWO DIFFERENT VERSIONS, AND THEY ARE NOT THE SAME THING:
10759
- cli the 'mutagent' binary you are running.
10760
- helix the build the cloud IMAGE pins, as the backend DECLARES it.
10560
+ ${chalk28.dim("$")} mutagent helix version
10561
+ ${chalk28.dim("$")} mutagent helix version --json
10761
10562
 
10762
- The helix line is DECLARED, not observed: reading it for real means spawning a
10763
- box to run one command. When the difference matters, observe it:
10764
- ${chalk27.dim("$")} mutagent sandbox exec -- ${IN_BOX_HELIX_BINARY} --version
10765
-
10766
- This says nothing about a Helix installed on THIS machine. For that, run
10767
- 'helix --version' directly.
10563
+ cli the mutagent CLI you are running
10564
+ helix the Helix version the cloud image is released with, as the server reports it
10565
+ For the Helix on this machine, run 'helix --version'.
10768
10566
 
10769
10567
  AI Agent Directive:
10770
10568
  --json returns { cli, helix, helixSource }. 'helixSource' is "presets", which
@@ -10786,7 +10584,7 @@ async function reportVersions(isJson) {
10786
10584
  return;
10787
10585
  }
10788
10586
  console.log(`cli ${cli}`);
10789
- console.log(`helix ${helix ?? "unknown"} ${chalk27.dim("(pinned by the image, per the backend’s presets)")}`);
10587
+ console.log(`helix ${helix ?? "unknown"} ${chalk28.dim("(pinned by the image, per the backend’s presets)")}`);
10790
10588
  }
10791
10589
  async function pinnedHelixVersion() {
10792
10590
  const { presets, default: fallback } = await listPresets();
@@ -10827,47 +10625,39 @@ function reportBoxRun(result, output, isJson, context) {
10827
10625
 
10828
10626
  // src/commands/helix/index.ts
10829
10627
  function createHelixCommand() {
10830
- const helix = new Command14("helix").description("Run Helix in Cloud using the workspace destination").allowUnknownOption(true).allowExcessArguments(true).argument("[argv...]", "Native harness arguments for the classic arm (or --prime)").helpCommand("help [command]", "Print usage for mutagent helix or one of its subcommands").addHelpText("after", [
10628
+ const helix = new Command14("helix").description("Run Helix in the cloud").allowUnknownOption(true).allowExcessArguments(true).argument("[argv...]", 'Helix flags and the task, e.g. -p "<task>"').helpCommand("help [command]", "Print usage for mutagent helix or one of its subcommands").addHelpText("after", [
10629
+ "",
10630
+ "Run options:",
10631
+ ' -p "<task>" Run the task to completion and print the final answer',
10632
+ ' --mode json "<task>" Stream events as JSON lines',
10633
+ " --mode rpc, --rpc Read JSON commands on stdin; write events on stdout",
10634
+ " --prime Run the lean Prime agent instead of the full orchestrator",
10635
+ " --model <provider/model> Model to use (default: the workspace default model; see mutagent helix models)",
10636
+ " --env <name> Workspace Environment to load (see mutagent env ls)",
10637
+ " --preset <name> Sandbox preset (default: the server's Helix preset)",
10638
+ " --sandbox-provider <name> Run on this configured sandbox provider",
10639
+ " --cwd <path> Working directory in the sandbox",
10640
+ " --sandbox <id> (internal) Run in an existing sandbox, by sandbox id",
10831
10641
  "",
10832
10642
  "Examples:",
10833
10643
  ' mutagent helix -p "plan the refactor"',
10834
10644
  ' mutagent helix --mode json "run the tests"',
10835
10645
  " mutagent helix --prime --mode rpc",
10836
- ' mutagent helix --env staging --preset helix -p "check the deploy"',
10837
- " mutagent helix agent --file runtime-agent.md --prime --mode rpc",
10838
- "",
10839
- "Modes:",
10840
- " -p runs the complete model/tool loop and prints the final text.",
10841
- " --mode json streams native harness JSONL events; -p is optional.",
10842
- " --mode rpc reads JSONL commands on stdin and writes native responses/events.",
10843
- " Text/JSON runs preserve every message and option in relative order.",
10844
- " The last valid --mode wins; RPC overrides -p and ignores positional messages.",
10845
- " --mode=rpc is a Cloud convenience, normalized to native --mode rpc.",
10846
- " RPC stdin EOF explicitly closes remote input; stream disconnect only detaches.",
10847
- " Ctrl-C sends SIGINT to the launched execution; its native exit status is reported.",
10848
- " Read-only session attach Ctrl-C detaches without signalling the execution.",
10849
- " --rpc also selects the Cloud RPC transport.",
10850
- " A standalone terminal chat UI is not available.",
10851
- " Native launch stdout contains harness output only, including with global --json.",
10852
- "",
10853
- "Placement:",
10854
- " The server selects the configured Helix preset and destination.",
10855
- " --preset selects a compatible preset; --env selects a workspace Environment.",
10856
- " --sandbox-provider <name> places the launch on that configured sandbox provider.",
10857
- " LLM --provider/--model select a model, not the execution destination.",
10858
- " --sandbox <id> is available for an existing internal test box.",
10859
- " mutagent sandbox and mutagent env retain their independent command groups.",
10646
+ ' mutagent helix --env staging -p "check the deploy"',
10647
+ " mutagent helix agent --file reviewer.md --prime --mode rpc",
10860
10648
  "",
10861
- "Sessions:",
10862
- " Live execution references support reconnect and control.",
10863
- " A reference is not a durable conversation identity across restore.",
10864
- " Disconnect detaches; box retention follows the server idle policy.",
10649
+ "Each run prints its session reference (hs1_...) on stderr. Use it with",
10650
+ "mutagent helix session attach, send, signal and checkpoint.",
10651
+ "Ctrl-C sends SIGINT to the session, which stops it; the exit status is reported.",
10652
+ "There is no interactive chat UI: choose -p, --mode json or --mode rpc.",
10653
+ "With --mode rpc, closing stdin ends the session's input; a dropped connection only detaches.",
10654
+ "A sandbox idle for 15 minutes is checkpointed and stopped; mutagent helix session restore rebuilds it.",
10865
10655
  "",
10866
10656
  "AI Agent Directive:",
10867
10657
  " Choose -p, --mode json or --mode rpc explicitly.",
10868
- " Classic loads full ADLC; --prime selects Prime.",
10869
- " helix agent selects a native runtime brief; it does not compile deployment packages.",
10870
- " mutagent install helix installs the tool locally; this command runs it remotely."
10658
+ " Without --prime the full Helix orchestrator runs; --prime runs the lean Prime agent.",
10659
+ " Keep the session reference (hs1_...) printed on stderr; attach, send, signal and checkpoint take it.",
10660
+ " mutagent install helix installs Helix on this machine; this command runs it in the cloud."
10871
10661
  ].join(`
10872
10662
  `)).action(async (argv, _options, command) => {
10873
10663
  try {
@@ -10897,75 +10687,37 @@ function createHelixCommand() {
10897
10687
 
10898
10688
  // src/commands/sandbox/index.ts
10899
10689
  import { Command as Command15 } from "commander";
10900
- import chalk33 from "chalk";
10690
+ import chalk34 from "chalk";
10901
10691
 
10902
10692
  // src/commands/sandbox/spawn.ts
10903
- import chalk28 from "chalk";
10693
+ import chalk29 from "chalk";
10904
10694
  init_errors();
10905
10695
  var ARCHES = ["x86_64", "arm64"];
10906
10696
  var EGRESS_POLICIES = ["deny", "allowlist", "allow"];
10907
10697
  var EGRESS_KINDS = ["port", "cidr", "domain"];
10908
10698
  function registerSpawnCommand(parent, registration) {
10909
10699
  const jsonSource = registration?.jsonSource ?? parent;
10910
- parent.command("spawn").description("Spawn a persistent sandbox from an image and attach to it").requiredOption("--image <ref>", "Image reference (repository:tag or @digest)").option("--arch <arch>", `Image architecture: ${ARCHES.join(" | ")} (default: this host's)`).option("--name <name>", "Definition name (default: derived from the image reference)").option("--provider <name>", "Provider registry key (default: the backend default)").option("--env <name>", "Environment (secrets + variables) to load at spawn").option("--egress <policy>", `Outbound network policy: ${EGRESS_POLICIES.join(" | ")} (default: deny)`).option("--egress-allow <rule>", "Allowance for --egress allowlist, as kind:value (repeatable)", collectRule, []).option("--detach", "Spawn without attaching — print the ID and return").addHelpText("after", `
10700
+ parent.command("spawn").description("Spawn a persistent sandbox from an image and attach to it").requiredOption("--image <ref>", "Image reference (repository:tag or @digest)").option("--arch <arch>", `Image architecture: ${ARCHES.join(" | ")} (default: this host's)`).option("--name <name>", "Definition name (default: derived from the image reference)").option("--provider <name>", "Sandbox provider (default: the server's; see sandbox providers)").option("--env <name>", "Workspace Environment to load (see mutagent env ls)").option("--egress <policy>", `Outbound network: ${EGRESS_POLICIES.join(" | ")} (default: deny)`).option("--egress-allow <rule>", "domain:, port: or cidr: rule for --egress allowlist (repeatable)", collectRule, []).option("--detach", "Print the sandbox ID and return").addHelpText("after", `
10911
10701
  Examples:
10912
- ${chalk28.dim("$")} mutagent sandbox spawn --image alpine:3.20
10913
- ${chalk28.dim("$")} mutagent sandbox spawn --image ghcr.io/example/helix:latest --arch x86_64
10914
- ${chalk28.dim("$")} mutagent sandbox spawn --image alpine:3.20 --detach --json
10915
- ${chalk28.dim("$")} mutagent sandbox spawn --image alpine:3.20 --provider docker --env staging
10916
- ${chalk28.dim("$")} mutagent sandbox spawn --image <helix-image> --egress allow ${chalk28.dim("Required for a Helix session")}
10917
- ${chalk28.dim("$")} mutagent sandbox spawn --image <ref> --egress allowlist --egress-allow domain:api.anthropic.com
10918
-
10919
- --image is required. A sandbox is spawned from a DEFINITION, and the definition
10920
- names its own image — there is no default the CLI could invent for you. To run
10921
- something without knowing an image reference, use 'mutagent helix agent' or
10922
- 'mutagent sandbox exec', which spawn from a named preset instead.
10923
-
10924
- --arch defaults to this host's architecture, on the assumption that you are
10925
- running an image you just built. A definition whose arch does not match its
10926
- image is rejected at preflight rather than emulated, so set it explicitly when
10927
- the image was built elsewhere.
10928
-
10929
- --env names an Environment (a stored bundle of secrets and variables). It is a
10930
- DIFFERENT axis from --provider, which selects the backend. Secrets never travel
10931
- on the command line.
10932
-
10933
- NETWORK EGRESS IS DENIED BY DEFAULT, AND THAT BREAKS HELIX SESSIONS:
10934
- A sandbox runs code we did not write, so the safe default is the default:
10935
- nothing reaches out. That is right for a plain 'exec' box, which needs no
10936
- network at all.
10937
-
10938
- It is WRONG for a Helix session, for two independent reasons — either alone
10939
- is enough to sink it:
10940
- 1. Under deny the container runs with --network none. Docker accepts -P and
10941
- publishes nothing, so the in-box server on :8080 is unreachable and
10942
- 'mutagent helix session start' fails with "Helix is not reachable".
10943
- 2. An agent turn has to reach a model provider regardless.
10944
-
10945
- So: ${chalk28.bold("--egress allow")} before 'helix session start'. Without it the failure
10946
- presents as an unreachable box, which reads like a broken image rather than a
10947
- policy you chose.
10948
-
10949
- ${chalk28.bold("--egress allowlist")} narrows that to named allowances, each given explicitly
10950
- as kind:value — 'domain:api.anthropic.com', 'port:443', 'cidr:10.0.0.0/8'. The
10951
- kind is never inferred from the value. An allowlist with no rules denies
10952
- everything, so it is refused rather than silently walling the box off; it also
10953
- needs a provider with egress control, which not every backend has.
10954
-
10955
- Without --detach this spawns and immediately attaches, so the common path is a
10956
- single command. Ctrl-C then DETACHES — the sandbox keeps running and the
10957
- re-attach command is printed. Use 'mutagent sandbox rm <id> --force' to destroy it.
10702
+ ${chalk29.dim("$")} mutagent sandbox spawn --image alpine:3.20
10703
+ ${chalk29.dim("$")} mutagent sandbox spawn --image alpine:3.20 --detach --json
10704
+ ${chalk29.dim("$")} mutagent sandbox spawn --image <helix-image> --egress allow
10705
+ ${chalk29.dim("$")} mutagent sandbox spawn --image <ref> --egress allowlist --egress-allow domain:api.anthropic.com
10706
+
10707
+ --arch defaults to this machine's architecture; set it for an image built elsewhere.
10708
+ Egress defaults to deny. On Docker the sandbox then runs with --network none, so a Helix
10709
+ session in it cannot be reached and cannot call its LLM provider: use --egress allow
10710
+ for a Helix sandbox. An allowlist needs at least one --egress-allow rule
10711
+ (domain:, port: or cidr:).
10712
+ Without --detach, spawn attaches; Ctrl-C detaches and prints the re-attach command.
10958
10713
 
10959
10714
  AI Agent Directive:
10960
- Pass --egress allow whenever the box is for a Helix session — 'mutagent helix
10961
- session start' CANNOT reach the in-box server under the default deny policy,
10962
- and the error it raises names the box, not the policy. A box you only 'exec'
10963
- in needs no egress.
10964
- Prefer --detach --json for automation: it returns one JSON object with the
10965
- sandbox id, so you can decide what to run before committing to a live stream.
10966
- Without --detach, --json output is NDJSON and the final line is the summary.
10967
- A spawned sandbox costs resources until 'mutagent sandbox rm <id>' — confirm
10968
- with the user before spawning, and tell them the ID afterwards.
10715
+ Pass --egress allow when the sandbox is for a Helix session: under the default deny
10716
+ policy the session cannot be reached, and the error names the sandbox, not the
10717
+ policy. A sandbox used only for exec needs no egress.
10718
+ Prefer --detach --json for automation: it returns one object, { success, sandbox,
10719
+ _links }. A spawned sandbox runs until it is removed or stopped by the idle policy:
10720
+ confirm with the user before spawning, and tell them the sandbox ID.
10969
10721
  `).action(async (options) => {
10970
10722
  const isJson = getJsonFlag(jsonSource);
10971
10723
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -11087,10 +10839,10 @@ async function createSandbox(definition, isJson) {
11087
10839
  const spinner = ora2(`Spawning ${definition.image} (${definition.arch})...`).start();
11088
10840
  try {
11089
10841
  const sandbox = await spawnSandbox(definition);
11090
- spinner.succeed(chalk28.green(`Sandbox ${sandbox.id} spawned`));
10842
+ spinner.succeed(chalk29.green(`Sandbox ${sandbox.id} spawned`));
11091
10843
  return sandbox;
11092
10844
  } catch (error) {
11093
- spinner.fail(chalk28.red("Spawn failed"));
10845
+ spinner.fail(chalk29.red("Spawn failed"));
11094
10846
  throw error;
11095
10847
  }
11096
10848
  }
@@ -11125,30 +10877,24 @@ function reportDetached(sandbox, id, output, isJson) {
11125
10877
  }
11126
10878
 
11127
10879
  // src/commands/sandbox/exec.ts
11128
- import chalk29 from "chalk";
10880
+ import chalk30 from "chalk";
11129
10881
  init_errors();
11130
10882
  function registerExecCommand(parent, registration) {
11131
10883
  const jsonSource = registration?.jsonSource ?? parent;
11132
- parent.command("exec").description("Run a command in a sandbox — an existing one, or a fresh one").argument("<argv...>", "Command and arguments (argv, not a shell string)").option("--sandbox <id>", "Run in this existing sandbox instead of a fresh one").option("--preset <name>", "Preset for the fresh sandbox (ignored with --sandbox)").option("--keep", "Keep the fresh sandbox afterwards (ignored with --sandbox)").option("--cwd <dir>", "Working directory inside the sandbox").option("--timeout-ms <n>", "Command timeout in milliseconds").option("--no-stream", "Wait for the full result instead of streaming it").addHelpText("after", `
10884
+ parent.command("exec").description("Run a command in a sandbox — an existing one, or a fresh one").argument("<argv...>", "Command and arguments (argv, not a shell string)").option("--sandbox <id>", "Run in this existing sandbox instead of a fresh one").option("--preset <name>", "Preset for a fresh sandbox (not with --sandbox)").option("--keep", "Keep the fresh sandbox after the command (not with --sandbox)").option("--cwd <dir>", "Working directory inside the sandbox").option("--timeout-ms <n>", "Command timeout in milliseconds").option("--no-stream", "With --sandbox, wait for the full result instead of streaming").addHelpText("after", `
11133
10885
  Examples:
11134
- ${chalk29.dim("$")} mutagent sandbox exec -- ls -la /workspace
11135
- ${chalk29.dim("$")} mutagent sandbox exec --sandbox sbx_123 -- bun test
11136
- ${chalk29.dim("$")} mutagent sandbox exec --sandbox sbx_123 --cwd /src -- git status
11137
- ${chalk29.dim("$")} mutagent sandbox exec --preset default -- uname -m --json
10886
+ ${chalk30.dim("$")} mutagent sandbox exec -- uname -m
10887
+ ${chalk30.dim("$")} mutagent sandbox exec --json --preset <name> -- uname -m
10888
+ ${chalk30.dim("$")} mutagent sandbox exec --sandbox sbx_123 --cwd /src -- git status
10889
+ ${chalk30.dim("$")} mutagent sandbox exec -- sh -c 'ls | wc -l'
11138
10890
 
11139
- Put ${chalk29.bold("--")} before the command. Everything after it is argv and is passed through
11140
- untouched, so flags meant for your command are not read as flags for this one.
10891
+ Put this command's flags, and --json, before the sandbox command; everything after it
10892
+ goes to the sandbox unchanged (${chalk30.bold("--")} is optional). The command is argv, not a shell
10893
+ string: name a shell for pipes.
11141
10894
 
11142
- ARGV, NOT A SHELL STRING. There is no shell in the sandbox unless you name one:
11143
- ${chalk29.dim("$")} mutagent sandbox exec -- sh -c 'ls | wc -l' ${chalk29.dim("a shell, explicitly")}
11144
- ${chalk29.dim("$")} mutagent sandbox exec -- 'ls | wc -l' ${chalk29.dim("looks for a file called 'ls | wc -l'")}
11145
-
11146
- WITH --sandbox runs in that box and streams as output is produced.
11147
- WITHOUT --sandbox spawns a fresh box from a preset, runs, tears it down. That
11148
- path is blocking, so --no-stream is redundant there and
11149
- --preset / --keep apply only there.
11150
-
11151
- The command's exit code becomes this command's exit code.
10895
+ Without --sandbox, a fresh sandbox starts from a preset (see sandbox presets), runs the
10896
+ command and is removed unless --keep. With --sandbox, output streams as it is produced;
10897
+ --preset and --keep are refused there. The sandbox command's exit code is this command's.
11152
10898
 
11153
10899
  AI Agent Directive:
11154
10900
  Use --no-stream --json (or the one-shot form, which is always blocking) for a
@@ -11273,35 +11019,30 @@ function reportResult(result, output, isJson, context) {
11273
11019
  }
11274
11020
 
11275
11021
  // src/commands/sandbox/attach.ts
11276
- import chalk30 from "chalk";
11022
+ import chalk31 from "chalk";
11277
11023
  init_errors();
11278
11024
  function registerAttachCommand(parent, registration) {
11279
11025
  const jsonSource = registration?.jsonSource ?? parent;
11280
- parent.command("attach").description("Attach to a running sandbox and stream its output").argument("<id>", "Sandbox ID").option("--since <seq>", "Resume after this sequence number instead of from the start").addHelpText("after", `
11026
+ parent.command("attach").description("Stream a running sandbox's output").argument("<id>", "Sandbox ID").option("--since <seq>", "Resume after this sequence number instead of from the start").addHelpText("after", `
11281
11027
  Examples:
11282
- ${chalk30.dim("$")} mutagent sandbox attach sbx_123
11283
- ${chalk30.dim("$")} mutagent sandbox attach sbx_123 --since 412
11284
- ${chalk30.dim("$")} mutagent sandbox attach sbx_123 --json | tail -1
11285
- ${chalk30.dim("$")} mutagent sandbox attach sbx_123 > session.log
11028
+ ${chalk31.dim("$")} mutagent sandbox attach sbx_123
11029
+ ${chalk31.dim("$")} mutagent sandbox attach sbx_123 --since 412
11030
+ ${chalk31.dim("$")} mutagent sandbox attach sbx_123 --json | tail -1
11031
+ ${chalk31.dim("$")} mutagent sandbox attach sbx_123 > session.log
11286
11032
 
11287
- Ctrl-C detaches. The sandbox keeps running and the exact re-attach command,
11288
- with the resume point filled in, is printed to stderr. Only 'mutagent sandbox rm <id> --force'
11289
- destroys a sandbox.
11033
+ Ctrl-C detaches; the sandbox keeps running and the re-attach command, with the resume
11034
+ point filled in, is printed to stderr. The sandbox runs until it is removed or stopped
11035
+ by the idle policy (see \`mutagent sandbox --help\`).
11290
11036
 
11291
- Sandbox stdout goes to stdout and sandbox stderr goes to stderr, so redirects
11292
- and pipes behave as they would for a local process. Everything this CLI says
11293
- about the session goes to stderr.
11037
+ Sandbox stdout goes to stdout and sandbox stderr to stderr; CLI messages go to stderr.
11294
11038
 
11295
- If the connection drops, attach reconnects from the last sequence number seen.
11296
- If the server's buffer has rolled past that point, the missing range is stated
11297
- plainly rather than being papered over.
11039
+ A dropped connection resumes from the last sequence number; output the server no
11040
+ longer holds is reported as a gap.
11298
11041
 
11299
11042
  AI Agent Directive:
11300
- Use --json. Output is NDJSON — one object per line, {"type":"stdout"|"stderr"|
11301
- "notice", ...} — because a live stream cannot be one JSON document until it is
11302
- over. The final line is a complete summary object; read it with 'tail -1' if
11303
- you only need the result.
11304
- Run 'mutagent sandbox ls --json' first to confirm the sandbox is running.
11043
+ Run 'mutagent sandbox ls --json' first to confirm the sandbox is running. With
11044
+ --json the output is one JSON object per line; the last line is the summary, so
11045
+ read it with 'tail -1' when you only need the result.
11305
11046
  `).action(async (id, options) => {
11306
11047
  const isJson = getJsonFlag(jsonSource);
11307
11048
  try {
@@ -11332,27 +11073,24 @@ AI Agent Directive:
11332
11073
  }
11333
11074
 
11334
11075
  // src/commands/sandbox/providers.ts
11335
- import chalk31 from "chalk";
11076
+ import chalk32 from "chalk";
11336
11077
  init_errors();
11337
11078
  function registerProvidersCommand(parent, registration) {
11338
11079
  const jsonSource = registration?.jsonSource ?? parent;
11339
- parent.command("providers").description("List the sandbox providers this backend can reach").addHelpText("after", `
11080
+ parent.command("providers").description("List sandbox providers for mutagent helix --sandbox-provider").addHelpText("after", `
11340
11081
  Examples:
11341
- ${chalk31.dim("$")} mutagent sandbox providers
11342
- ${chalk31.dim("$")} mutagent sandbox providers --json
11343
-
11344
- A PROVIDER is where a sandbox runs (docker, agentcore, …). It is a different
11345
- axis from --env, which names a stored bundle of secrets and variables, and from
11346
- --preset, which names a whole definition. Omitting --provider takes the default
11347
- reported here.
11082
+ ${chalk32.dim("$")} mutagent sandbox providers
11083
+ ${chalk32.dim("$")} mutagent sandbox providers --json
11348
11084
 
11349
- Providers are a property of the BACKEND, not of your workspace: this list is the
11350
- same for everyone on this deployment.
11085
+ A sandbox provider is where a sandbox runs (for example docker or agentcore). It is not an
11086
+ LLM provider (\`mutagent providers\`) and not an Environment (\`mutagent env\`).
11087
+ \`mutagent helix --sandbox-provider <name>\` accepts a name here when a Helix preset runs
11088
+ on it. The list is the same for every workspace on this server.
11351
11089
 
11352
11090
  AI Agent Directive:
11353
11091
  --json returns { providers: [...], default: <name|null>, count: N }. Read it
11354
- before passing --provider to 'spawn' or 'preflight'; an unknown provider is
11355
- rejected by the server.
11092
+ before passing --provider to 'spawn' or 'preflight', or --sandbox-provider to
11093
+ 'mutagent helix'; the server refuses an unknown sandbox provider.
11356
11094
  `).action(async () => {
11357
11095
  const isJson = getJsonFlag(jsonSource);
11358
11096
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -11363,7 +11101,7 @@ AI Agent Directive:
11363
11101
  return;
11364
11102
  }
11365
11103
  if (list.names.length === 0) {
11366
- console.log(chalk31.gray("No sandbox providers configured on this backend."));
11104
+ console.log(chalk32.gray("No sandbox providers configured on this backend."));
11367
11105
  return;
11368
11106
  }
11369
11107
  output.output(list.names.map((name) => ({ name, default: name === list.default ? "yes" : "" })));
@@ -11374,28 +11112,19 @@ AI Agent Directive:
11374
11112
  }
11375
11113
  function registerPreflightCommand(parent, registration) {
11376
11114
  const jsonSource = registration?.jsonSource ?? parent;
11377
- parent.command("preflight").description("Check a definition against a provider without creating anything").requiredOption("--image <ref>", "Image reference (repository:tag or @digest)").option("--arch <arch>", "Image architecture: x86_64 | arm64 (default: this host's)").option("--name <name>", "Definition name (default: derived from the image reference)").option("--provider <name>", "Provider to check against (default: the backend default)").option("--env <name>", "Environment (secrets + variables) the definition would load").option("--egress <policy>", "Outbound network policy: deny | allowlist | allow").option("--egress-allow <rule>", "Allowance for --egress allowlist, as kind:value (repeatable)", (value, previous) => [...previous, value], []).addHelpText("after", `
11115
+ parent.command("preflight").description("Check whether a sandbox provider can run a definition, without creating anything").requiredOption("--image <ref>", "Image reference (repository:tag or @digest)").option("--arch <arch>", "Image architecture: x86_64 | arm64 (default: this host's)").option("--name <name>", "Definition name (default: derived from the image reference)").option("--provider <name>", "Sandbox provider to check against (default: the server's)").option("--env <name>", "Workspace Environment the definition would load").option("--egress <policy>", "Outbound network: deny | allowlist | allow (default: deny)").option("--egress-allow <rule>", "domain:, port: or cidr: rule for --egress allowlist (repeatable)", (value, previous) => [...previous, value], []).addHelpText("after", `
11378
11116
  Examples:
11379
- ${chalk31.dim("$")} mutagent sandbox preflight --image alpine:3.20
11380
- ${chalk31.dim("$")} mutagent sandbox preflight --image alpine:3.20 --provider agentcore --json
11381
- ${chalk31.dim("$")} mutagent sandbox preflight --image <ref> --egress allowlist --egress-allow domain:api.anthropic.com
11117
+ ${chalk32.dim("$")} mutagent sandbox preflight --image alpine:3.20
11118
+ ${chalk32.dim("$")} mutagent sandbox preflight --image alpine:3.20 --provider agentcore --json
11119
+ ${chalk32.dim("$")} mutagent sandbox preflight --image <ref> --egress allowlist --egress-allow domain:api.anthropic.com
11382
11120
 
11383
- FREE, AND CREATES NOTHING. This is the question 'spawn' answers by spawning:
11384
- whether the provider you are aiming at can actually run this definition. It
11385
- takes the same flags as 'mutagent sandbox spawn' and builds the same definition,
11386
- so a verdict here is about the object spawn would really send — not a near-miss.
11387
-
11388
- EVERY violation comes back, not just the first, so one round trip is enough to
11389
- fix a definition rather than one per problem.
11390
-
11391
- INCOMPATIBLE IS NOT AN ERROR. A definition the provider refuses is a successful
11392
- answer to the question you asked; the command exits 0 and reports
11393
- compatible: false. A non-zero exit here means the CHECK failed — a bad flag, an
11394
- unknown provider, a network problem.
11121
+ Takes the same flags as spawn and checks the definition spawn would send. Every
11122
+ violation is listed. An incompatible definition exits 0 with compatible: false;
11123
+ a non-zero exit means the check itself failed.
11395
11124
 
11396
11125
  AI Agent Directive:
11397
- --json returns { success, provider, compatible, violations: [...] }. Read
11398
- 'compatible', not the exit code — an incompatible definition still exits 0.
11126
+ --json returns { success, provider, compatible, violations, definition }. Read
11127
+ 'compatible', not the exit code: an incompatible definition still exits 0.
11399
11128
  `).action(async (options) => {
11400
11129
  const isJson = getJsonFlag(jsonSource);
11401
11130
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -11416,7 +11145,7 @@ AI Agent Directive:
11416
11145
  output.success(`${definition.name} is compatible with ${verdict.provider}`);
11417
11146
  return;
11418
11147
  }
11419
- console.log(chalk31.yellow(`${definition.name} is NOT compatible with ${verdict.provider}:`));
11148
+ console.log(chalk32.yellow(`${definition.name} is NOT compatible with ${verdict.provider}:`));
11420
11149
  for (const violation of verdict.violations)
11421
11150
  console.log(` - ${violation}`);
11422
11151
  } catch (error) {
@@ -11426,20 +11155,18 @@ AI Agent Directive:
11426
11155
  }
11427
11156
 
11428
11157
  // src/commands/sandbox/inventory.ts
11429
- import chalk32 from "chalk";
11158
+ import chalk33 from "chalk";
11430
11159
  init_errors();
11431
11160
  function registerLsCommand(parent, registration) {
11432
11161
  const jsonSource = registration?.jsonSource ?? parent;
11433
- parent.command("ls").description("List your sandboxes").addHelpText("after", `
11162
+ parent.command("ls").description("List the workspace's live sandboxes").addHelpText("after", `
11434
11163
  Examples:
11435
- ${chalk32.dim("$")} mutagent sandbox ls
11436
- ${chalk32.dim("$")} mutagent sandbox ls --json
11437
-
11438
- Listing is always workspace-scoped — these are the sandboxes in the workspace
11439
- you are configured for, not every sandbox on the account. Change it with:
11440
- ${chalk32.dim("$")} mutagent config set workspace <workspace-id>
11164
+ ${chalk33.dim("$")} mutagent sandbox ls
11165
+ ${chalk33.dim("$")} mutagent sandbox ls --json
11441
11166
 
11442
- An empty list is a normal result, not an error: it means no sandbox is running.
11167
+ Lists the sandboxes in the configured workspace
11168
+ (mutagent config set workspace <workspace-id>).
11169
+ An empty list means none are running.
11443
11170
 
11444
11171
  AI Agent Directive:
11445
11172
  Run this before attach, exec or rm to confirm the sandbox exists and is
@@ -11457,7 +11184,7 @@ AI Agent Directive:
11457
11184
  return;
11458
11185
  }
11459
11186
  if (sandboxes.length === 0) {
11460
- console.log(chalk32.gray("No sandboxes running."));
11187
+ console.log(chalk33.gray("No sandboxes running."));
11461
11188
  console.log("");
11462
11189
  console.log(" Start one: mutagent sandbox spawn --image <ref>");
11463
11190
  return;
@@ -11480,15 +11207,14 @@ function toRow(sandbox) {
11480
11207
  }
11481
11208
  function registerPresetsCommand(parent, registration) {
11482
11209
  const jsonSource = registration?.jsonSource ?? parent;
11483
- parent.command("presets").description("List the presets a one-shot run can name").addHelpText("after", `
11210
+ parent.command("presets").description("List presets for mutagent helix --preset").addHelpText("after", `
11484
11211
  Examples:
11485
- ${chalk32.dim("$")} mutagent sandbox presets
11486
- ${chalk32.dim("$")} mutagent sandbox presets --json
11212
+ ${chalk33.dim("$")} mutagent sandbox presets
11213
+ ${chalk33.dim("$")} mutagent sandbox presets --json
11487
11214
 
11488
- A preset is a named definition on the backend. It is what lets 'mutagent helix agent' and
11489
- 'mutagent sandbox exec' start something without you knowing an image reference, a registry
11490
- or a region. Image references are deliberately not returned — if you need
11491
- image-level detail you are looking for 'helix spawn --image'.
11215
+ A preset is a named sandbox definition kept on the server. \`mutagent helix --preset <name>\`
11216
+ and \`mutagent sandbox exec --preset <name>\` use it. Image references are not shown.
11217
+ An unknown preset name is refused.
11492
11218
 
11493
11219
  AI Agent Directive:
11494
11220
  --json returns { presets: [...], default: <name|null>, count: N }. Read this
@@ -11504,7 +11230,7 @@ AI Agent Directive:
11504
11230
  return;
11505
11231
  }
11506
11232
  if (presets.length === 0) {
11507
- console.log(chalk32.gray("No presets configured on this backend."));
11233
+ console.log(chalk33.gray("No presets configured on this backend."));
11508
11234
  console.log("");
11509
11235
  console.log(" Spawn with an explicit image: mutagent sandbox spawn --image <ref>");
11510
11236
  return;
@@ -11523,21 +11249,19 @@ AI Agent Directive:
11523
11249
  }
11524
11250
  function registerRmCommand(parent, registration) {
11525
11251
  const jsonSource = registration?.jsonSource ?? parent;
11526
- parent.command("rm").description("Destroy a sandbox").argument("<id>", "Sandbox ID").option("-f, --force", "Skip confirmation").addHelpText("after", `
11252
+ parent.command("rm").description("Destroy a sandbox now").argument("<id>", "Sandbox ID").option("-f, --force", "Confirm (required unless --json)").addHelpText("after", `
11527
11253
  Examples:
11528
- ${chalk32.dim("$")} mutagent sandbox rm sbx_123 --force
11529
- ${chalk32.dim("$")} mutagent sandbox rm sbx_123 --json
11530
-
11531
- This is the ONLY command that destroys a sandbox — detaching, Ctrl-C and a
11532
- closed terminal all leave it running. Removing a sandbox that is already gone
11533
- succeeds, so a retried teardown is safe.
11254
+ ${chalk33.dim("$")} mutagent sandbox rm sbx_123 --force
11255
+ ${chalk33.dim("$")} mutagent sandbox rm sbx_123 --json
11534
11256
 
11535
- ${chalk32.dim("Note: --force is required. The CLI is non-interactive — confirm with the user via your native flow, then pass --force. --json auto-confirms.")}
11536
- ${chalk32.dim("Warning: any session running in the sandbox ends immediately and cannot be recovered.")}
11257
+ Ends every session in the sandbox. The server first tries to capture a checkpoint,
11258
+ so a Helix session can be rebuilt with \`mutagent helix session restore <id>\`.
11259
+ Removing a sandbox that is already gone succeeds.
11260
+ Without --force (and without --json) the command refuses; it never prompts.
11537
11261
 
11538
11262
  AI Agent Directive:
11539
- Destroying a sandbox ends any session running in it and cannot be undone.
11540
- Confirm with the user before running it, then pass --force (or --json).
11263
+ Removing a sandbox ends every session in it now. Confirm with the user before
11264
+ running it, then pass --force (or --json).
11541
11265
  `).action(async (id, options) => {
11542
11266
  const isJson = getJsonFlag(jsonSource);
11543
11267
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -11567,17 +11291,13 @@ Use --force to confirm: mutagent sandbox rm ${id} --force`);
11567
11291
  }
11568
11292
  function registerTracesCommand(parent, registration) {
11569
11293
  const jsonSource = registration?.jsonSource ?? parent;
11570
- parent.command("traces").description("Show the spans captured for a sandbox").argument("<id>", "Sandbox ID").addHelpText("after", `
11294
+ parent.command("traces").description("Show the spans recorded for a sandbox").argument("<id>", "Sandbox ID").addHelpText("after", `
11571
11295
  Examples:
11572
- ${chalk32.dim("$")} mutagent sandbox traces sbx_123
11573
- ${chalk32.dim("$")} mutagent sandbox traces sbx_123 --json
11574
-
11575
- Spans are what the session actually did — the record to read when the output
11576
- alone does not explain the result. They are called spans, not traces, because
11577
- that is what the server returns; the command keeps the familiar name.
11296
+ ${chalk33.dim("$")} mutagent sandbox traces sbx_123
11297
+ ${chalk33.dim("$")} mutagent sandbox traces sbx_123 --json
11578
11298
 
11579
- An empty result means no spans were captured. That is not the same as the box
11580
- having done nothing: telemetry ingest has to be configured for spans to exist.
11299
+ An empty list means no spans were recorded; telemetry must be configured on the
11300
+ server for spans to exist.
11581
11301
 
11582
11302
  AI Agent Directive:
11583
11303
  --json returns { spans: [...], count: N, sandboxId }. Read spans before
@@ -11594,7 +11314,7 @@ AI Agent Directive:
11594
11314
  return;
11595
11315
  }
11596
11316
  if (spans.length === 0) {
11597
- console.log(chalk32.gray(`No spans captured for sandbox ${id}.`));
11317
+ console.log(chalk33.gray(`No spans captured for sandbox ${id}.`));
11598
11318
  return;
11599
11319
  }
11600
11320
  output.output(spans.map((span) => {
@@ -11613,17 +11333,14 @@ AI Agent Directive:
11613
11333
  }
11614
11334
  function registerStatusCommand(parent, registration) {
11615
11335
  const jsonSource = registration?.jsonSource ?? parent;
11616
- parent.command("status").description("Report one sandbox’s lifecycle state").argument("<id>", "Sandbox ID").addHelpText("after", `
11336
+ parent.command("status").description("Print one sandbox's state").argument("<id>", "Sandbox ID").addHelpText("after", `
11617
11337
  Examples:
11618
- ${chalk32.dim("$")} mutagent sandbox status sbx_123
11619
- ${chalk32.dim("$")} mutagent sandbox status sbx_123 --json
11620
-
11621
- ONE FIELD, DELIBERATELY. 'GET /api/sandbox/:id' answers with the lifecycle
11622
- state and nothing else — provider, arch, definition and creation time come from
11623
- the LIST route. Use 'mutagent sandbox ls --json' when you want the record.
11338
+ ${chalk33.dim("$")} mutagent sandbox status sbx_123
11339
+ ${chalk33.dim("$")} mutagent sandbox status sbx_123 --json
11624
11340
 
11625
11341
  States: pending, starting, running, paused, stopping, stopped, failed.
11626
- A sandbox this workspace does not own is a 404, not an empty status.
11342
+ For sandbox provider, arch and creation time use \`mutagent sandbox ls --json\`.
11343
+ A sandbox outside this workspace is reported as not found.
11627
11344
 
11628
11345
  AI Agent Directive:
11629
11346
  --json returns { success, sandboxId, status, _links }. Prefer this over
@@ -11646,58 +11363,41 @@ AI Agent Directive:
11646
11363
 
11647
11364
  // src/commands/sandbox/index.ts
11648
11365
  function createSandboxCommand() {
11649
- const sandbox = new Command15("sandbox").description("Create, inspect and run commands in cloud sandboxes").addHelpText("after", `
11366
+ const sandbox = new Command15("sandbox").description("Internal: sandbox management. Everyday use goes through `mutagent helix`.").addHelpText("after", `
11650
11367
  Examples:
11651
- ${chalk33.dim("$")} mutagent sandbox presets ${chalk33.dim("What a one-shot can run on")}
11652
- ${chalk33.dim("$")} mutagent sandbox providers ${chalk33.dim("Where a box can run")}
11653
- ${chalk33.dim("$")} mutagent sandbox preflight --image alpine:3.20 ${chalk33.dim("Would this definition run?")}
11654
- ${chalk33.dim("$")} mutagent sandbox spawn --image alpine:3.20 ${chalk33.dim("Persistent box, then attach")}
11655
- ${chalk33.dim("$")} mutagent sandbox ls ${chalk33.dim("What is running")}
11656
- ${chalk33.dim("$")} mutagent sandbox status sbx_1 ${chalk33.dim("One box, one field")}
11657
- ${chalk33.dim("$")} mutagent sandbox exec --sandbox sbx_1 -- bun test
11658
- ${chalk33.dim("$")} mutagent sandbox attach sbx_1 --since 412 ${chalk33.dim("Resume after a sequence number")}
11659
- ${chalk33.dim("$")} mutagent sandbox traces sbx_1 --json ${chalk33.dim("What the box actually did")}
11660
- ${chalk33.dim("$")} mutagent sandbox rm sbx_1 --force ${chalk33.dim("Destroy it")}
11661
-
11662
- BOXES ARE NOT AGENTS. This group is the machine. To put an agent in one:
11663
- ${chalk33.dim("$")} mutagent helix agent --prompt "<who>" -p "<task>" ${chalk33.dim("One turn, fresh box")}
11664
- ${chalk33.dim("$")} mutagent helix session start sbx_1 --rpc ${chalk33.dim("An agent inside a box you own")}
11368
+ ${chalk34.dim("$")} mutagent sandbox presets ${chalk34.dim("Presets `mutagent helix --preset` can name")}
11369
+ ${chalk34.dim("$")} mutagent sandbox providers ${chalk34.dim("Sandbox providers `mutagent helix --sandbox-provider` can name")}
11370
+ ${chalk34.dim("$")} mutagent sandbox preflight --image alpine:3.20 ${chalk34.dim("Would this definition run?")}
11371
+ ${chalk34.dim("$")} mutagent sandbox spawn --image alpine:3.20 --detach
11372
+ ${chalk34.dim("$")} mutagent sandbox ls
11373
+ ${chalk34.dim("$")} mutagent sandbox status sbx_1
11374
+ ${chalk34.dim("$")} mutagent sandbox exec --sandbox sbx_1 -- bun test
11375
+ ${chalk34.dim("$")} mutagent sandbox attach sbx_1 --since 412
11376
+ ${chalk34.dim("$")} mutagent sandbox traces sbx_1
11377
+ ${chalk34.dim("$")} mutagent sandbox rm sbx_1 --force
11665
11378
 
11666
11379
  One-shot vs persistent:
11667
- ${chalk33.bold("exec")} without --sandbox starts from a named PRESET, does one thing and
11668
- tears the sandbox down. Nothing to clean up, no image to know.
11669
- ${chalk33.bold("spawn")} takes an explicit --image and leaves a box running for you to
11670
- ${chalk33.bold("attach")} and ${chalk33.bold("exec --sandbox")} against until you ${chalk33.bold("rm")} it.
11380
+ ${chalk34.bold("exec")} without --sandbox starts a fresh sandbox from a preset, runs one command
11381
+ and removes the sandbox. ${chalk34.bold("spawn")} starts a sandbox from an image and leaves it running
11382
+ for ${chalk34.bold("attach")} and ${chalk34.bold("exec --sandbox")}.
11671
11383
 
11672
11384
  Lifecycle:
11673
- A spawned sandbox outlives your terminal. Ctrl-C, a dropped connection and a
11674
- closed terminal all DETACH — whatever is running keeps running and can be
11675
- re-attached. Only 'mutagent sandbox rm <id> --force' destroys a sandbox, so
11676
- remember to run it when you are finished.
11677
-
11678
- Access:
11679
- Sandbox access is minted per WORKSPACE, so a workspace must be configured:
11680
- ${chalk33.dim("$")} mutagent config set workspace <workspace-id>
11385
+ Ctrl-C, a dropped connection or a closed terminal detaches; the sandbox keeps running.
11386
+ After 15 minutes with no input, attach, exec or agent turn, the server checkpoints
11387
+ and stops it. A Helix session in it can be rebuilt with
11388
+ \`mutagent helix session restore <id>\`. \`sandbox rm\` destroys a sandbox now.
11681
11389
 
11682
11390
  Output:
11683
- Sandbox stdout goes to stdout, sandbox stderr goes to stderr, and anything
11684
- this CLI says about the box goes to stderr. Redirects and pipes therefore
11685
- behave as they would for a locally running process. Spinners and colour are
11686
- suppressed when the output is not a terminal or NO_COLOR is set.
11687
-
11688
- Moved here from 'mutagent helix' (BREAKING):
11689
- spawn, ls, rm, presets, exec, traces and attach used to live under
11690
- 'mutagent helix'. Those spellings are GONE — there is no alias. Rewrite
11691
- 'mutagent helix <verb>' as 'mutagent sandbox <verb>'.
11391
+ Sandbox stdout goes to stdout, sandbox stderr to stderr; CLI messages go to stderr.
11392
+ With --json, attach, spawn without --detach and exec --sandbox without --no-stream
11393
+ print one JSON object per line, ending with a summary. Other commands print one object.
11692
11394
 
11693
11395
  AI Agent Directive:
11694
- Run 'mutagent sandbox ls --json' before attach, exec --sandbox, status or rm.
11695
- ONLY the streaming paths ('attach', 'spawn' without --detach, 'exec --sandbox'
11696
- without --no-stream) emit NDJSON in --json mode — one object per line, with a
11697
- complete summary object as the final line — because a live stream is not a
11698
- single JSON document until it ends. Everything else returns one JSON object.
11699
- Spawning consumes resources until removed — confirm with the user before
11700
- 'spawn' and before 'rm'.
11396
+ Run 'mutagent sandbox ls --json' before attach, exec --sandbox, status or rm. A
11397
+ spawned sandbox runs until it is removed or stopped by the idle policy: confirm
11398
+ with the user before 'spawn' and before 'rm', and tell them the sandbox ID.
11399
+
11400
+ Requires a workspace: mutagent config set workspace <workspace-id>
11701
11401
  `);
11702
11402
  registerSpawnCommand(sandbox);
11703
11403
  registerLsCommand(sandbox);
@@ -11714,7 +11414,7 @@ AI Agent Directive:
11714
11414
 
11715
11415
  // src/commands/env/index.ts
11716
11416
  import { Command as Command16 } from "commander";
11717
- import chalk34 from "chalk";
11417
+ import chalk35 from "chalk";
11718
11418
  import { readFileSync as readFileSync14 } from "fs";
11719
11419
  init_errors();
11720
11420
 
@@ -11814,34 +11514,26 @@ function describeWithoutValue(arg) {
11814
11514
 
11815
11515
  // src/commands/env/index.ts
11816
11516
  function createEnvCommand() {
11817
- const env = new Command16("env").description("Workspace Environments — the variables and secrets an agent’s tools need").addHelpText("after", `
11517
+ const env = new Command16("env").description("Workspace Environments: the variables and secrets an agent’s tools need").addHelpText("after", `
11818
11518
  Examples:
11819
- ${chalk34.dim("$")} mutagent env ls
11820
- ${chalk34.dim("$")} mutagent env set demo GREETING=hello --secret GITHUB_TOKEN=ghp_…
11821
- ${chalk34.dim("$")} mutagent env set staging --from-file .env.staging --secrets-from-file .env.staging.secrets
11822
- ${chalk34.dim("$")} mutagent env show demo
11823
- ${chalk34.dim("$")} mutagent env unset demo GREETING
11824
- ${chalk34.dim("$")} mutagent env rm demo --force
11825
- ${chalk34.dim("$")} mutagent helix session start sbx_1 --rpc --env demo ${chalk34.dim("Load it into a session")}
11826
- ${chalk34.dim("$")} mutagent helix agent --env demo -p "…" ${chalk34.dim("Load it into a one-shot turn")}
11827
-
11828
- ENVIRONMENTS ARE NOT PROVIDERS. 'mutagent providers' configures which LLM models
11829
- a workspace may call and with whose key. 'mutagent env' holds what an agent's
11830
- TOOLS need — a GitHub token, a Langfuse key, a database URL, a feature flag.
11831
- A variable named like a provider key (ANTHROPIC_API_KEY, OPENAI_API_KEY, …) is
11832
- refused, because it would shadow the workspace's provider injection in the box;
11833
- pass --allow-provider-key to store it deliberately.
11834
-
11835
- VALUES ARE WRITE-ONLY. No verb here prints one, and no server route returns one.
11836
- 'show' prints each entry's name and a sha256[:8] fingerprint of its value, which
11837
- is enough to tell whether the value you hold is the one stored.
11838
-
11839
- Names: Environment ^[A-Za-z0-9._-]{1,64}$ · variables ^[A-Z_][A-Z0-9_]*$ · 64 KiB
11840
- per Environment.
11841
-
11842
- Access:
11843
- Environments are minted per WORKSPACE, so a workspace must be configured:
11844
- ${chalk34.dim("$")} mutagent config set workspace <workspace-id>
11519
+ ${chalk35.dim("$")} mutagent env ls
11520
+ ${chalk35.dim("$")} mutagent env set demo GREETING=hello --secret GITHUB_TOKEN=ghp_…
11521
+ ${chalk35.dim("$")} mutagent env set staging --from-file .env.staging --secrets-from-file .env.staging.secrets
11522
+ ${chalk35.dim("$")} mutagent env show demo
11523
+ ${chalk35.dim("$")} mutagent env unset demo GREETING
11524
+ ${chalk35.dim("$")} mutagent env rm demo --force
11525
+ ${chalk35.dim("$")} mutagent helix --env demo -p "check the deploy" ${chalk35.dim("Load it into a Helix cloud run")}
11526
+
11527
+ An Environment holds the variables and secrets an agent's tools need: a GitHub token,
11528
+ a Langfuse key, a database URL. LLM provider keys belong in \`mutagent providers\`; a
11529
+ variable named like one (ANTHROPIC_API_KEY, …) is refused unless --allow-provider-key.
11530
+
11531
+ Values are write-only. \`show\` prints each entry's name and a sha256[:8] fingerprint.
11532
+
11533
+ Names: Environments use letters, digits, '.', '_' and '-' (up to 64); variables use
11534
+ A-Z, 0-9 and _, not starting with a digit. An Environment holds up to 64 KiB.
11535
+
11536
+ Requires a workspace: mutagent config set workspace <workspace-id>
11845
11537
 
11846
11538
  AI Agent Directive:
11847
11539
  Use 'set' to create or update — it creates the Environment when absent and
@@ -11860,10 +11552,10 @@ AI Agent Directive:
11860
11552
  function registerLs(parent) {
11861
11553
  parent.command("ls").description("List the workspace’s Environments").addHelpText("after", `
11862
11554
  Examples:
11863
- ${chalk34.dim("$")} mutagent env ls
11864
- ${chalk34.dim("$")} mutagent env ls --json
11555
+ ${chalk35.dim("$")} mutagent env ls
11556
+ ${chalk35.dim("$")} mutagent env ls --json
11865
11557
 
11866
- An empty list is a normal result: the workspace has no Environments yet.
11558
+ An empty list means the workspace has no Environments yet.
11867
11559
 
11868
11560
  AI Agent Directive:
11869
11561
  --json returns { success, environments: [{ name, vars, secrets, createdAt, updatedAt }], count }.
@@ -11878,7 +11570,7 @@ AI Agent Directive:
11878
11570
  return;
11879
11571
  }
11880
11572
  if (environments.length === 0) {
11881
- console.log(chalk34.gray("No Environments in this workspace. Create one: mutagent env set <name> KEY=VALUE"));
11573
+ console.log(chalk35.gray("No Environments in this workspace. Create one: mutagent env set <name> KEY=VALUE"));
11882
11574
  return;
11883
11575
  }
11884
11576
  output.output(environments.map((e) => ({
@@ -11895,13 +11587,15 @@ AI Agent Directive:
11895
11587
  function registerShow(parent) {
11896
11588
  parent.command("show").description("Entry names and fingerprints of one Environment — never a value").argument("<name>", "Environment name").addHelpText("after", `
11897
11589
  Examples:
11898
- ${chalk34.dim("$")} mutagent env show demo
11899
- ${chalk34.dim("$")} mutagent env show demo --json
11590
+ ${chalk35.dim("$")} mutagent env show demo
11591
+ ${chalk35.dim("$")} mutagent env show demo --json
11900
11592
 
11901
11593
  Each line is NAME KIND FINGERPRINT. The fingerprint is sha256(value)[:8]; compare
11902
11594
  it with your own (printf %s "$VALUE" | shasum -a 256 | cut -c1-8) to check what is
11903
11595
  stored without the server ever sending the value.
11904
11596
 
11597
+ An unknown name is an error.
11598
+
11905
11599
  AI Agent Directive:
11906
11600
  --json returns { success, environment: { name, vars: [{name, fingerprint}],
11907
11601
  secrets: [{name, fingerprint}], createdAt, updatedAt } }.
@@ -11917,37 +11611,29 @@ AI Agent Directive:
11917
11611
  });
11918
11612
  }
11919
11613
  function registerSet(parent) {
11920
- parent.command("set").description("Set variables (and --secret secrets) on an Environment; creates it when absent").argument("<name>", "Environment name").argument("[assignments...]", "KEY=VALUE variables (non-secret)").option("-s, --secret <KEY=VALUE...>", "Secret entries — encrypted at rest, never returned").option("--from-file <path>", "Read variables from a .env file (KEY=VALUE lines); argv wins on the same name").option("--secrets-from-file <path>", "Read secrets from a .env file").option("--replace", "Replace the whole Environment (PUT) instead of merging into it (PATCH)").option("--allow-provider-key", "Permit a variable named like an LLM provider key (it would shadow the workspace’s provider injection)").addHelpText("after", `
11614
+ parent.command("set").description("Set variables (and --secret secrets) on an Environment; creates it when absent").argument("<name>", "Environment name").argument("[assignments...]", "KEY=VALUE variables (non-secret)").option("-s, --secret <KEY=VALUE...>", "Secret entries — encrypted at rest, never returned").option("--from-file <path>", "Read variables from a .env file (KEY=VALUE lines); argv wins on the same name").option("--secrets-from-file <path>", "Read secrets from a .env file").option("--replace", "Replace the whole Environment instead of merging into it").option("--allow-provider-key", "Permit a variable named like an LLM provider key").addHelpText("after", `
11921
11615
  Examples:
11922
- ${chalk34.dim("$")} mutagent env set demo GREETING=hello
11923
- ${chalk34.dim("$")} mutagent env set demo GREETING=hello --secret TOKEN=abc123
11924
- ${chalk34.dim("$")} mutagent env set demo --secret GITHUB_TOKEN=ghp_x --secret LANGFUSE_SECRET_KEY=sk-lf-x
11925
- ${chalk34.dim("$")} mutagent env set staging --from-file .env.staging
11926
- ${chalk34.dim("$")} mutagent env set staging --secrets-from-file .env.staging.secrets
11927
- ${chalk34.dim("$")} mutagent env set staging --replace --from-file .env.staging ${chalk34.dim("Drop everything not in the file")}
11928
-
11929
- MERGE BY DEFAULT. Entries you name are set; entries you do not name stay. The
11930
- Environment is created if it does not exist, so this is one step on a fresh
11931
- workspace. --replace sends the whole Environment instead and REMOVES every entry
11932
- you did not name — secrets included.
11933
-
11934
- VARIABLES vs SECRETS. Positional KEY=VALUE and --from-file are non-secret
11935
- variables (stored readable). --secret and --secrets-from-file are secrets
11936
- (encrypted at rest with the same key as provider credentials). Both reach the
11937
- box as plain environment variables; the difference is how they are stored and
11938
- that a secret's value can never be read back through the API. A name may not be
11939
- both.
11940
-
11941
- A VALUE MAY CONTAIN '='. The split is at the first one: TOKEN=a=b stores "a=b".
11942
- Quote values with spaces for your shell; this command does not re-split them.
11943
-
11944
- Names: variables ^[A-Z_][A-Z0-9_]*$ · Environment ^[A-Za-z0-9._-]{1,64}$ · 64 KiB
11945
- per Environment. A variable named like an LLM provider key (ANTHROPIC_API_KEY, …)
11946
- is a 409 unless --allow-provider-key is passed — see 'mutagent env --help'.
11616
+ ${chalk35.dim("$")} mutagent env set demo GREETING=hello
11617
+ ${chalk35.dim("$")} mutagent env set demo GREETING=hello --secret TOKEN=abc123
11618
+ ${chalk35.dim("$")} mutagent env set staging --from-file .env.staging
11619
+ ${chalk35.dim("$")} mutagent env set staging --replace --from-file .env.staging ${chalk35.dim("Drop everything not in the file")}
11620
+
11621
+ Entries you name are set and the rest stay; the Environment is created if missing.
11622
+ --replace removes every entry you do not name, secrets included.
11623
+ Positional KEY=VALUE and --from-file are variables; --secret and --secrets-from-file are
11624
+ secrets (encrypted at rest, never returned). Both reach the box as env vars. A name
11625
+ cannot be both.
11626
+ --secret takes every KEY=VALUE after it, so put variables first:
11627
+ ${chalk35.dim("$")} mutagent env set demo A=1 --secret TOKEN=x
11628
+ A value may contain '=': TOKEN=a=b stores "a=b".
11629
+ Names: A-Z, 0-9 and _, not starting with a digit. An invalid name exits 2. A name that
11630
+ looks like an LLM provider key is refused by the server (exit 1) unless
11631
+ --allow-provider-key.
11947
11632
 
11948
11633
  AI Agent Directive:
11949
11634
  Nothing printed here contains a value. --json returns { success, environment,
11950
- replaced: boolean }. A refused name exits 2 and names the KEY, not the value.
11635
+ replaced: boolean }. An invalid name exits 2 and names the KEY, not the value;
11636
+ a name that looks like an LLM provider key exits 1 unless --allow-provider-key.
11951
11637
  `).action(async (name, assignments, options) => {
11952
11638
  const isJson = getJsonFlag(parent);
11953
11639
  const output = new OutputFormatter(isJson ? "json" : "table");
@@ -11977,12 +11663,11 @@ AI Agent Directive:
11977
11663
  function registerUnset(parent) {
11978
11664
  parent.command("unset").description("Remove entries from an Environment").argument("<name>", "Environment name").argument("<keys...>", "Entry names to remove").option("-s, --secret", "The names are secrets (default: try both variables and secrets)").addHelpText("after", `
11979
11665
  Examples:
11980
- ${chalk34.dim("$")} mutagent env unset demo GREETING
11981
- ${chalk34.dim("$")} mutagent env unset demo TOKEN OTHER_TOKEN
11666
+ ${chalk35.dim("$")} mutagent env unset demo GREETING
11667
+ ${chalk35.dim("$")} mutagent env unset demo TOKEN OTHER_TOKEN
11982
11668
 
11983
- A name is removed wherever it is — variable or secret — because a name cannot be
11984
- both. Unsetting a name that is not there is not an error: the Environment ends
11985
- in the state you asked for.
11669
+ A name is removed wherever it is, variable or secret. Unsetting a name that is not
11670
+ there succeeds.
11986
11671
 
11987
11672
  AI Agent Directive:
11988
11673
  --json returns { success, environment, removed: [names] }.
@@ -12007,15 +11692,17 @@ AI Agent Directive:
12007
11692
  });
12008
11693
  }
12009
11694
  function registerRm(parent) {
12010
- parent.command("rm").description("Delete an Environment and its secrets").argument("<name>", "Environment name").option("-f, --force", "Skip confirmation").addHelpText("after", `
11695
+ parent.command("rm").description("Delete an Environment and its secrets").argument("<name>", "Environment name").option("-f, --force", "Confirm (required unless --json)").addHelpText("after", `
12011
11696
  Examples:
12012
- ${chalk34.dim("$")} mutagent env rm demo --force
12013
- ${chalk34.dim("$")} mutagent env rm demo --json
11697
+ ${chalk35.dim("$")} mutagent env rm demo --force
11698
+ ${chalk35.dim("$")} mutagent env rm demo --json
12014
11699
 
12015
- Deletes the Environment and its encrypted secrets. Boxes already running keep
12016
- what they were handed; the next run naming this Environment fails with 404.
11700
+ Deletes the Environment and its encrypted secrets. Boxes already running keep what
11701
+ they were handed; the next run naming this Environment fails.
11702
+ Without --force (and without --json) the command refuses; it never prompts.
11703
+ An unknown name is an error.
12017
11704
 
12018
- ${chalk34.dim("Note: --force is required. The CLI is non-interactive — confirm with the user via your native flow, then pass --force. --json auto-confirms.")}
11705
+ JSON: { success, deleted, name }
12019
11706
 
12020
11707
  AI Agent Directive:
12021
11708
  Confirm with the user before running it, then pass --force (or --json).
@@ -12061,7 +11748,7 @@ function printEnvironment(environment, output, isJson) {
12061
11748
  output.output({ success: true, environment });
12062
11749
  return;
12063
11750
  }
12064
- console.log(`${chalk34.bold(environment.name)} ${chalk34.dim(environment.updatedAt ?? "")}`);
11751
+ console.log(`${chalk35.bold(environment.name)} ${chalk35.dim(environment.updatedAt ?? "")}`);
12065
11752
  printEntries(environment);
12066
11753
  }
12067
11754
  function printEntries(environment) {
@@ -12070,12 +11757,12 @@ function printEntries(environment) {
12070
11757
  ...environment.secrets.map((s) => ({ name: s.name, kind: "secret", fingerprint: s.fingerprint }))
12071
11758
  ];
12072
11759
  if (rows.length === 0) {
12073
- console.log(chalk34.gray(" (no entries)"));
11760
+ console.log(chalk35.gray(" (no entries)"));
12074
11761
  return;
12075
11762
  }
12076
11763
  const width = Math.max(...rows.map((r) => r.name.length));
12077
11764
  for (const row of rows) {
12078
- console.log(` ${row.name.padEnd(width)} ${row.kind === "secret" ? chalk34.yellow("secret") : chalk34.dim("var ")} ${chalk34.dim(row.fingerprint)}`);
11765
+ console.log(` ${row.name.padEnd(width)} ${row.kind === "secret" ? chalk35.yellow("secret") : chalk35.dim("var ")} ${chalk35.dim(row.fingerprint)}`);
12079
11766
  }
12080
11767
  }
12081
11768
 
@@ -12647,77 +12334,56 @@ program.name("mutagent").description(`Mutagent CLI - command-line client for the
12647
12334
  });
12648
12335
  program.addHelpText("beforeAll", () => renderBanner());
12649
12336
  program.addHelpText("after", `
12650
- ${chalk35.bold.cyan("WORKFLOWS:")}
12651
- ${chalk35.bold("Setup")} mutagent login → mutagent init
12652
- ${chalk35.bold("Lifecycle Tools")} mutagent install helix ${chalk35.dim("(login-gated)")}
12653
- ${chalk35.bold("Feedback")} mutagent feedback send "<what happened>" --category <cli|helix|stage:<x>> ${chalk35.dim("[--session <id>] [--attach-transcript]")}
12654
-
12655
- ${chalk35.dim("For CLI usage guidance for AI agents, see the Skill at")}
12656
- ${chalk35.cyan(".claude/skills/mutagent-cli/SKILL.md")}
12657
-
12658
- ${chalk35.yellow("Global flags:")}
12659
- -v, --version ${chalk35.dim("Print the CLI version")} ${chalk35.dim("(before a subcommand; after one it is the subcommand's)")}
12660
- --json --api-key <k> --endpoint <url> --non-interactive
12661
-
12662
- ${chalk35.yellow("Non-Interactive Mode (CI/CD & Coding Agents):")}
12663
- export MUTAGENT_API_KEY=mt_... ${chalk35.dim("or")} --api-key mt_...
12664
- --json ${chalk35.dim("for structured output")} --non-interactive ${chalk35.dim("to disable prompts")}
12665
-
12666
- ${chalk35.yellow("Command Navigation:")}
12667
- mutagent login ${chalk35.dim("Login (browser OAuth — recommended)")}
12668
- mutagent auth status ${chalk35.dim("Check auth + workspace")}
12669
- mutagent init ${chalk35.dim("Initialize project (.mutagentrc.json)")}
12670
- mutagent workspaces list --json ${chalk35.dim("List workspaces (verify ID)")}
12671
- mutagent config set workspace <id> ${chalk35.dim("Set active workspace")}
12672
- mutagent usage --json ${chalk35.dim("Show account usage + provider status")}
12673
-
12674
- mutagent providers list --json ${chalk35.dim("List configured BYOK providers")}
12675
- mutagent providers list --models ${chalk35.dim("See available models per provider")}
12676
-
12677
- mutagent helix agent "<who>" -p "<task>" ${chalk35.dim("One Helix agent turn in a cloud sandbox")}
12678
- mutagent sandbox ls --json ${chalk35.dim("List running sandboxes")}
12679
- mutagent sandbox spawn --image <ref> ${chalk35.dim("Persistent sandbox you attach to")}
12680
- mutagent sandbox rm <id> --force ${chalk35.dim("Destroy a sandbox (the only thing that does)")}
12681
-
12682
- mutagent install helix ${chalk35.dim("Install the ADL lifecycle conductor locally (login-gated)")}
12683
- mutagent install helix --version 1.2.3 ${chalk35.dim("Pin a version")}
12684
- mutagent install --help ${chalk35.dim("helix")}
12685
-
12686
- mutagent hooks --help ${chalk35.dim("Hook setup for Claude Code session telemetry upload")}
12687
-
12688
- ${chalk35.bold.red("Report Issues:")}
12689
- Hit a bug? Run: ${chalk35.cyan('mutagent feedback send "describe what went wrong" --category cli')}
12690
- Lifecycle-stage feedback: ${chalk35.cyan('mutagent feedback send "eval gate unclear" --category stage:evaluate')}
12691
- Link to a session: ${chalk35.cyan('mutagent feedback send "..." --session <session-id>')}
12692
- Attach your coding-agent session transcript: ${chalk35.cyan('mutagent feedback send "..." --attach-transcript')}
12693
- ${chalk35.dim("--category accepts: cli | helix | stage:<spec|build|evaluate|diagnose|optimize>")}
12694
-
12695
- ${chalk35.yellow("Directive System:")}
12696
- Every --json response may include:
12697
- ${chalk35.bold("_directive.display")} Type tag — 'status_card' for card-kind directives (drives test/docs guards)
12698
- ${chalk35.bold("_directive.renderedCard")} Pre-formatted card ${chalk35.red("(MUST echo verbatim in chat whenever this field exists — see SKILL.md Verbatim Card Display Protocol)")}
12699
- ${chalk35.bold("_directive.instruction")} Next step for the agent (self-sufficient, no Skill required)
12700
- ${chalk35.bold("_directive.next")} Array of suggested follow-up commands
12701
- ${chalk35.bold("_links")} Dashboard/API URLs (format as markdown links)
12702
- ${chalk35.bold("_compat")} Compat metadata: cliVersion, skillVersion, skillMinCliVersion
12703
-
12704
- ${chalk35.yellow("AI Agent Rules (MANDATORY for coding agents):")}
12705
- 1. Login (two paths):
12706
- - CI / fully automated: export MUTAGENT_API_KEY=mt_... then mutagent login --json
12707
- - Helping a user onboard: mutagent login --browser --json
12708
- (CLI prints an auth URL — surface it to the user verbatim, then the CLI
12709
- polls for up to 5 minutes while the user authorizes in their browser.
12710
- This IS agent-compatible. --non-interactive is NOT needed with --browser.)
12711
- 2. EVERY command MUST include --json (no exceptions)
12712
- 3. Run <command> --help BEFORE first use of any command
12713
- 4. Parse _directive.renderedCard and copy it into your CHAT RESPONSE verbatim
12714
- ${chalk35.red("HARD STOP")}: do NOT run further commands until the card is rendered in chat
12715
- 5. After mutagent init, verify workspace: mutagent workspaces list --json
12716
- 6. ALL user interaction via AskUserQuestion — CLI is non-interactive
12337
+ ${chalk36.yellow("Get started:")}
12338
+ mutagent login ${chalk36.dim("Sign in (browser or API key)")}
12339
+ mutagent providers mirror ${chalk36.dim("Copy local Helix LLM provider keys into the workspace")}
12340
+ mutagent helix -p "<task>" ${chalk36.dim("Run Helix in the cloud")}
12341
+ mutagent install helix ${chalk36.dim("Install Helix on this machine")}
12342
+
12343
+ ${chalk36.yellow("Workspace:")}
12344
+ mutagent auth status ${chalk36.dim("Sign-in state and the active workspace")}
12345
+ mutagent workspaces list ${chalk36.dim("Workspaces you can see")}
12346
+ mutagent config set workspace <id> ${chalk36.dim("Choose the active workspace")}
12347
+ mutagent providers list ${chalk36.dim("LLM providers in the workspace")}
12348
+ mutagent helix models ${chalk36.dim("Models Helix can use in the cloud, and the workspace default model")}
12349
+ mutagent env ls ${chalk36.dim("Environments: variables and secrets for tools")}
12350
+ mutagent usage ${chalk36.dim("Count the workspace's LLM providers and your workspaces")}
12351
+
12352
+ ${chalk36.yellow("Automation:")}
12353
+ MUTAGENT_API_KEY=mt_... ${chalk36.dim("Sign in without a browser (or --api-key <key>)")}
12354
+ --json ${chalk36.dim("Structured output")}
12355
+ --non-interactive ${chalk36.dim("No sign-in method or consent prompts (also when CI=true or stdin is not a terminal); a first sign-in in a terminal ends with a menu unless --json")}
12356
+ -v, --version ${chalk36.dim("CLI version (only before a subcommand)")}
12357
+
12358
+ ${chalk36.yellow("Report a problem:")}
12359
+ mutagent feedback send "<what happened>" --category cli
12360
+ ${chalk36.dim("--category accepts: cli | helix | stage:<spec|build|evaluate|diagnose|optimize>")}
12361
+
12362
+ ${chalk36.dim("For CLI usage guidance for AI agents, see the Skill at")}
12363
+ ${chalk36.cyan(".claude/skills/mutagent-cli/SKILL.md")}
12364
+
12365
+ ${chalk36.yellow("Directive System:")}
12366
+ A --json response may include:
12367
+ ${chalk36.bold("_directive.renderedCard")} A status card: copy it into your chat reply verbatim before running another command
12368
+ ${chalk36.bold("_directive.instruction")} What to do next
12369
+ ${chalk36.bold("_directive.next")} Suggested follow-up commands
12370
+ ${chalk36.bold("_links")} Dashboard and API URLs
12371
+ ${chalk36.bold("_compat")} cliVersion, skillVersion, skillMinCliVersion
12372
+
12373
+ ${chalk36.yellow("AI Agent Rules (MANDATORY for coding agents):")}
12374
+ 1. Sign in: with MUTAGENT_API_KEY set, run mutagent login --json. To help a user sign in,
12375
+ run mutagent login --browser --json, show the printed URL to the user verbatim, and
12376
+ wait: the CLI polls for up to 5 minutes.
12377
+ 2. Pass --json to every command except a mutagent helix launch (-p, --mode json or rpc), whose output is Helix's own.
12378
+ 3. Run <command> --help before the first use of a command.
12379
+ 4. When a response has _directive.renderedCard, copy it into your chat reply verbatim
12380
+ before running another command.
12381
+ 5. After mutagent init, check the workspace: mutagent workspaces list --json
12382
+ 6. Ask the user through your own question tool. With --json only mutagent login can prompt, in a terminal; pass --browser or set MUTAGENT_API_KEY.
12717
12383
  ${!hasCredentials() ? `
12718
- ` + chalk35.yellow(" Warning: Not authenticated. Run: mutagent login") + `
12384
+ ` + chalk36.yellow(" Warning: Not authenticated. Run: mutagent login") + `
12719
12385
  ` : ""}${!hasRcConfig() ? `
12720
- ` + chalk35.green(" Get started: mutagent init") + `
12386
+ ` + chalk36.green(" Get started: mutagent init") + `
12721
12387
  ` : ""}`);
12722
12388
  var rawArgs = process.argv.slice(2);
12723
12389
  if (isGlobalVersionFlag(rawArgs)) {
@@ -12782,5 +12448,5 @@ var argvForCommander = execCommand === undefined ? rawArgs : protectBoxArgv(rawA
12782
12448
  if (!helixHelpJson)
12783
12449
  program.parse(argvForCommander, { from: "user" });
12784
12450
 
12785
- //# debugId=383FC9FB725D595F64756E2164756E21
12451
+ //# debugId=0381115E4249D9B864756E2164756E21
12786
12452
  //# sourceMappingURL=cli.js.map