@skrr-ai/cli 0.1.49 → 0.1.51

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 (180) hide show
  1. package/README.md +5 -4
  2. package/dist/base-command.js +75 -6
  3. package/dist/commands/agents/chat.d.ts +14 -0
  4. package/dist/commands/agents/chat.js +37 -1
  5. package/dist/commands/agents/create.js +14 -7
  6. package/dist/commands/agents/show.d.ts +9 -0
  7. package/dist/commands/agents/show.js +53 -0
  8. package/dist/commands/agents/update.d.ts +9 -0
  9. package/dist/commands/agents/update.js +38 -5
  10. package/dist/commands/balance/show.d.ts +34 -0
  11. package/dist/commands/balance/show.js +92 -2
  12. package/dist/commands/code/index.d.ts +1 -0
  13. package/dist/commands/code/index.js +49 -5
  14. package/dist/commands/code/run.js +5 -1
  15. package/dist/commands/commitments/cycles.d.ts +19 -0
  16. package/dist/commands/commitments/cycles.js +46 -0
  17. package/dist/commands/commitments/doctor.d.ts +1 -0
  18. package/dist/commands/commitments/doctor.js +19 -4
  19. package/dist/commands/commitments/effective-policy.js +40 -1
  20. package/dist/commands/commitments/explain.d.ts +17 -0
  21. package/dist/commands/commitments/explain.js +40 -0
  22. package/dist/commands/commitments/next-tick.js +3 -1
  23. package/dist/commands/commitments/preflight.js +10 -0
  24. package/dist/commands/commitments/show.js +11 -1
  25. package/dist/commands/daemon/config.d.ts +15 -0
  26. package/dist/commands/daemon/config.js +38 -0
  27. package/dist/commands/daemon/index.js +1 -0
  28. package/dist/commands/daemon/status.js +15 -1
  29. package/dist/commands/goals/create.d.ts +1 -0
  30. package/dist/commands/goals/create.js +22 -0
  31. package/dist/commands/harnesses/models.d.ts +54 -0
  32. package/dist/commands/harnesses/models.js +100 -7
  33. package/dist/commands/harnesses/show.d.ts +11 -0
  34. package/dist/commands/harnesses/show.js +35 -0
  35. package/dist/commands/harnesses/update.d.ts +28 -1
  36. package/dist/commands/harnesses/update.js +73 -10
  37. package/dist/commands/logout.js +12 -1
  38. package/dist/commands/machines/dedicated/index.js +1 -1
  39. package/dist/commands/machines/dedicated/sign-in.js +4 -1
  40. package/dist/commands/machines/dedicated/terminal/kill.d.ts +21 -0
  41. package/dist/commands/machines/dedicated/terminal/kill.js +55 -0
  42. package/dist/commands/machines/dedicated/terminal/ls.d.ts +22 -0
  43. package/dist/commands/machines/dedicated/terminal/ls.js +70 -0
  44. package/dist/commands/machines/dedicated/terminal/rename.d.ts +23 -0
  45. package/dist/commands/machines/dedicated/terminal/rename.js +64 -0
  46. package/dist/commands/machines/dedicated/terminal.d.ts +9 -0
  47. package/dist/commands/machines/dedicated/terminal.js +27 -2
  48. package/dist/commands/machines/hosted/connect.js +2 -2
  49. package/dist/commands/machines/hosted/destroy.js +1 -1
  50. package/dist/commands/machines/hosted/exec.js +1 -1
  51. package/dist/commands/machines/hosted/list.js +1 -1
  52. package/dist/commands/machines/hosted/pause.js +1 -1
  53. package/dist/commands/machines/hosted/pull.js +1 -1
  54. package/dist/commands/machines/hosted/resume.js +1 -1
  55. package/dist/commands/machines/hosted/start.js +2 -2
  56. package/dist/commands/machines/hosted/status.js +2 -1
  57. package/dist/commands/outbox/list.d.ts +12 -1
  58. package/dist/commands/outbox/list.js +31 -2
  59. package/dist/commands/revoke-daemon.js +9 -0
  60. package/dist/commands/spaces/create.d.ts +1 -0
  61. package/dist/commands/spaces/create.js +17 -0
  62. package/dist/commands/spaces/show.js +5 -0
  63. package/dist/commands/spaces/update.d.ts +1 -0
  64. package/dist/commands/spaces/update.js +16 -0
  65. package/dist/commands/tasks/create.d.ts +1 -0
  66. package/dist/commands/tasks/create.js +6 -0
  67. package/dist/commands/tasks/list.js +1 -0
  68. package/dist/commands/tasks/show.js +45 -0
  69. package/dist/commands/tasks/update.d.ts +1 -0
  70. package/dist/commands/tasks/update.js +22 -0
  71. package/dist/commands/tasks/upsert.d.ts +1 -0
  72. package/dist/commands/tasks/upsert.js +16 -0
  73. package/dist/lib/agent-config.d.ts +2 -0
  74. package/dist/lib/agent-config.js +3 -1
  75. package/dist/lib/agentic-stream.d.ts +68 -1
  76. package/dist/lib/agentic-stream.js +294 -7
  77. package/dist/lib/api-fetch.js +19 -0
  78. package/dist/lib/auth-core-init.d.ts +2 -10
  79. package/dist/lib/auth-core-init.js +20 -67
  80. package/dist/lib/auth-failure.d.ts +33 -0
  81. package/dist/lib/auth-failure.js +65 -0
  82. package/dist/lib/auth-storage.d.ts +14 -0
  83. package/dist/lib/auth-storage.js +14 -0
  84. package/dist/lib/commitments.d.ts +37 -2
  85. package/dist/lib/commitments.js +148 -0
  86. package/dist/lib/daemonBroker.d.ts +134 -31
  87. package/dist/lib/daemonBroker.js +376 -31
  88. package/dist/lib/dedicated-lease-command.d.ts +14 -0
  89. package/dist/lib/dedicated-lease-command.js +30 -1
  90. package/dist/lib/dedicated-machines.js +8 -25
  91. package/dist/lib/dedicated-service-command.d.ts +11 -3
  92. package/dist/lib/dedicated-service-command.js +20 -3
  93. package/dist/lib/dedicated-service.d.ts +46 -0
  94. package/dist/lib/dedicated-service.js +85 -7
  95. package/dist/lib/dedicated-terminal.d.ts +21 -0
  96. package/dist/lib/dedicated-terminal.js +104 -9
  97. package/dist/lib/delegated-cli.js +4 -1
  98. package/dist/lib/exec-runtime-binary.d.ts +3 -1
  99. package/dist/lib/exec-runtime-binary.js +4 -2
  100. package/dist/lib/first-party-harness-broker.d.ts +10 -0
  101. package/dist/lib/first-party-harness-broker.js +13 -1
  102. package/dist/lib/first-party-harness-doctor.js +41 -1
  103. package/dist/lib/first-party-harness-managed.d.ts +4 -3
  104. package/dist/lib/first-party-harness-project-trust.d.ts +125 -0
  105. package/dist/lib/first-party-harness-project-trust.js +364 -0
  106. package/dist/lib/first-party-harness.d.ts +9 -2
  107. package/dist/lib/first-party-harness.js +6 -5
  108. package/dist/lib/harnesses.d.ts +10 -0
  109. package/dist/lib/hosted-machines.d.ts +10 -1
  110. package/dist/lib/hosted-machines.js +36 -2
  111. package/dist/lib/live-runtime-binary.d.ts +40 -0
  112. package/dist/lib/live-runtime-binary.js +193 -0
  113. package/dist/lib/login.d.ts +23 -0
  114. package/dist/lib/login.js +60 -10
  115. package/dist/lib/machine-spend-cap.d.ts +23 -0
  116. package/dist/lib/machine-spend-cap.js +68 -0
  117. package/dist/lib/message-intent.js +3 -1
  118. package/dist/lib/node-adapter.js +15 -3
  119. package/dist/lib/tasks.d.ts +20 -0
  120. package/dist/lib/tasks.js +54 -0
  121. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +90 -0
  122. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +113 -0
  123. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +2 -0
  124. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +26 -1
  125. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.d.ts +47 -0
  126. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.js +69 -0
  127. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +133 -0
  128. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +189 -0
  129. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +41 -3
  130. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
  131. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +25 -3
  132. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.d.ts +32 -0
  133. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.js +268 -0
  134. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.d.ts +2 -2
  135. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +11 -10
  136. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +36 -0
  137. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +156 -0
  138. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.d.ts +8 -3
  139. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.js +9 -4
  140. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +8 -0
  141. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +16 -10
  142. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.d.ts +6 -0
  143. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.js +32 -9
  144. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.d.ts +81 -0
  145. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.js +87 -0
  146. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +22 -0
  147. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +61 -2
  148. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/types.d.ts +6 -1
  149. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +90 -0
  150. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +105 -0
  151. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +2 -0
  152. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +25 -1
  153. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.d.ts +47 -0
  154. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.js +66 -0
  155. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +133 -0
  156. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +182 -0
  157. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +41 -3
  158. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
  159. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -1
  160. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.d.ts +32 -0
  161. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.js +257 -0
  162. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.d.ts +2 -2
  163. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +11 -10
  164. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +36 -0
  165. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +147 -0
  166. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.d.ts +8 -3
  167. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.js +9 -4
  168. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +8 -0
  169. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +16 -10
  170. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.d.ts +6 -0
  171. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.js +31 -9
  172. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.d.ts +81 -0
  173. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.js +80 -0
  174. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +22 -0
  175. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +58 -1
  176. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/types.d.ts +6 -1
  177. package/dist/node_modules/@skrr-ai/auth-core/package.json +61 -1
  178. package/dist/node_modules/@skrr-ai/data-provider/index.js +3418 -3344
  179. package/oclif.manifest.json +24813 -24242
  180. package/package.json +4 -1
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderRuntimeSpend = renderRuntimeSpend;
4
+ exports.renderSessionBillingAccount = renderSessionBillingAccount;
4
5
  exports.renderPlanCeilings = renderPlanCeilings;
