@rosthq/cli 0.7.10 → 0.7.12

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.
package/dist/index.js CHANGED
@@ -47455,7 +47455,7 @@ The Compass is drafted, then activated by a human through supersession.
47455
47455
  order: 15,
47456
47456
  title: "AICOS chat guide",
47457
47457
  summary: "How the AI Chief of Staff chat works in the authenticated app shell and what it can safely do today.",
47458
- version: "2026-07-03.2",
47458
+ version: "2026-07-03.5",
47459
47459
  public: true,
47460
47460
  audiences: ["human", "in_app_agent"],
47461
47461
  stages: ["company_setup", "operating_rhythm"],
@@ -47499,9 +47499,9 @@ You can reopen prior threads, search thread titles and summaries, or start a new
47499
47499
 
47500
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
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 an active agent occupancy. It is idempotent; running it again repairs missing pieces instead of creating a second AICOS.
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 live 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 promotes that canonical stock agent to live for Runner readiness, and ends the extra AICOS occupancies so Settings, lane readiness, and chat resolve the same governed agent.
47503
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.
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 AICOS seat has its own operations panel instead of the normal **Staff this seat** setup tabs, because AICOS is the company's governed AI Chief of Staff foundation rather than a generic agent to restaff from stock, custom design, or runner pairing controls. Use that panel to reach AICOS Settings, its Charter, runner readiness, MCP token management, and Trust Card run evidence.
47505
47505
 
47506
47506
  ## What context AICOS receives
47507
47507
 
