@rosthq/cli 0.7.9 → 0.7.11

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.
@@ -1 +1 @@
1
- {"version":3,"file":"command-manifest.d.ts","sourceRoot":"","sources":["../../src/generated/command-manifest.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAE3E,eAAO,MAAM,gBAAgB,EAAE,SAAS,oBAAoB,EAiN3D,CAAC"}
1
+ {"version":3,"file":"command-manifest.d.ts","sourceRoot":"","sources":["../../src/generated/command-manifest.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAE3E,eAAO,MAAM,gBAAgB,EAAE,SAAS,oBAAoB,EAkN3D,CAAC"}
package/dist/index.js CHANGED
@@ -46165,6 +46165,17 @@ var softwareGrantStatusSchema = external_exports2.enum(["active", "revoked"]);
46165
46165
  var softwareConfigValueKindSchema = external_exports2.enum(["plain", "secret_ref"]);
46166
46166
  var softwareCredentialRequestStatusSchema = external_exports2.enum(["pending", "fulfilled", "rejected", "canceled"]);
46167
46167
  var softwareSecretAccessOperationSchema = external_exports2.enum(["use", "vercel_env_sync", "test_execution"]);
46168
+ var softwareProjectSummarySchema = external_exports2.object({
46169
+ id: uuid10,
46170
+ name: external_exports2.string().min(1),
46171
+ slug: external_exports2.string().min(1),
46172
+ base_branch: external_exports2.string().min(1),
46173
+ repo_ref: external_exports2.string().nullable()
46174
+ });
46175
+ var softwareProjectListInputSchema = external_exports2.object({}).strict();
46176
+ var softwareProjectListOutputSchema = external_exports2.object({
46177
+ projects: external_exports2.array(softwareProjectSummarySchema)
46178
+ });
46168
46179
  var VAULT_REF_PROVIDER = /^(?:infisical|doppler|local-dev):\/\/\S+$/;
46169
46180
  var RAW_SECRET_SUBSTRING = /sk-ant-|sk-proj-|sk-live-|BEGIN |PRIVATE KEY|api[_-]?key=|secret=/i;
46170
46181
  var softwareVaultRefSchema = external_exports2.string().min(1).max(500).regex(VAULT_REF_PROVIDER, "vault_ref must be a provider URI, e.g. infisical://path, doppler://path, or local-dev://path").refine((value) => !RAW_SECRET_SUBSTRING.test(value), {
@@ -47444,11 +47455,11 @@ The Compass is drafted, then activated by a human through supersession.
47444
47455
  order: 15,
47445
47456
  title: "AICOS chat guide",
47446
47457
  summary: "How the AI Chief of Staff chat works in the authenticated app shell and what it can safely do today.",
47447
- version: "2026-07-03.1",
47458
+ version: "2026-07-03.3",
47448
47459
  public: true,
47449
47460
  audiences: ["human", "in_app_agent"],
47450
47461
  stages: ["company_setup", "operating_rhythm"],
47451
- relatedCommandIds: ["confirmation.approve", "onboarding.status", "graph.get", "seat.get"],
47462
+ relatedCommandIds: ["confirmation.approve", "onboarding.status", "graph.get", "seat.get", "mcp_token.list", "runner.list"],
47452
47463
  legal: {
47453
47464
  publicRisk: "low",
47454
47465
  notes: [
@@ -47484,6 +47495,14 @@ Each thread has a purpose:
47484
47495
 
47485
47496
  You can reopen prior threads, search thread titles and summaries, or start a new one. Session history is stored in tenant-scoped conversation tables so chat history is not reconstructed from analytics, events, or run logs.
47486
47497
 
47498
+ ## Set up AICOS for an existing company
47499
+
47500
+ New companies receive the governed AICOS foundation during provisioning. Existing companies can also set it up later from **Settings -> AICOS**. Owners and admins can see whether the foundation exists, repair it if needed, and choose the run location after setup.
47501
+
47502
+ The repair action ensures the same structure new companies receive: a Founder/CEO root seat, an owner occupancy for stewardship, an **AI Chief of Staff** seat, the stock AICOS agent, and one active agent occupancy. It is idempotent; running it again repairs missing pieces instead of creating a second AICOS. If older setup attempts left more than one active AICOS occupancy, the repair path keeps the best current stock AICOS agent \u2014 live before dry-run, draft, or paused \u2014 and ends the extra AICOS occupancies so Settings, lane readiness, and chat resolve the same governed agent.
47503
+
47504
+ Settings is the infrastructure surface for AICOS. Use it to confirm foundation status, inspect the current lane, see whether a seat-scoped MCP token exists, and choose **Cloud**, **Runner**, or **MCP** when the server-side readiness checks allow it. The richer agent controls remain with the agent itself: open the AICOS seat from Settings or the agent fleet to manage Charter, tool permissions, approvals, go-live state, and run evidence.
47505
+
47487
47506
  ## What context AICOS receives
47488
47507
 
47489
47508
  Each turn carries server-normalized page context: route template, current path, safe search state, selected seat or object ids when authorized, and onboarding state when relevant. The server derives tenant and user authority from the authenticated session. Client-supplied tenant context is never accepted as authority.
@@ -48311,7 +48330,7 @@ External connectors are being rolled out provider by provider, conservatively (r
48311
48330
  order: 48,
48312
48331
  title: "CLI and MCP installation guide",
48313
48332
  summary: "Install the public CLI, register remote token-backed MCP clients, and find the full command and tool catalog.",
48314
- version: "2026-07-03.6",
48333
+ version: "2026-07-03.10",
48315
48334
  public: true,
48316
48335
  audiences: ["human", "cli", "mcp", "in_app_agent"],
48317
48336
  stages: ["company_setup", "staffing"],
@@ -49024,6 +49043,7 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
49024
49043
  | \`rost_revoke_skill_from_seat\` | \`skill.revoke_from_seat\` | Human-gated revocation that stops future Skill use without deleting historical activations. | Tenant-admin | Call with \`{"assignment_id":"<assignment-id>"}\`; non-interactive callers receive a confirmation handoff. |
49025
49044
  | \`rost_list_model_catalog\` | \`model.catalog\` | List guided model tiers \u2014 recommendations, token prices, cost bands, best-fit work, and model ids for \`--model\`. | Tenant | Call with \`{}\`. |
49026
49045
  | \`rost_create_a_forge_build_request\` | \`software_factory.request.create\` | Open a governed Forge build request against a connected software project; records the request plus its initial intake phase run. Requires the Forge add-on; the title is untrusted display text. | Tenant | Call with \`{"software_project_id":"<project-id>","title":"Add invoice export"}\`. |
49046
+ | \`rost_list_forge_projects\` | \`software_factory.project.list\` | List active Forge software projects so a tenant can choose a project for build requests, GitHub repository bindings, and config. | Tenant | Call with \`{}\`; use the returned \`id\` as \`software_project_id\`. |
49027
49047
  | \`rost_list_forge_build_requests\` | \`software_factory.request.list\` | The Forge control-room board: build requests with status, current phase, and risk. Read-only; requires the Forge add-on. | Tenant | Call with \`{}\` or \`{"limit":20}\`. |
49028
49048
  | \`rost_show_a_forge_build_request\` | \`software_factory.request.show\` | The Forge request detail: the request, current phase, phase-run history, and gates. Read-only; requires the Forge add-on. | Tenant | Call with \`{"build_request_id":"<request-id>"}\`. |
49029
49049
  | \`rost_advance_a_forge_build_request_phase\` | \`software_factory.phase.advance\` | Advance a build request to its next phase; the server enforces the closed phase state machine and rejects an illegal transition. | Tenant | Call with \`{"build_request_id":"<request-id>","to_phase":"discovery_scoping"}\`. |
@@ -49038,18 +49058,20 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
49038
49058
  | \`rost_list_forge_secret_requests\` | \`software_factory.secret_request.list\` | List missing-secret requests and their approval Task ids/statuses. It returns request metadata only \u2014 no secret values and no vault refs. | Tenant | Call with \`{}\` or \`{"status":"pending"}\`. |
49039
49059
  | \`rost_grant_a_forge_secret\` | \`software_factory.secret.grant\` | A human grants a seat scoped access to a project secret by vault ref (Postgres stores only the pointer, never secret material). Owner-only and human-gated. | Tenant-admin | Call with \`{"software_project_id":"<project-id>","environment":"production","seat_id":"<seat-id>","action":"vercel.env.sync","key_name":"OPENAI_API_KEY","vault_ref":"infisical://prod/OPENAI_API_KEY"}\`. |
49040
49060
  | \`rost_revoke_a_forge_secret_grant\` | \`software_factory.secret.revoke\` | Revoke a Forge secret grant (teardown \u2014 cut a compromised seat's access immediately). Owner-only and human-gated. | Tenant-admin | Call with \`{"grant_id":"<grant-id>"}\`; non-interactive callers receive a confirmation handoff. |
49041
- | \`rost_bind_a_forge_github_installation\` | \`software_factory.github.installation.bind\` | Bind a GitHub App installation and selected repositories to Forge projects. Owner-only and human-gated. | Tenant-admin | Call with \`{"installation_id":123,"account_login":"acme","account_id":456,"account_type":"Organization","repositories":[{"software_project_id":"<project-id>","repository_id":789,"repository_name":"app","repository_full_name":"acme/app"}]}\`. |
49061
+ | \`rost_bind_a_forge_github_installation\` | \`software_factory.github.installation.bind\` | Bind a GitHub App installation and selected repositories to Forge projects. Owner-only and human-gated; the settings UI uses the same command after GitHub redirects back with an installation id. | Tenant-admin | First run \`rost command software_factory.project.list --json '{}'\` to choose a project id. Then call \`{"installation_id":123,"account_login":"acme","account_id":456,"account_type":"Organization","repositories":[{"software_project_id":"<project-id>","repository_id":789,"repository_name":"app","repository_full_name":"acme/app","private":true,"default_branch":"main","permissions":{"metadata":"read","contents":"write","pull_requests":"write","issues":"write"}}]}\`. |
49042
49062
  | \`rost_set_a_forge_github_automation_policy\` | \`software_factory.github.policy.upsert\` | Set the server-side GitHub automation policy: allowed actions, automation-mode ceiling, and operations requiring humans. Owner-only and human-gated. | Tenant-admin | Call with \`{"software_project_id":"<project-id>","automation_mode_ceiling":"pr_only","allowed_actions":["webhook_intake","installation_token"],"requires_human_for":["deploy_production"]}\`. |
49043
49063
  | \`rost_create_a_forge_github_installation_token\` | \`software_factory.github.installation_token.create\` | Mint a short-lived GitHub App installation token for one bound repository after policy evaluation. The token is returned once and never stored. | Tenant | Call with \`{"software_project_id":"<project-id>","repository_id":789,"requested_actions":["read"],"requested_automation_mode":"plan_only"}\`. |
49044
49064
  | \`rost_list_forge_runner_capacity\` | \`software_factory.capacity.list\` | List Forge runner capacity observations (advisory scheduling input only, never an authority input). Read-only; requires the Forge add-on. | Tenant | Call with \`{}\` or \`{"runner_id":"<runner-id>"}\`. |
49045
49065
 
49066
+ The web Forge control room at \`/forge\` uses the same command path for project selection, build request creation/list/detail reads, runner capacity, configuration status, and Developer Team template install. The template install button deliberately stages a pending confirmation before durable seats, Skills, or authority grants are applied.
49067
+
49046
49068
  #### Forge GitHub App access
49047
49069
 
49048
49070
  Forge repository access is through a tenant-bound GitHub App installation, not through a human's ambient \`git\` or \`gh\` credentials. The server verifies GitHub webhook signatures before parsing payloads, resolves the installation and repository against active tenant bindings, records delivery ids for idempotency, and creates GitHub issue build requests only when an active repo binding and policy allow intake.
49049
49071
 
49050
49072
  Installation tokens are short lived and are not stored. The durable record is audit metadata: installation id, repository id, requested action, policy decision, reason, and related Forge request when one exists. GitHub issue titles, labels, and bodies are treated as untrusted source content; labels may request an automation mode, but only server-side Forge automation policy can allow \`pr_only\`, \`merge_after_checks\`, \`deploy_preview\`, or \`deploy_production\`. Production deploy remains human-gated.
49051
49073
 
49052
- The Forge GitHub App itself is org-owned. App private key, webhook secret, and client secret values are stored through the vault and configured by refs (\`GITHUB_APP_PRIVATE_KEY_REF\`, \`GITHUB_APP_WEBHOOK_SECRET_REF\`, \`GITHUB_APP_CLIENT_SECRET_REF\`); raw secret env vars are local/legacy compatibility only. The dogfood app is \`ROST Forge\` under \`agenticoperatingsystem\` with App ID \`4208495\` and Client ID \`Iv23liImH7qlBvXYQ0Wx\`. The base app omits \`administration\` and \`workflows\`; elevated repo creation or workflow mutation needs a separate human-approved power path.
49074
+ The Forge GitHub App itself is org-owned. App private key, webhook secret, and client secret values are platform-owned ROST operator credentials stored through the vault's reserved ROST system namespace, not through a customer tenant namespace or a tenant UUID deployment variable. Manifest conversion and manual secret storage also require the app-level \`GITHUB_APP_OPERATOR_EMAILS\` allowlist, which should contain only approved ROST platform operator emails. Deployment config carries only refs (\`GITHUB_APP_PRIVATE_KEY_REF\`, \`GITHUB_APP_WEBHOOK_SECRET_REF\`, \`GITHUB_APP_CLIENT_SECRET_REF\`); raw secret env vars are local/legacy compatibility only. The dogfood app is \`ROST Forge\` under \`agenticoperatingsystem\` with App ID \`4208495\` and Client ID \`Iv23liImH7qlBvXYQ0Wx\`. The base app omits \`administration\` and \`workflows\`; elevated repo creation or workflow mutation needs a separate human-approved power path.
49053
49075
 
49054
49076
  ### Seat-scoped operating tools
49055
49077
 
@@ -49390,7 +49412,7 @@ Set \`ROST_SKILL_INSTALL_ROOT\` only for a sandbox or CI override. Local copies
49390
49412
  order: 55,
49391
49413
  title: "Agent reference map",
49392
49414
  summary: "Where CLI sessions, MCP clients, and in-app agents should retrieve {{brand}} guidance before recommending setup changes.",
49393
- version: "2026-07-03.1",
49415
+ version: "2026-06-27.2",
49394
49416
  public: true,
49395
49417
  audiences: ["cli", "mcp", "in_app_agent"],
49396
49418
  stages: ["company_setup", "graph_design", "charter_design", "staffing", "operating_rhythm"],
@@ -49887,7 +49909,7 @@ Keep agent scope tight at first. Approve more autonomy only after evidence. Use
49887
49909
  order: 71,
49888
49910
  title: "Confirmations and human gates guide",
49889
49911
  summary: "How {{brand}} routes authority-changing work through human confirmation, and why agents never approve their own requests.",
49890
- version: "2026-06-27.2",
49912
+ version: "2026-07-03.1",
49891
49913
  public: true,
49892
49914
  audiences: ["human", "cli", "mcp", "in_app_agent"],
49893
49915
  stages: ["graph_design", "charter_design", "staffing", "operating_rhythm"],
@@ -49924,6 +49946,8 @@ A gated command does not mutate when an agent calls it. It returns a pending con
49924
49946
 
49925
49947
  When an **agent** triggers a gate, {{brand}} also notifies the seat's steward (Slack with an email fallback) so the held action does not sit silent \u2014 a human is told a decision is waiting and given the approve link. A human pausing their own gated command does not self-notify. See the notifications guide.
49926
49948
 
49949
+ Approval replays are validated again before execution. If the stored confirmation input no longer matches the original command schema, the approval fails without consuming the pending confirmation or running the durable command; reject it and re-issue the gated action from the current product surface.
49950
+
49927
49951
  \`\`\`bash
49928
49952
  # Human approves a pending confirmation
49929
49953
  {{cli}} command confirmation.approve --json '{"confirmation_id":"<confirmation-id>"}'
@@ -50149,7 +50173,7 @@ Agents may explain which setting is needed and why. They should not ask users to
50149
50173
  order: 73,
50150
50174
  title: "Members and invites guide",
50151
50175
  summary: "How to invite humans into the company without confusing user access with seat accountability.",
50152
- version: "2026-07-03.1",
50176
+ version: "2026-07-03.2",
50153
50177
  public: true,
50154
50178
  audiences: ["human", "cli", "mcp", "in_app_agent"],
50155
50179
  stages: ["company_setup", "staffing"],
@@ -50228,7 +50252,7 @@ Every notification should include the seat, cause, evidence, and requested decis
50228
50252
  order: 75,
50229
50253
  title: "Local runner guide",
50230
50254
  summary: "How local agent sessions and runner surfaces should operate through {{brand}} without bypassing Charters or audit.",
50231
- version: "2026-07-03.1",
50255
+ version: "2026-07-03.2",
50232
50256
  public: true,
50233
50257
  audiences: ["human", "cli", "mcp", "in_app_agent"],
50234
50258
  stages: ["staffing", "operating_rhythm"],
@@ -50260,13 +50284,13 @@ Use Local Runner when a Seat should run on a machine you control. Install it fro
50260
50284
 
50261
50285
  Recommended paths:
50262
50286
 
50263
- - **Always-on host:** \`{{cli}} runner install-service --name <runner-name> --execute\` installs a background service (macOS launchd user agent, \`user/<uid>\` domain) that keeps the runner online. Manage it with \`{{cli}} runner start|stop|restart|status|logs|uninstall --name <runner-name>\`. On macOS this loads reliably when the runner user is logged in at the console; on a purely headless SSH box where that user is not at the console, run \`{{cli}} runner serve\` under a process supervisor instead.
50287
+ - **Always-on host:** \`{{cli}} runner install-service --name <runner-name> --execute\` installs a background service (macOS launchd user agent) that keeps the runner online. Manage it with \`{{cli}} runner start|stop|restart|status|logs|uninstall --name <runner-name>\`. On macOS the CLI tries both \`user/<uid>\` and \`gui/<uid>\` launchd domains because session behavior differs across console and SSH contexts; \`runner status --name <runner-name>\` prints the loaded domain.
50264
50288
  - **Manual smoke:** \`{{cli}} runner serve --once\` heartbeats once and exits; \`{{cli}} runner serve --execute\` claims and executes runner work orders while it runs.
50265
50289
 
50266
50290
  Two ways to authenticate and pair \u2014 both first-class, an agent can drive either entirely from the terminal:
50267
50291
 
50268
50292
  - **CLI-native (agent):** \`{{cli}} login --device\` prints a code for one human OAuth approval; after that the session is stored locally and the agent runs unattended ("approve once, then autonomous"). Then \`{{cli}} runner serve\`/\`install-service\` pairs against your active company \u2014 an authenticated CLI can also self-issue a pairing code with \`runner.pairing.start\` and claim it.
50269
- - **Web-guided (human):** issue a pairing code from Settings > Runners > Add a Runner, then on the host run \`{{cli}} runner serve --user-code <code>\`. The host stores only the runner bearer secret in an owner-only file and never logs in.
50293
+ - **Web-guided (human):** issue a pairing code from Settings > Runners > Add a Runner, then on the host run \`{{cli}} runner serve --user-code <code>\`. An explicit user code always claims that code, even if the host has an older local runner state file, so the browser and terminal do not accidentally point at different runner ids. The host stores only the runner bearer secret in an owner-only file and never logs in.
50270
50294
 
50271
50295
  Headless and service accounts are supported first-class: when no usable OS keychain exists (for example a service or SSH account), the CLI stores the session in an owner-only file (directory 0700, file 0600) automatically \u2014 no development flag. That file holds only the CLI session token, never tenant secrets, API keys, or vault refs.
50272
50296
 
@@ -52216,6 +52240,7 @@ var COMMAND_MANIFEST = [
52216
52240
  { "id": "software_factory.github.installation.bind", "namespace": "software_factory", "action": "github.installation.bind", "title": "Bind a Forge GitHub installation", "description": "Bind a signed GitHub App installation and selected repositories to Forge projects. Owner-only and human-gated; webhook payloads never supply tenant authority.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "app_slug", "flag": "app-slug", "type": "string", "required": false }, { "name": "installation_id", "flag": "installation-id", "type": "integer", "required": true }, { "name": "account_login", "flag": "account-login", "type": "string", "required": true }, { "name": "account_id", "flag": "account-id", "type": "integer", "required": true }, { "name": "account_type", "flag": "account-type", "type": "string", "required": true }], "hasComplexInput": true, "help": "Bind a GitHub App installation and selected repositories to Forge projects. Owner-only and human-gated." },
52217
52241
  { "id": "software_factory.github.policy.upsert", "namespace": "software_factory", "action": "github.policy.upsert", "title": "Set a Forge GitHub automation policy", "description": "Set the server-side policy for GitHub actions and automation-mode ceilings. Owner-only and human-gated; production deploy remains human-gated.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": true }, { "name": "authority_profile_id", "flag": "authority-profile-id", "type": "string", "required": false }, { "name": "automation_mode_ceiling", "flag": "automation-mode-ceiling", "type": "enum", "required": false, "enumValues": ["plan_only", "pr_only", "merge_after_checks", "deploy_preview", "deploy_production"] }, { "name": "allowed_actions", "flag": "allowed-actions", "type": "array", "required": true, "itemType": "string" }, { "name": "requires_human_for", "flag": "requires-human-for", "type": "array", "required": true, "itemType": "string" }], "hasComplexInput": false, "help": "Set Forge's server-side GitHub automation policy for actions, mode ceilings, and human-gated operations." },
52218
52242
  { "id": "software_factory.phase.advance", "namespace": "software_factory", "action": "phase.advance", "title": "Advance a Forge build request phase", "description": "Advance a build request to the next phase, enforcing the closed phase state machine server-side (an illegal transition is rejected). Writes a phase-run row and updates the current phase. Entitlement-gated.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "build_request_id", "flag": "build-request-id", "type": "string", "required": true }, { "name": "to_phase", "flag": "to-phase", "type": "enum", "required": true, "enumValues": ["intake", "discovery_scoping", "plan_review", "implementation", "plan_conformance_review", "security_review", "qa_verification", "release_preview", "merge_or_deploy_gate", "closeout"] }, { "name": "owning_seat_id", "flag": "owning-seat-id", "type": "string", "required": false }, { "name": "note", "flag": "note", "type": "string", "required": false }], "hasComplexInput": false, "help": "Advance a build request to its next phase; the server enforces the closed phase state machine and rejects an illegal transition." },
52243
+ { "id": "software_factory.project.list", "namespace": "software_factory", "action": "project.list", "title": "List Forge projects", "description": "List active Forge software projects so a tenant can choose a project for build requests, GitHub repository bindings, and config. Read-only and entitlement-gated.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "List active Forge software projects so a tenant can choose a project for build requests, GitHub repository bindings, and config. Read-only; requires the Forge add-on." },
52219
52244
  { "id": "software_factory.request.create", "namespace": "software_factory", "action": "request.create", "title": "Create a Forge build request", "description": "Open a governed Forge build request against a connected software project. Records the request plus its initial intake phase run. Entitlement-gated (Forge add-on); the title is untrusted display text, never an instruction.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": true }, { "name": "title", "flag": "title", "type": "string", "required": true }, { "name": "source_channel", "flag": "source-channel", "type": "enum", "required": false, "enumValues": ["ui", "cli", "github_issue", "linear", "api"] }, { "name": "source_ref", "flag": "source-ref", "type": "string", "required": false }], "hasComplexInput": true, "help": "Open a governed Forge build request against a connected software project; the title is untrusted display text. Requires the Forge add-on." },
52220
52245
  { "id": "software_factory.request.list", "namespace": "software_factory", "action": "request.list", "title": "List Forge build requests", "description": "The Forge control-room board: the tenant's build requests with status, current phase, and risk/automation mode. Read-only, entitlement-gated.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "limit", "flag": "limit", "type": "integer", "required": false }], "hasComplexInput": false, "help": "List Forge build requests (the control-room board) with status, current phase, and risk. Read-only; requires the Forge add-on." },
52221
52246
  { "id": "software_factory.request.show", "namespace": "software_factory", "action": "request.show", "title": "Show a Forge build request", "description": "The Forge request detail: the build request, its current phase, phase-run history, and gates. Read-only, entitlement-gated, tenant-scoped.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "build_request_id", "flag": "build-request-id", "type": "string", "required": true }], "hasComplexInput": false, "help": "Show a Forge build request's detail: current phase, phase-run history, and gates. Read-only; requires the Forge add-on." },
@@ -54906,11 +54931,13 @@ async function pairIfNeeded(client, fetchImpl, appUrl2, config2, io) {
54906
54931
  config2.expectedTenantId = expectedTenantId;
54907
54932
  }
54908
54933
  }
54909
- const existing = await loadState(config2.stateFile, config2.expectedTenantId);
54910
- if (existing) {
54911
- io.stdout.write(`reusing paired runner ${existing.runner_id}
54934
+ if (config2.userCode === void 0) {
54935
+ const existing = await loadState(config2.stateFile, config2.expectedTenantId);
54936
+ if (existing) {
54937
+ io.stdout.write(`reusing paired runner ${existing.runner_id}
54912
54938
  `);
54913
- return existing;
54939
+ return existing;
54940
+ }
54914
54941
  }
54915
54942
  let userCode;
54916
54943
  if (config2.userCode !== void 0) {
@@ -55265,7 +55292,12 @@ async function installService(options) {
55265
55292
  await writeFile3(paths.launchAgentPath, plist, { mode: 420 });
55266
55293
  await bootoutQuietly(options.exec, options.uid, paths.launchAgentPath);
55267
55294
  try {
55268
- await options.exec("launchctl", ["bootstrap", domainTarget(options.uid), paths.launchAgentPath]);
55295
+ const bootstrapped = await bootstrapService(options.exec, options.uid, paths.launchAgentPath);
55296
+ options.io.stdout.write(
55297
+ bootstrapped.alreadyLoaded ? `Runner service was already loaded in launchd domain ${bootstrapped.domain}.
55298
+ ` : `Loaded runner service in launchd domain ${bootstrapped.domain}.
55299
+ `
55300
+ );
55269
55301
  } catch (error51) {
55270
55302
  options.io.stderr.write(
55271
55303
  `Failed to load the runner service: ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}
@@ -55300,15 +55332,13 @@ async function startService(options) {
55300
55332
  );
55301
55333
  return 1;
55302
55334
  }
55335
+ let bootstrapped;
55303
55336
  try {
55304
- await options.exec("launchctl", ["bootstrap", domainTarget(options.uid), paths.launchAgentPath]);
55305
- options.io.stdout.write(`Started runner service ${label}.
55306
- `);
55307
- return 0;
55308
- } catch (bootstrapError) {
55337
+ bootstrapped = await bootstrapService(options.exec, options.uid, paths.launchAgentPath);
55338
+ } catch {
55309
55339
  try {
55310
- await options.exec("launchctl", ["kickstart", "-k", serviceTarget(options.uid, label)]);
55311
- options.io.stdout.write(`Runner service ${label} was already loaded; restarted it.
55340
+ const target2 = await kickstartService(options.exec, options.uid, label);
55341
+ options.io.stdout.write(`Runner service ${label} was already loaded at ${target2}; restarted it.
55312
55342
  `);
55313
55343
  return 0;
55314
55344
  } catch (kickstartError) {
@@ -55319,6 +55349,24 @@ async function startService(options) {
55319
55349
  return 1;
55320
55350
  }
55321
55351
  }
55352
+ if (!bootstrapped.alreadyLoaded) {
55353
+ options.io.stdout.write(`Started runner service ${label} in launchd domain ${bootstrapped.domain}.
55354
+ `);
55355
+ return 0;
55356
+ }
55357
+ const target = `${bootstrapped.domain}/${label}`;
55358
+ try {
55359
+ await kickstartServiceTarget(options.exec, target);
55360
+ options.io.stdout.write(`Runner service ${label} was already loaded at ${target}; restarted it.
55361
+ `);
55362
+ return 0;
55363
+ } catch (kickstartError) {
55364
+ options.io.stderr.write(
55365
+ `Failed to start runner service ${label}: ${redactForLog(kickstartError instanceof Error ? kickstartError.message : String(kickstartError))}
55366
+ `
55367
+ );
55368
+ return 1;
55369
+ }
55322
55370
  }
55323
55371
  async function stopService(options) {
55324
55372
  if (options.platform !== "darwin") {
@@ -55327,11 +55375,11 @@ async function stopService(options) {
55327
55375
  const safeName = safeRunnerName(options.runnerName);
55328
55376
  const paths = runnerServicePaths(options.homeDir, safeName);
55329
55377
  const label = serviceLabel(safeName);
55330
- try {
55331
- await options.exec("launchctl", ["bootout", domainTarget(options.uid), paths.launchAgentPath]);
55378
+ const stopped = await bootoutAllDomains(options.exec, options.uid, paths.launchAgentPath);
55379
+ if (stopped) {
55332
55380
  options.io.stdout.write(`Stopped runner service ${label}.
55333
55381
  `);
55334
- } catch {
55382
+ } else {
55335
55383
  options.io.stdout.write(`Runner service ${label} was not running.
55336
55384
  `);
55337
55385
  }
@@ -55344,8 +55392,8 @@ async function restartService(options) {
55344
55392
  const safeName = safeRunnerName(options.runnerName);
55345
55393
  const label = serviceLabel(safeName);
55346
55394
  try {
55347
- await options.exec("launchctl", ["kickstart", "-k", serviceTarget(options.uid, label)]);
55348
- options.io.stdout.write(`Restarted runner service ${label}.
55395
+ const target = await kickstartService(options.exec, options.uid, label);
55396
+ options.io.stdout.write(`Restarted runner service ${label} at ${target}.
55349
55397
  `);
55350
55398
  return 0;
55351
55399
  } catch (error51) {
@@ -55365,13 +55413,14 @@ async function statusService(options) {
55365
55413
  const label = serviceLabel(safeName);
55366
55414
  const installed = await fileExists(paths.launchAgentPath);
55367
55415
  try {
55368
- const { stdout } = await options.exec("launchctl", ["print", serviceTarget(options.uid, label)]);
55369
- const state = extractLaunchdState(stdout);
55416
+ const printed = await printService(options.exec, options.uid, label);
55417
+ const state = extractLaunchdState(printed.stdout);
55370
55418
  options.io.stdout.write(
55371
55419
  `${[
55372
55420
  `runner service: ${label}`,
55373
55421
  `installed: ${installed ? "yes" : "no"}`,
55374
55422
  `loaded: yes`,
55423
+ `domain: ${printed.target}`,
55375
55424
  `state: ${state ?? "unknown"}`,
55376
55425
  `plist: ${paths.launchAgentPath}`,
55377
55426
  `logs: ${paths.logDir}`
@@ -55489,17 +55538,72 @@ function safeRunnerName(runnerName) {
55489
55538
  function serviceLabel(safeName) {
55490
55539
  return `com.${cliBrand.binName}.runner.${safeName}`;
55491
55540
  }
55492
- function domainTarget(uid) {
55493
- return `user/${uid}`;
55541
+ function launchdDomains(uid) {
55542
+ return [`user/${uid}`, `gui/${uid}`];
55494
55543
  }
55495
- function serviceTarget(uid, label) {
55496
- return `user/${uid}/${label}`;
55544
+ function serviceTargets(uid, label) {
55545
+ return launchdDomains(uid).map((domain2) => `${domain2}/${label}`);
55497
55546
  }
55498
55547
  async function bootoutQuietly(exec, uid, plistPath) {
55499
- try {
55500
- await exec("launchctl", ["bootout", domainTarget(uid), plistPath]);
55501
- } catch {
55548
+ await bootoutAllDomains(exec, uid, plistPath);
55549
+ }
55550
+ async function bootoutAllDomains(exec, uid, plistPath) {
55551
+ let bootedOut = false;
55552
+ for (const domain2 of launchdDomains(uid)) {
55553
+ try {
55554
+ await exec("launchctl", ["bootout", domain2, plistPath]);
55555
+ bootedOut = true;
55556
+ } catch {
55557
+ }
55558
+ }
55559
+ return bootedOut;
55560
+ }
55561
+ async function bootstrapService(exec, uid, plistPath) {
55562
+ const errors = [];
55563
+ for (const domain2 of launchdDomains(uid)) {
55564
+ try {
55565
+ await exec("launchctl", ["bootstrap", domain2, plistPath]);
55566
+ return { domain: domain2, alreadyLoaded: false };
55567
+ } catch (error51) {
55568
+ const message = error51 instanceof Error ? error51.message : String(error51);
55569
+ if (isLaunchdAlreadyLoadedError(message)) {
55570
+ return { domain: domain2, alreadyLoaded: true };
55571
+ }
55572
+ errors.push(`${domain2}: ${message}`);
55573
+ }
55574
+ }
55575
+ throw new Error(errors.join("; "));
55576
+ }
55577
+ function isLaunchdAlreadyLoadedError(message) {
55578
+ const normalized = message.toLowerCase();
55579
+ return normalized.includes("service is already loaded") || normalized.includes("service already loaded") || normalized.includes("already bootstrapped") || normalized.includes("already exists");
55580
+ }
55581
+ async function kickstartService(exec, uid, label) {
55582
+ const errors = [];
55583
+ for (const target of serviceTargets(uid, label)) {
55584
+ try {
55585
+ await kickstartServiceTarget(exec, target);
55586
+ return target;
55587
+ } catch (error51) {
55588
+ errors.push(`${target}: ${error51 instanceof Error ? error51.message : String(error51)}`);
55589
+ }
55590
+ }
55591
+ throw new Error(errors.join("; "));
55592
+ }
55593
+ async function kickstartServiceTarget(exec, target) {
55594
+ await exec("launchctl", ["kickstart", "-k", target]);
55595
+ }
55596
+ async function printService(exec, uid, label) {
55597
+ const errors = [];
55598
+ for (const target of serviceTargets(uid, label)) {
55599
+ try {
55600
+ const { stdout } = await exec("launchctl", ["print", target]);
55601
+ return { target, stdout };
55602
+ } catch (error51) {
55603
+ errors.push(`${target}: ${error51 instanceof Error ? error51.message : String(error51)}`);
55604
+ }
55502
55605
  }
55606
+ throw new Error(errors.join("; "));
55503
55607
  }
55504
55608
  function unsupportedPlatform(io, platform2) {
55505
55609
  io.stderr.write(