5
6
  const core_1 = require("@oclif/core");
6
7
  const auth_core_1 = require("@skrr-ai/auth-core");
@@ -37,6 +38,45 @@ function renderRuntimeSpend(spend, log) {
37
38
  }
38
39
  }
39
40
  }
41
+ /**
42
+ * The account the NEXT session bills against, as lines — printed FIRST (OSK-12122).
43
+ *
44
+ * `skrr balance` opened on the personal account, so a user whose sessions bill
45
+ * the default workspace read `Credits: 999,503,672,326 ($999,503.67)` while every
46
+ * session was refused against that workspace's $0. The screen that exists to
47
+ * answer "why was I refused for credit" answered for the one account the
48
+ * refusal was not about, and nothing on it said which account that was.
49
+ *
50
+ * `balance` absent means the read failed: say so, never print a figure.
51
+ * Pure so the wording is pinned directly, like `renderRuntimeSpend`.
52
+ */
53
+ function renderSessionBillingAccount(target, balance, log, bin = 'skrr') {
54
+ const label = target.name ? `"${target.name}" (${target.workspaceId})` : target.workspaceId;
55
+ const why = target.source === 'selected'
56
+ ? 'the workspace selected for this CLI'
57
+ : 'no workspace is selected, so sessions resolve to your default workspace';
58
+ log(`Next session bills: workspace ${label}`);
59
+ log(` (${why})`);
60
+ if (!balance) {
61
+ log(` Balance: unavailable (\`${bin} balance show --workspace ${target.workspaceId}\` to retry)`);
62
+ return;
63
+ }
64
+ if (!balance.exists) {
65
+ log(' No billing account yet — nothing has ever been funded or spent here.');
66
+ log(' WARNING: a managed session carrying this workspace cannot be funded; it will be refused.');
67
+ }
68
+ else {
69
+ for (const line of (0, balance_1.workspaceBalanceLines)(balance))
70
+ log(line);
71
+ if (balance.availableMicros <= 0) {
72
+ log(' WARNING: nothing available — managed sessions billed here are refused for credit, ' +
73
+ 'whatever your personal balance below says.');
74
+ }
75
+ }
76
+ if (balance.availableMicros <= 0) {
77
+ log(` Add credit to this workspace, or bill another one with \`${bin} workspaces use <workspace>\`.`);
78
+ }
79
+ }
40
80
  /**
41
81
  * Your plan's ceilings, as lines.
42
82
  *
@@ -135,17 +175,42 @@ class BalanceShow extends base_command_1.BaseCommand {
135
175
  catch (err) {
136
176
  this.handleApiError(err);
137
177
  }
178
+ // OSK-12122 — the account the next session bills, resolved the way the
179
+ // session resolves it, read before anything is printed so it can go first.
180
+ const sessionTarget = await this.resolveSessionBillingTarget();
181
+ let sessionBalance = null;
182
+ if (sessionTarget) {
183
+ try {
184
+ sessionBalance = await (0, balance_1.getWorkspaceBalance)(sessionTarget.workspaceId);
185
+ }
186
+ catch {
187
+ sessionBalance = null;
188
+ }
189
+ }
138
190
  if (flags.json) {
139
- this.log(JSON.stringify(balance, null, 2));
191
+ // Additive: the personal balance keeps its shape, and the account a
192
+ // session bills rides beside it (`null` when no workspace resolves).
193
+ this.log(JSON.stringify({
194
+ ...(balance ?? {}),
195
+ sessionBilling: sessionTarget
196
+ ? { ...sessionTarget, balance: sessionBalance, balanceReadable: !!sessionBalance }
197
+ : null,
198
+ }, null, 2));
140
199
  return;
141
200
  }
142
201
  if (!balance) {
143
202
  this.error('No balance record for this account.', { exit: 1 });
144
203
  }
204
+ if (sessionTarget) {
205
+ renderSessionBillingAccount(sessionTarget, sessionBalance, (line) => this.log(line), this.config.bin);
206
+ this.log('');
207
+ }
145
208
  // Name the account. "Hosted usage balance" alone reads as "the balance
146
209
  // that matters here", which is exactly the wrong reading for someone sent
147
210
  // by a workspace refusal.
148
- this.log('Hosted usage balance — your personal account');
211
+ this.log(sessionTarget
212
+ ? 'Hosted usage balance — your personal account (not what the session above bills)'
213
+ : 'Hosted usage balance — your personal account');
149
214
  // Credits AND dollars, because a credit is a micro-dollar and nothing on
150
215
  // this surface said so. Four spend screens reported four units with no
151
216
  // stated relationship between them (OSK-4664).
@@ -235,6 +300,31 @@ class BalanceShow extends base_command_1.BaseCommand {
235
300
  this.log('`skrr balance show --workspace <id>`. `skrr payments wallet` is the');
236
301
  this.log('separate x402 crypto wallet, in USDC — not this balance.');
237
302
  }
303
+ /**
304
+ * Which workspace the next session carries: the selected one, else the
305
+ * caller's default. Fail-open — an unreadable listing with nothing selected
306
+ * returns null and the screen falls back to the personal view it always had,
307
+ * rather than guessing an account.
308
+ */
309
+ async resolveSessionBillingTarget() {
310
+ const selected = process.env.OVERSKY_WORKSPACE_ID?.trim();
311
+ let listing = null;
312
+ try {
313
+ listing = await (0, workspaces_1.listWorkspaceListing)();
314
+ }
315
+ catch {
316
+ listing = null;
317
+ }
318
+ const workspaceId = selected || listing?.defaultWorkspaceId || null;
319
+ if (!workspaceId)
320
+ return null;
321
+ const name = listing?.rows.find((row) => row.id === workspaceId)?.name;
322
+ return {
323
+ workspaceId,
324
+ ...(name ? { name } : {}),
325
+ source: selected ? 'selected' : 'default',
326
+ };
327
+ }
238
328
  /**
239
329
  * Your plan's ceilings, beside the balance they are spent before.
240
330
  *
@@ -39,4 +39,5 @@ export default class Code extends Command {
39
39
  */