@@ -48330,7 +48330,7 @@ External connectors are being rolled out provider by provider, conservatively (r
48330
48330
  order: 48,
48331
48331
  title: "CLI and MCP installation guide",
48332
48332
  summary: "Install the public CLI, register remote token-backed MCP clients, and find the full command and tool catalog.",
48333
- version: "2026-07-03.9",
48333
+ version: "2026-07-03.12",
48334
48334
  public: true,
48335
48335
  audiences: ["human", "cli", "mcp", "in_app_agent"],
48336
48336
  stages: ["company_setup", "staffing"],
@@ -48429,6 +48429,8 @@ Use the public CLI when a technical operator, Claude Code, Codex, Cursor, or ano
48429
48429
 
48430
48430
  The MCP server is remote and token-backed. There is no local MCP daemon to install. The CLI logs you in, mints a scoped MCP token, and prints the registration block for the deployed \`/mcp\` route.
48431
48431
 
48432
+ AICOS MCP mode is deliberately seat-scoped. In Settings, the AICOS panel can create a token for the canonical AI Chief of Staff seat and then marks MCP selectable only while a non-revoked, unexpired AICOS seat token exists. Do not use a tenant-admin MCP token to staff or test AICOS; it should operate as its own governed seat, with the web panel remaining the readiness monitor and transcript surface.
48433
+
48432
48434
  ## Prerequisite: a human approver in a browser
48433
48435
 
48434
48436
  A headless agent cannot complete setup unattended. Two things always require a human, so plan to announce the handoff rather than stall silently:
@@ -49058,17 +49060,20 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
49058
49060
  | \`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"}\`. |
49059
49061
  | \`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"}\`. |
49060
49062
  | \`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. |
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"}}]}\`. |
49062
49063
  | \`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"]}\`. |
49063
49064
  | \`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"}\`. |
49064
49065
  | \`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>"}\`. |
49065
49066
 
49067
+ 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.
49068
+
49066
49069
  #### Forge GitHub App access
49067
49070
 
49068
49071
  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.
49069
49072
 
49070
49073
  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.
49071
49074
 
49075
+ Tenant GitHub App installation binding is intentionally not a CLI or MCP tool. Start at \`/forge/settings/github\`: the settings flow preserves the signed GitHub setup state, performs user-scoped GitHub App authorization, verifies admin repositories server-side, and then submits the governed bind command from the UI.
49076
+
49072
49077
  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.
49073
49078
 
49074
49079
  ### Seat-scoped operating tools
@@ -49410,7 +49415,7 @@ Set \`ROST_SKILL_INSTALL_ROOT\` only for a sandbox or CI override. Local copies
49410
49415
  order: 55,
49411
49416
  title: "Agent reference map",
49412
49417
  summary: "Where CLI sessions, MCP clients, and in-app agents should retrieve {{brand}} guidance before recommending setup changes.",
49413
- version: "2026-07-03.1",
49418
+ version: "2026-06-27.2",
49414
49419
  public: true,
49415
49420
  audiences: ["cli", "mcp", "in_app_agent"],
49416
49421
  stages: ["company_setup", "graph_design", "charter_design", "staffing", "operating_rhythm"],
@@ -49907,7 +49912,7 @@ Keep agent scope tight at first. Approve more autonomy only after evidence. Use
49907
49912
  order: 71,
49908
49913
  title: "Confirmations and human gates guide",
49909
49914
  summary: "How {{brand}} routes authority-changing work through human confirmation, and why agents never approve their own requests.",
49910
- version: "2026-06-27.2",
49915
+ version: "2026-07-03.1",
49911
49916
  public: true,
49912
49917
  audiences: ["human", "cli", "mcp", "in_app_agent"],
49913
49918
  stages: ["graph_design", "charter_design", "staffing", "operating_rhythm"],
@@ -49944,6 +49949,8 @@ A gated command does not mutate when an agent calls it. It returns a pending con
49944
49949
 
49945
49950
  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.
49946
49951
 
49952
+ 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.
49953
+
49947
49954
  \`\`\`bash
49948
49955
  # Human approves a pending confirmation
49949
49956
  {{cli}} command confirmation.approve --json '{"confirmation_id":"<confirmation-id>"}'
@@ -50169,7 +50176,7 @@ Agents may explain which setting is needed and why. They should not ask users to
50169
50176
  order: 73,
50170
50177
  title: "Members and invites guide",
50171
50178
  summary: "How to invite humans into the company without confusing user access with seat accountability.",
50172
- version: "2026-07-03.1",
50179
+ version: "2026-07-03.2",
50173
50180
  public: true,
50174
50181
  audiences: ["human", "cli", "mcp", "in_app_agent"],
50175
50182
  stages: ["company_setup", "staffing"],
@@ -50248,7 +50255,7 @@ Every notification should include the seat, cause, evidence, and requested decis
50248
50255
  order: 75,
50249
50256
  title: "Local runner guide",
50250
50257
  summary: "How local agent sessions and runner surfaces should operate through {{brand}} without bypassing Charters or audit.",
50251
- version: "2026-07-03.1",
50258
+ version: "2026-07-03.2",
50252
50259
  public: true,
50253
50260
  audiences: ["human", "cli", "mcp", "in_app_agent"],
50254
50261
  stages: ["staffing", "operating_rhythm"],
@@ -50280,13 +50287,13 @@ Use Local Runner when a Seat should run on a machine you control. Install it fro
50280
50287
 
50281
50288
  Recommended paths:
50282
50289
 
50283
- - **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.
50290
+ - **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.
50284
50291
  - **Manual smoke:** \`{{cli}} runner serve --once\` heartbeats once and exits; \`{{cli}} runner serve --execute\` claims and executes runner work orders while it runs.
50285
50292
 
50286
50293
  Two ways to authenticate and pair \u2014 both first-class, an agent can drive either entirely from the terminal:
50287
50294
 
50288
50295
  - **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.
50289
- - **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.
50296
+ - **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.
50290
50297
 
50291
50298
  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.
50292
50299
 
@@ -52233,7 +52240,7 @@ var COMMAND_MANIFEST = [
52233
52240
  { "id": "software_factory.developer_team.install", "namespace": "software_factory", "action": "developer_team.install", "title": "Install Forge Developer Team", "description": "Install the governed Forge Developer Team template: seats, draft Charters, first-party skill assignments, and authority presets. Entitlement-gated and human-confirmed.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "steward_seat_id", "flag": "steward-seat-id", "type": "string", "required": true }, { "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": false }, { "name": "assign_skills", "flag": "assign-skills", "type": "boolean", "required": false }], "hasComplexInput": false, "help": "Install the governed Forge Developer Team template with seats, draft Charters, skills, project authority grants, and audit records. Tenant-admin and human-gated; creates no live agent occupancy." },
52234
52241
  { "id": "software_factory.gate.decide", "namespace": "software_factory", "action": "gate.decide", "title": "Decide a Forge gate", "description": "A human approves or rejects a Forge gate. The resolver is recorded (never an agent \u2014 invariant #7); sensitive gates (security sign-off, merge/deploy, authority change, production deploy) also write a linked human decision. Requires confirmation.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "gate_id", "flag": "gate-id", "type": "string", "required": true }, { "name": "decision", "flag": "decision", "type": "enum", "required": true, "enumValues": ["approve", "reject"] }, { "name": "rationale", "flag": "rationale", "type": "string", "required": false }], "hasComplexInput": false, "help": "A human approves or rejects a Forge gate; an agent caller routes to /approvals and can never self-approve. Sensitive gates record a human decision." },
52235
52242
  { "id": "software_factory.github.installation_token.create", "namespace": "software_factory", "action": "github.installation_token.create", "title": "Create a Forge GitHub installation token", "description": "Mint a short-lived GitHub App installation token for one bound repository after server-side policy evaluation. The token is returned once and never persisted.", "requiredScope": "tenant", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": true }, { "name": "repository_id", "flag": "repository-id", "type": "integer", "required": true }, { "name": "requested_actions", "flag": "requested-actions", "type": "array", "required": true, "itemType": "string" }, { "name": "requested_automation_mode", "flag": "requested-automation-mode", "type": "enum", "required": false, "enumValues": ["plan_only", "pr_only", "merge_after_checks", "deploy_preview", "deploy_production"] }, { "name": "seat_id", "flag": "seat-id", "type": "string", "required": false }], "hasComplexInput": false, "help": "Mint a short-lived GitHub App installation token for one bound repository after policy evaluation; token material is returned once and never stored." },
52236
- { "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." },
52243
+ { "id": "software_factory.github.installation.bind", "namespace": "software_factory", "action": "github.installation.bind", "title": "Bind a Forge GitHub installation", "description": "Bind a GitHub App installation and selected repositories to Forge projects from the authenticated Forge GitHub settings flow. Owner-only and human-gated; webhook payloads never supply tenant authority.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": false, "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 from the authenticated Forge GitHub settings flow. Owner-only and human-gated." },
52237
52244
  { "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." },
52238
52245
  { "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." },
52239
52246
  { "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." },
@@ -54927,11 +54934,13 @@ async function pairIfNeeded(client, fetchImpl, appUrl2, config2, io) {
54927
54934
  config2.expectedTenantId = expectedTenantId;
54928
54935
  }
54929
54936
  }
54930
- const existing = await loadState(config2.stateFile, config2.expectedTenantId);
54931
- if (existing) {
54932
- io.stdout.write(`reusing paired runner ${existing.runner_id}
54937
+ if (config2.userCode === void 0) {
54938
+ const existing = await loadState(config2.stateFile, config2.expectedTenantId);
54939
+ if (existing) {
54940
+ io.stdout.write(`reusing paired runner ${existing.runner_id}
54933
54941
  `);
54934
- return existing;
54942
+ return existing;
54943
+ }
54935
54944
  }
54936
54945
  let userCode;
54937
54946
  if (config2.userCode !== void 0) {
@@ -55286,7 +55295,12 @@ async function installService(options) {
55286
55295
  await writeFile3(paths.launchAgentPath, plist, { mode: 420 });
55287
55296
  await bootoutQuietly(options.exec, options.uid, paths.launchAgentPath);
55288
55297
  try {
55289
- await options.exec("launchctl", ["bootstrap", domainTarget(options.uid), paths.launchAgentPath]);
55298
+ const bootstrapped = await bootstrapService(options.exec, options.uid, paths.launchAgentPath);
55299
+ options.io.stdout.write(
55300
+ bootstrapped.alreadyLoaded ? `Runner service was already loaded in launchd domain ${bootstrapped.domain}.
55301
+ ` : `Loaded runner service in launchd domain ${bootstrapped.domain}.
55302
+ `
55303
+ );
55290
55304
  } catch (error51) {
55291
55305
  options.io.stderr.write(
55292
55306
  `Failed to load the runner service: ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}
@@ -55321,15 +55335,13 @@ async function startService(options) {
55321
55335
  );
55322
55336
  return 1;
55323
55337
  }
55338
+ let bootstrapped;
55324
55339
  try {
55325
- await options.exec("launchctl", ["bootstrap", domainTarget(options.uid), paths.launchAgentPath]);
55326
- options.io.stdout.write(`Started runner service ${label}.
55327
- `);
55328
- return 0;
55329
- } catch (bootstrapError) {
55340
+ bootstrapped = await bootstrapService(options.exec, options.uid, paths.launchAgentPath);
55341
+ } catch {
55330
55342
  try {
55331
- await options.exec("launchctl", ["kickstart", "-k", serviceTarget(options.uid, label)]);
55332
- options.io.stdout.write(`Runner service ${label} was already loaded; restarted it.
55343
+ const target2 = await kickstartService(options.exec, options.uid, label);
55344
+ options.io.stdout.write(`Runner service ${label} was already loaded at ${target2}; restarted it.
55333
55345
  `);
55334
55346
  return 0;
55335
55347
  } catch (kickstartError) {
@@ -55340,6 +55352,24 @@ async function startService(options) {
55340
55352
  return 1;
55341
55353
  }
55342
55354
  }
55355
+ if (!bootstrapped.alreadyLoaded) {
55356
+ options.io.stdout.write(`Started runner service ${label} in launchd domain ${bootstrapped.domain}.
55357
+ `);
55358
+ return 0;
55359
+ }
55360
+ const target = `${bootstrapped.domain}/${label}`;
55361
+ try {
55362
+ await kickstartServiceTarget(options.exec, target);
55363
+ options.io.stdout.write(`Runner service ${label} was already loaded at ${target}; restarted it.
55364
+ `);
55365
+ return 0;
55366
+ } catch (kickstartError) {
55367
+ options.io.stderr.write(
55368
+ `Failed to start runner service ${label}: ${redactForLog(kickstartError instanceof Error ? kickstartError.message : String(kickstartError))}
55369
+ `
55370
+ );
55371
+ return 1;
55372
+ }
55343
55373
  }
55344
55374
  async function stopService(options) {
55345
55375
  if (options.platform !== "darwin") {
@@ -55348,11 +55378,11 @@ async function stopService(options) {
55348
55378
  const safeName = safeRunnerName(options.runnerName);
55349
55379
  const paths = runnerServicePaths(options.homeDir, safeName);
55350
55380
  const label = serviceLabel(safeName);
55351
- try {
55352
- await options.exec("launchctl", ["bootout", domainTarget(options.uid), paths.launchAgentPath]);
55381
+ const stopped = await bootoutAllDomains(options.exec, options.uid, paths.launchAgentPath);
55382
+ if (stopped) {
55353
55383
  options.io.stdout.write(`Stopped runner service ${label}.
55354
55384
  `);
55355
- } catch {
55385
+ } else {
55356
55386
  options.io.stdout.write(`Runner service ${label} was not running.
55357
55387
  `);
55358
55388
  }
@@ -55365,8 +55395,8 @@ async function restartService(options) {
55365
55395
  const safeName = safeRunnerName(options.runnerName);
55366
55396
  const label = serviceLabel(safeName);
55367
55397
  try {
55368
- await options.exec("launchctl", ["kickstart", "-k", serviceTarget(options.uid, label)]);
55369
- options.io.stdout.write(`Restarted runner service ${label}.
55398
+ const target = await kickstartService(options.exec, options.uid, label);
55399
+ options.io.stdout.write(`Restarted runner service ${label} at ${target}.
55370
55400
  `);
55371
55401
  return 0;
55372
55402
  } catch (error51) {
@@ -55386,13 +55416,14 @@ async function statusService(options) {
55386
55416
  const label = serviceLabel(safeName);
55387
55417
  const installed = await fileExists(paths.launchAgentPath);
55388
55418
  try {
55389
- const { stdout } = await options.exec("launchctl", ["print", serviceTarget(options.uid, label)]);
55390
- const state = extractLaunchdState(stdout);
55419
+ const printed = await printService(options.exec, options.uid, label);
55420
+ const state = extractLaunchdState(printed.stdout);
55391
55421
  options.io.stdout.write(
55392
55422
  `${[
55393
55423
  `runner service: ${label}`,
55394
55424
  `installed: ${installed ? "yes" : "no"}`,
55395
55425
  `loaded: yes`,
55426
+ `domain: ${printed.target}`,
55396
55427
  `state: ${state ?? "unknown"}`,
55397
55428
  `plist: ${paths.launchAgentPath}`,
55398
55429
  `logs: ${paths.logDir}`
@@ -55510,17 +55541,72 @@ function safeRunnerName(runnerName) {
55510
55541
  function serviceLabel(safeName) {
55511
55542
  return `com.${cliBrand.binName}.runner.${safeName}`;
55512
55543
  }
55513
- function domainTarget(uid) {
55514
- return `user/${uid}`;
55544
+ function launchdDomains(uid) {
55545
+ return [`user/${uid}`, `gui/${uid}`];
55515
55546
  }
55516
- function serviceTarget(uid, label) {
55517
- return `user/${uid}/${label}`;
55547
+ function serviceTargets(uid, label) {
55548
+ return launchdDomains(uid).map((domain2) => `${domain2}/${label}`);
55518
55549
  }
55519
55550
  async function bootoutQuietly(exec, uid, plistPath) {
55520
- try {
55521
- await exec("launchctl", ["bootout", domainTarget(uid), plistPath]);
55522
- } catch {
55551
+ await bootoutAllDomains(exec, uid, plistPath);
55552
+ }
55553
+ async function bootoutAllDomains(exec, uid, plistPath) {
55554
+ let bootedOut = false;
55555
+ for (const domain2 of launchdDomains(uid)) {
55556
+ try {
55557
+ await exec("launchctl", ["bootout", domain2, plistPath]);
55558
+ bootedOut = true;
55559
+ } catch {
55560
+ }
55561
+ }
55562
+ return bootedOut;
55563
+ }
55564
+ async function bootstrapService(exec, uid, plistPath) {
55565
+ const errors = [];
55566
+ for (const domain2 of launchdDomains(uid)) {
55567
+ try {
55568
+ await exec("launchctl", ["bootstrap", domain2, plistPath]);
55569
+ return { domain: domain2, alreadyLoaded: false };
55570
+ } catch (error51) {
55571
+ const message = error51 instanceof Error ? error51.message : String(error51);
55572
+ if (isLaunchdAlreadyLoadedError(message)) {
55573
+ return { domain: domain2, alreadyLoaded: true };
55574
+ }
55575
+ errors.push(`${domain2}: ${message}`);
55576
+ }
55577
+ }
55578
+ throw new Error(errors.join("; "));
55579
+ }
55580
+ function isLaunchdAlreadyLoadedError(message) {
55581
+ const normalized = message.toLowerCase();
55582
+ return normalized.includes("service is already loaded") || normalized.includes("service already loaded") || normalized.includes("already bootstrapped") || normalized.includes("already exists");
55583
+ }
55584
+ async function kickstartService(exec, uid, label) {
55585
+ const errors = [];
55586
+ for (const target of serviceTargets(uid, label)) {
55587
+ try {
55588
+ await kickstartServiceTarget(exec, target);
55589
+ return target;
55590
+ } catch (error51) {
55591
+ errors.push(`${target}: ${error51 instanceof Error ? error51.message : String(error51)}`);
55592
+ }
55593
+ }
55594
+ throw new Error(errors.join("; "));
55595
+ }
55596
+ async function kickstartServiceTarget(exec, target) {
55597
+ await exec("launchctl", ["kickstart", "-k", target]);
55598
+ }
55599
+ async function printService(exec, uid, label) {
55600
+ const errors = [];
55601
+ for (const target of serviceTargets(uid, label)) {
55602
+ try {
55603
+ const { stdout } = await exec("launchctl", ["print", target]);
55604
+ return { target, stdout };
55605
+ } catch (error51) {
55606
+ errors.push(`${target}: ${error51 instanceof Error ? error51.message : String(error51)}`);
55607
+ }
55523
55608
  }
55609
+ throw new Error(errors.join("; "));
55524
55610
  }
55525
55611
  function unsupportedPlatform(io, platform2) {
55526
55612
  io.stderr.write(