40
40
  export declare function forwardToEngine(cmd: Command, subcommand: string | null, passthrough: string[], options?: {
41
41
  managed?: boolean;
42
+ projectPolicy?: 'tui' | 'headless';
42
43
  }): Promise<void>;
@@ -6,6 +6,7 @@ const auth_core_1 = require("@skrr-ai/auth-core");
6
6
  const first_party_harness_1 = require("../../lib/first-party-harness");
7
7
  const first_party_harness_broker_1 = require("../../lib/first-party-harness-broker");
8
8
  const first_party_harness_managed_1 = require("../../lib/first-party-harness-managed");
9
+ const first_party_harness_project_trust_1 = require("../../lib/first-party-harness-project-trust");
9
10
  /**
10
11
  * `skrr code` — launch the first-party harness's interactive coding TUI in the
11
12
  * current directory.
@@ -29,7 +30,12 @@ class Code extends core_1.Command {
29
30
  static description = 'Everything after ' +
30
31
  `\`code\` is forwarded to the engine unchanged, so engine flags are not listed here — ` +
31
32
  `run \`${auth_core_1.FIRST_PARTY_HARNESS.command} run --help\` for this wrapper, or the engine's own help for its ` +
32
- `flags. ${first_party_harness_managed_1.SKRR_AGENT_FLAG} is the one flag this wrapper consumes itself.`;
33
+ `flags. This wrapper consumes three flags itself: ${first_party_harness_managed_1.SKRR_AGENT_FLAG}, ` +
34
+ `${first_party_harness_project_trust_1.TRUST_PROJECT_FLAG} (load this project's own configuration — plugins, tools, MCP ` +
35
+ `servers, permissions — which is otherwise NOT loaded until you trust the project; ` +
36
+ `the TUI asks once when a project ships any), and ${first_party_harness_project_trust_1.ENGINE_PERMISSIONS_FLAG} (let your ` +
37
+ `engine config decide permissions instead of the TUI default: reads run, edits, ` +
38
+ `commands and web access ask).`;
33
39
  static strict = false;
34
40
  static examples = [
35
41
  '<%= config.bin %> code',
@@ -37,6 +43,8 @@ class Code extends core_1.Command {
37
43
  `<%= config.bin %> code ${first_party_harness_managed_1.SKRR_AGENT_FLAG} Engineer`,
38
44
  '<%= config.bin %> code run "fix the failing test"',
39
45
  '<%= config.bin %> code doctor',
46
+ `<%= config.bin %> code ${first_party_harness_project_trust_1.TRUST_PROJECT_FLAG}`,
47
+ `<%= config.bin %> code ${first_party_harness_project_trust_1.ENGINE_PERMISSIONS_FLAG}`,
40
48
  ];
41
49
  async run() {
42
50
  // `this.argv`, NOT `this.parse()`. oclif's `strict = false` relaxes POSITIONAL
@@ -52,7 +60,7 @@ class Code extends core_1.Command {
52
60
  //
53
61
  // The unit tests did not catch it because they cover `buildEngineArgs`, which
54
62
  // always forwarded verbatim. The args never reached it.
55
- await forwardToEngine(this, null, this.argv, { managed: true });
63
+ await forwardToEngine(this, null, this.argv, { managed: true, projectPolicy: 'tui' });
56
64
  }
57
65
  }
58
66
  exports.default = Code;
@@ -79,7 +87,8 @@ exports.default = Code;
79
87
  async function forwardToEngine(cmd, subcommand, passthrough, options = {}) {
80
88
  // Unconditional: the engine must never see a flag it does not declare, whether
81
89
  // or not this run brokers. `skrr code --agent x` remains the ENGINE's flag.
82
- const { agentRef, engineArgv } = (0, first_party_harness_managed_1.takeOverskyAgentFlag)(passthrough);
90
+ const policyFlags = (0, first_party_harness_project_trust_1.takeProjectPolicyFlags)(passthrough);
91
+ const { agentRef, engineArgv } = (0, first_party_harness_managed_1.takeOverskyAgentFlag)(policyFlags.engineArgv);
83
92
  // `logToStderr`, never `log`: `skrr code run --json` writes machine-readable
84
93
  // output on stdout, and one friendly sentence in the middle of it is a parse
85
94
  // error at the other end.
@@ -88,6 +97,29 @@ async function forwardToEngine(cmd, subcommand, passthrough, options = {}) {
88
97
  say(`${first_party_harness_managed_1.SKRR_AGENT_FLAG} has no effect here — this command does not host a managed ` +
89
98
  'session. It was removed from the arguments passed to the engine.');
90
99
  }
100
+ // OSK-11958 — the project's own configuration can run code on this machine
101
+ // and loosen permissions, so it is not loaded until the person trusts the
102
+ // project. Decided BEFORE any lease is acquired: a refusal to answer must not
103
+ // cost a broker. Only the TUI and `run` — an engine subcommand such as
104
+ // `trust` must see the project config it exists to approve, and `acp` is
105
+ // the daemon's, which sets its own.
106
+ const projectDir = options.projectPolicy === 'tui'
107
+ ? (0, first_party_harness_project_trust_1.tuiProjectDir)(engineArgv, process.cwd())
108
+ : options.projectPolicy === 'headless'
109
+ ? process.cwd()
110
+ : null;
111
+ const policy = options.projectPolicy && projectDir
112
+ ? await (0, first_party_harness_project_trust_1.resolveProjectPolicy)({
113
+ cwd: projectDir,
114
+ env: process.env,
115
+ mode: options.projectPolicy,
116
+ trustProject: policyFlags.trustProject,
117
+ enginePermissions: policyFlags.enginePermissions,
118
+ ask: options.projectPolicy === 'tui' ? (0, first_party_harness_project_trust_1.ttyTrustAsker)() : undefined,
119
+ })
120
+ : null;
121
+ for (const notice of policy?.notices ?? [])
122
+ say(notice);
91
123
  const managed = options.managed
92
124
  ? (0, first_party_harness_broker_1.createManagedEngineHooks)({
93
125
  subcommand,
@@ -106,10 +138,22 @@ async function forwardToEngine(cmd, subcommand, passthrough, options = {}) {
106
138
  // skipped or failed attempt is a genuine BYOK run whose credential is
107
139
  // the ambient one. Only the hooks know which happened.
108
140
  credentialMode: managed.credentialMode,
141
+ policyEnv: policy?.env,
142
+ }
143
+ : { policyEnv: policy?.env });
144
+ if (code !== 0) {
145
+ // The engine ran with no managed session and no credential to inherit —
146
+ // its failure (typically a bare "Unexpected server error") names nothing
147
+ // the user can act on. The prepare-time warning already diagnosed this;
148
+ // say it again where the failure lands, because a warning scrolled past
149
+ // is not a diagnosis at the point of failure (OSK-11949).
150
+ if (managed?.unprovisionedByok()) {
151
+ say('No managed session and no provider credential reached the engine — if ' +
152
+ 'the failure above is an authentication or server error, sign in with ' +
153
+ '`skrr login` or set OPENROUTER_API_KEY and retry.');
109
154
  }
110
- : {});
111
- if (code !== 0)
112
155
  cmd.exit(code);
156
+ }
113
157
  }
114
158
  catch (err) {
115
159
  if (err instanceof first_party_harness_1.EngineStartRefusedError) {
@@ -16,6 +16,7 @@ class CodeRun extends core_1.Command {
16
16
  '<%= config.bin %> code run "add a regression test for the parser"',
17
17
  '<%= config.bin %> code run --json "summarize the diff"',
18
18
  '<%= config.bin %> code run --oversky-agent Engineer "fix the failing test"',
19
+ '<%= config.bin %> code run --trust-project "run the project\'s own lint plugin"',
19
20
  ];
20
21
  async run() {
21
22
  // `this.argv`, NOT `this.parse()`. oclif's `strict = false` relaxes POSITIONAL
@@ -31,7 +32,10 @@ class CodeRun extends core_1.Command {
31
32
  //
32
33
  // The unit tests did not catch it because they cover `buildEngineArgs`, which
33
34
  // always forwarded verbatim. The args never reached it.
34
- await (0, index_1.forwardToEngine)(this, 'run', this.argv, { managed: true });
35
+ // `headless`: project configuration is gated exactly as for the TUI, but
36
+ // nobody is asked and the engine keeps its own permission config — an `ask`
37
+ // default would be auto-rejected by a headless run (OSK-11958).
38
+ await (0, index_1.forwardToEngine)(this, 'run', this.argv, { managed: true, projectPolicy: 'headless' });
35
39
  }
36
40
  }
37
41
  exports.default = CodeRun;
@@ -0,0 +1,19 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * The Cycle is the unit a person reads: one row per check (and per wake that
4
+ * never became one), each with one outcome and, when it stopped, the one
5
+ * reason and what to do about it.
6
+ */
7
+ export default class CommitmentsCycles extends BaseCommand {
8
+ static description: string;
9
+ static examples: string[];
10
+ static args: {
11
+ commitmentId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
12
+ };
13
+ static flags: {
14
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
15
+ limit: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
16
+ cursor: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
17
+ };
18
+ run(): Promise<void>;
19
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../base-command");
5
+ const commitments_1 = require("../../lib/commitments");
6
+ /**
7
+ * The Cycle is the unit a person reads: one row per check (and per wake that
8
+ * never became one), each with one outcome and, when it stopped, the one
9
+ * reason and what to do about it.
10
+ */
11
+ class CommitmentsCycles extends base_command_1.BaseCommand {
12
+ static description = "List a commitment's cycles, newest first: what woke it, how each ended, and why it stopped";
13
+ static examples = [
14
+ '<%= config.bin %> commitments cycles obj_abc123',
15
+ '<%= config.bin %> commitments cycles obj_abc123 --limit 50 --json',
16
+ '<%= config.bin %> commitments cycles obj_abc123 --cursor <next_cursor>',
17
+ ];
18
+ static args = {
19
+ commitmentId: core_1.Args.string({ ignoreStdin: true, description: 'Commitment ID', required: true }),
20
+ };
21
+ static flags = {
22
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
23
+ limit: core_1.Flags.integer({ description: 'Max checks per page (1-50)', min: 1, max: 50 }),
24
+ cursor: core_1.Flags.string({ description: 'Opaque cursor returned by the preceding page' }),
25
+ };
26
+ async run() {
27
+ this.requireAuth();
28
+ const { args, flags } = await this.parse(CommitmentsCycles);
29
+ try {
30
+ const page = await commitments_1.commitmentApi.cycles(args.commitmentId, {
31
+ ...(typeof flags.limit === 'number' ? { limit: flags.limit } : {}),
32
+ ...(flags.cursor ? { cursor: flags.cursor } : {}),
33
+ });
34
+ if (flags.json) {
35
+ this.log(JSON.stringify(page, null, 2));
36
+ return;
37
+ }
38
+ for (const line of (0, commitments_1.formatCommitmentCycles)(page))
39
+ this.log(line);
40
+ }
41
+ catch (err) {
42
+ this.handleApiError(err);
43
+ }
44
+ }
45
+ }
46
+ exports.default = CommitmentsCycles;
@@ -6,6 +6,7 @@ export declare function doctorVerdict(input: {
6
6
  }): {
7
7
  verdict: string;
8
8
  next: string;
9
+ why?: string;
9
10
  };
10
11
  export default class CommitmentsDoctor extends BaseCommand {
11
12
  static description: string;
@@ -15,8 +15,12 @@ function doctorVerdict(input) {
15
15
  if (operatingState.status === 'waiting_approval') {
16
16
  return { verdict: 'waiting_approval', next: 'Open the pending prepared action for review' };
17
17
  }
18
- if (operatingState.coverage && operatingState.coverage.complete === false) {
19
- return { verdict: 'degraded', next: 'Retry doctor; one or more operating-state projections are unavailable' };
18
+ if (operatingState.coverage &&
19
+ operatingState.coverage.complete === false) {
20
+ return {
21
+ verdict: 'degraded',
22
+ next: 'Retry doctor; one or more operating-state projections are unavailable',
23
+ };
20
24
  }
21
25
  const autonomy = operatingState.autonomy;
22
26
  const budget = autonomy?.budget;
@@ -30,9 +34,18 @@ function doctorVerdict(input) {
30
34
  }
31
35
  if (operatingState.status && commitment.status === 'active') {
32
36
  const status = String(operatingState.status);
37
+ const healthy = ['on_track', 'healthy'].includes(status);
38
+ // Where the latest cycle stopped, as the server words it. "Read the raw
39
+ // trace" was the only next step an unhealthy Commitment was ever given,
40
+ // even when the stop already named the fix (OSK-12120).
41
+ const latestCheck = operatingState.latestCheck;
42
+ const stop = latestCheck?.stop;
43
+ const fix = typeof stop?.fix === 'string' && stop.fix ? stop.fix : undefined;
44
+ const why = typeof stop?.detail === 'string' && stop.detail ? stop.detail : undefined;
33
45
  return {
34
- verdict: ['on_track', 'healthy'].includes(status) ? 'active_healthy' : status,
35
- next: 'skrr commitments trace <id> --limit 50',
46
+ verdict: healthy ? 'active_healthy' : status,
47
+ next: !healthy && fix ? fix : 'skrr commitments trace <id> --limit 50',
48
+ ...(!healthy && why ? { why } : {}),
36
49
  };
37
50
  }
38
51
  if (commitment.status !== 'active')
@@ -81,6 +94,8 @@ class CommitmentsDoctor extends base_command_1.BaseCommand {
81
94
  this.log(JSON.stringify(report, null, 2));
82
95
  else {
83
96
  this.log(`Verdict: ${diagnosis.verdict}`);
97
+ if (diagnosis.why)
98
+ this.log(`Why: ${diagnosis.why}`);
84
99
  this.log(`Next: ${diagnosis.next.replace('<id>', args.id)}`);
85
100
  const autonomy = operatingState.autonomy;
86
101
  if (autonomy) {
@@ -5,10 +5,19 @@ const core_1 = require("@oclif/core");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const commitments_1 = require("../../lib/commitments");
7
7
  const commitment_product_1 = require("../../lib/commitment-product");
8
+ /** Which limit binds the daily action runs — the fix lives on that object. */
9
+ const RUN_LIMIT_BINDING_LABELS = {
10
+ northstar_allocation: 'Northstar allocation',
11
+ workspace_admin: 'workspace limit',
12
+ owner: 'owner limit',
13
+ };
8
14
  function effectivePolicyLines(policy) {
9
15
  const governance = policy.governance || {};
10
16
  const owner = policy.owner || {};
11
17
  const lines = [`Mode in force: ${(0, commitment_product_1.productModeLabel)(governance.effectiveAutonomyMode)}`];
18
+ // Whose permissions a run uses (OSK-11975), directly under the mode it can
19
+ // contradict — an Execute Commitment on a plan-mode Agent (OSK-12065).
20
+ lines.push(...(0, commitments_1.permissionLines)(policy.permissions));
12
21
  if (governance.blocked) {
13
22
  lines.push(`Workspace status: blocked${governance.blockedReason ? ` — ${governance.blockedReason}` : ''}`);
14
23
  }
@@ -28,17 +37,47 @@ function effectivePolicyLines(policy) {
28
37
  if (Array.isArray(governance.allowedCommitmentDeliveryModes)) {
29
38
  lines.push(`Workspace delivery allowlist: ${governance.allowedCommitmentDeliveryModes.length ? governance.allowedCommitmentDeliveryModes.map(commitment_product_1.deliveryLabel).join(', ') : 'none'}`);
30
39
  }
31
- if (Array.isArray(governance.requireAttestedAccessForDeliveryModes) && governance.requireAttestedAccessForDeliveryModes.length) {
40
+ if (Array.isArray(governance.requireAttestedAccessForDeliveryModes) &&
41
+ governance.requireAttestedAccessForDeliveryModes.length) {
32
42
  lines.push(`Runtime attestation required for: ${governance.requireAttestedAccessForDeliveryModes.map(commitment_product_1.deliveryLabel).join(', ')}`);
33
43
  }
34
44
  const runLimit = owner.actionRunsPerDay;
35
45
  if (runLimit) {
36
46
  lines.push(`Owner action-run limit: ${runLimit.mode === 'unbounded' ? 'Unlimited' : `${runLimit.value}/day`}`);
37
47
  }
48
+ const effectiveRuns = policy.effective?.actionRunsPerDay;
49
+ if (effectiveRuns) {
50
+ const bindingLabel = RUN_LIMIT_BINDING_LABELS[effectiveRuns.binding];
51
+ const binding = bindingLabel ? ` (binding: ${bindingLabel})` : '';
52
+ lines.push(`Effective action-run limit: ${effectiveRuns.mode === 'unbounded' ? 'Unlimited' : `${effectiveRuns.value}/day`}${binding}`);
53
+ }
38
54
  const cost = policy.billing;
39
55
  if (cost?.effectiveCostLimitUsd != null) {
40
56
  lines.push(`Effective daily cost limit: $${Number(cost.effectiveCostLimitUsd).toFixed(2)}`);
41
57
  }
58
+ // What a sensing turn's fence holds (OSK-11978). The shell line carries the
59
+ // part that is NOT enforced, because that is the one an operator granting
60
+ // it has to see.
61
+ const observation = policy.observation;
62
+ if (observation?.sensing && observation.retired) {
63
+ // OSK-11975 retired the sensing fence: printing "granted — read-only" for
64
+ // a grant that no longer fences anything is the claim this must not make.
65
+ const recorded = [
66
+ observation.shell?.granted ? 'shell' : null,
67
+ observation.browser?.granted ? 'browser' : null,
68
+ ].filter(Boolean);
69
+ lines.push(`Sensing runs: the Agent's own permissions${recorded.length
70
+ ? ` (recorded ${recorded.join(' and ')} grant is retired and has no effect)`
71
+ : ''}`);
72
+ }
73
+ else if (observation?.sensing) {
74
+ lines.push(`Sensing shell: ${observation.shell?.granted
75
+ ? 'granted — read-only filesystem on the Hosted Machine; network NOT fenced (a command can still change remote state); withheld on your own machine'
76
+ : 'not granted'}`);
77
+ lines.push(`Sensing browser: ${observation.browser?.granted
78
+ ? 'granted — read-only, enforced by your machine'
79
+ : 'not granted'}`);
80
+ }
42
81
  return lines;
43
82
  }
44
83
  /** Show the policy actually governing a Commitment without changing it. */
@@ -0,0 +1,17 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * Walk the latest cycle as six questions — did anything wake it, was there new
4
+ * evidence, was it its turn, did the agent think, did it act, did it tell you —
5
+ * and name the first "no" with its fix.
6
+ */
7
+ export default class CommitmentsExplain extends BaseCommand {
8
+ static description: string;
9
+ static examples: string[];
10
+ static args: {
11
+ commitmentId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
12
+ };
13
+ static flags: {
14
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
15
+ };
16
+ run(): Promise<void>;
17
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../base-command");
5
+ const commitments_1 = require("../../lib/commitments");
6
+ /**
7
+ * Walk the latest cycle as six questions — did anything wake it, was there new
8
+ * evidence, was it its turn, did the agent think, did it act, did it tell you —
9
+ * and name the first "no" with its fix.
10
+ */
11
+ class CommitmentsExplain extends base_command_1.BaseCommand {
12
+ static description = 'Explain where the latest cycle of a commitment stopped, as six yes/no questions';
13
+ static examples = [
14
+ '<%= config.bin %> commitments explain obj_abc123',
15
+ '<%= config.bin %> commitments explain obj_abc123 --json',
16
+ ];
17
+ static args = {
18
+ commitmentId: core_1.Args.string({ ignoreStdin: true, description: 'Commitment ID', required: true }),
19
+ };
20
+ static flags = {
21
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
22
+ };
23
+ async run() {
24
+ this.requireAuth();
25
+ const { args, flags } = await this.parse(CommitmentsExplain);
26
+ try {
27
+ const explanation = await commitments_1.commitmentApi.explain(args.commitmentId);
28
+ if (flags.json) {
29
+ this.log(JSON.stringify(explanation, null, 2));
30
+ return;
31
+ }
32
+ for (const line of (0, commitments_1.formatCommitmentExplanation)(explanation))
33
+ this.log(line);
34
+ }
35
+ catch (err) {
36
+ this.handleApiError(err);
37
+ }
38
+ }
39
+ }
40
+ exports.default = CommitmentsExplain;
@@ -75,7 +75,9 @@ function formatNextTick(projection) {
75
75
  `Posture: ${decision.posture ?? '-'}`,
76
76
  `Health: ${decision.health ?? '-'}`,
77
77
  `Risk: ${decision.riskLevel ?? '-'}`,
78
- `Confidence: ${decision.confidence ?? '-'}`,
78
+ // Two decimals: the arbiter's arithmetic yields values like
79
+ // 0.6000000000000001, which reads as a precision the number does not have.
80
+ `Confidence: ${typeof decision.confidence === 'number' ? decision.confidence.toFixed(2) : '-'}`,
79
81
  ];
80
82
  if (decision.whyNow)
81
83
  lines.push(`Why now: ${decision.whyNow}`);
@@ -110,6 +110,16 @@ class CommitmentsPreflight extends base_command_1.BaseCommand {
110
110
  return;
111
111
  }
112
112
  this.log(`${args.id ? 'Preflight' : 'Pre-create preview (nothing persisted)'}: ${report.verdict === 'ready' ? 'Ready to run' : report.verdict === 'blocked' ? 'Run is blocked' : 'A capability or configuration check is still needed'}`);
113
+ // Whose permissions the run will use (OSK-11975), before the gates, so an
114
+ // Execute Commitment on a plan-mode Agent is the first thing read
115
+ // (OSK-12065).
116
+ for (const line of (0, commitments_1.permissionLines)(report.permissions))
117
+ this.log(line);
118
+ if (flags.verbose) {
119
+ const retired = (0, commitments_1.retiredFieldsLine)(report.execution?.retired, report.execution?.retiredReason);
120
+ if (retired)
121
+ this.log(retired);
122
+ }
113
123
  this.log('\nGates:');
114
124
  for (const gate of report.gates || []) {
115
125
  for (const line of preflightGateLines(gate, flags.verbose))
@@ -50,6 +50,11 @@ class CommitmentsShow extends base_command_1.BaseCommand {
50
50
  return;
51
51
  }
52
52
  (0, commitments_1.renderCommitmentSummary)(response.commitment, (line) => this.log(line), response.latestCheck);
53
+ // Whose permissions a run uses (OSK-11975) — read from the operating state,
54
+ // which carries the same projection preflight reports (OSK-12065).
55
+ const autonomy = response.operatingState?.autonomy;
56
+ for (const line of (0, commitments_1.permissionLines)(autonomy?.permissions))
57
+ this.log(line);
53
58
  // `show` is the first surface a person reaches after linking something, and
54
59
  // it used to render only watchedSources — a freshly linked goal or task was
55
60
  // invisible until you knew to run `links list` (OSK-10963).
@@ -101,8 +106,13 @@ class CommitmentsShow extends base_command_1.BaseCommand {
101
106
  this.log('');
102
107
  this.log(`Full history: ${this.config.bin} commitments checks ${args.id}`);
103
108
  }
104
- if (flags.verbose)
109
+ if (flags.verbose) {
105
110
  this.log(`Compatibility policy: ${JSON.stringify(response.commitment.policy)}`);
111
+ const runtime = autonomy?.runtime;
112
+ const retired = (0, commitments_1.retiredFieldsLine)(runtime?.retired, runtime?.retiredReason);
113
+ if (retired)
114
+ this.log(retired);
115
+ }
106
116
  const url = (0, web_url_1.commitmentUrlFrom)(this.cliConfig.baseURL, response.commitment);
107
117
  if (url)
108
118
  this.log(`URL: ${url}`);
@@ -0,0 +1,15 @@
1
+ import { Command } from '@oclif/core';
2
+ /**
3
+ * `skrr daemon config` — set this machine's daemon configuration, including
4
+ * each tool category's permission mode (OSK-12099). Before this the only way
5
+ * to set a category was to guess the separate `skrrd` binary exists; every
6
+ * other daemon verb was forwarded and this one was not.
7
+ */
8
+ export default class DaemonConfig extends Command {
9
+ static aliases: string[];
10
+ static summary: string;
11
+ static description: string;
12
+ static strict: boolean;
13
+ static examples: string[];
14
+ run(): Promise<void>;
15
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
5
+ /**
6
+ * `skrr daemon config` — set this machine's daemon configuration, including
7
+ * each tool category's permission mode (OSK-12099). Before this the only way
8
+ * to set a category was to guess the separate `skrrd` binary exists; every
9
+ * other daemon verb was forwarded and this one was not.
10
+ */
11
+ class DaemonConfig extends core_1.Command {
12
+ static aliases = ['daemons:config'];
13
+ static summary = "Set the local daemon's configuration, e.g. a tool category's permission mode";
14
+ static description = (0, exec_runtime_binary_1.forwardedFlagsNote)('config');
15
+ static strict = false; // forward all args verbatim — see run()
16
+ static examples = [
17
+ '<%= config.bin %> daemon config --mode bash=manual',
18
+ '<%= config.bin %> daemon config --mode bash=auto --mode browser=auto',
19
+ '<%= config.bin %> daemon config --require-approvals',
20
+ ];
21
+ async run() {
22
+ // `this.argv`, NOT `this.parse()`: oclif would reject every `skrrd config`
23
+ // flag this wrapper has not redeclared (OSK-296).
24
+ const argv = this.argv;
25
+ try {
26
+ const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['config', ...argv]);
27
+ this.exit(code);
28
+ }
29
+ catch (err) {
30
+ if (err.message === 'runtime-binary-not-installed') {
31
+ this.log((0, exec_runtime_binary_1.notInstalledMessage)());
32
+ this.exit(127);
33
+ }
34
+ throw err;
35
+ }
36
+ }
37
+ }
38
+ exports.default = DaemonConfig;
@@ -47,6 +47,7 @@ class Daemon extends core_1.Command {
47
47
  this.log(' skrr daemon stop — stop the daemon');
48
48
  this.log(' skrr daemon update — update the daemon binary to the latest signed release');
49
49
  this.log(' skrr daemon status — show running state and config');
50
+ this.log(' skrr daemon config — set config, e.g. a tool category: --mode bash=manual');
50
51
  this.log(' skrr daemon logs — tail recent daemon logs');
51
52
  this.log(' skrr daemon recover-offline-queue — restore recoverable offline WALs');
52
53
  this.log(' skrr daemon usage — show local Claude Code and Codex usage limits');