@skrr-ai/cli 0.1.50 → 0.1.52

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 (131) hide show
  1. package/dist/base-command.d.ts +7 -0
  2. package/dist/base-command.js +95 -8
  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 +8 -4
  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 +32 -0
  9. package/dist/commands/agents/update.js +81 -9
  10. package/dist/commands/balance/show.d.ts +34 -0
  11. package/dist/commands/balance/show.js +92 -2
  12. package/dist/commands/commitments/doctor.d.ts +1 -0
  13. package/dist/commands/commitments/doctor.js +19 -4
  14. package/dist/commands/commitments/effective-policy.js +15 -1
  15. package/dist/commands/commitments/next-tick.js +12 -2
  16. package/dist/commands/commitments/preflight.js +10 -0
  17. package/dist/commands/commitments/show.js +11 -1
  18. package/dist/commands/context.js +2 -1
  19. package/dist/commands/daemon/config.d.ts +15 -0
  20. package/dist/commands/daemon/config.js +53 -0
  21. package/dist/commands/daemon/index.js +1 -0
  22. package/dist/commands/daemon/status.js +15 -1
  23. package/dist/commands/doctor.js +8 -1
  24. package/dist/commands/harnesses/models.d.ts +54 -0
  25. package/dist/commands/harnesses/models.js +100 -7
  26. package/dist/commands/harnesses/show.d.ts +11 -0
  27. package/dist/commands/harnesses/show.js +35 -0
  28. package/dist/commands/harnesses/update.d.ts +28 -1
  29. package/dist/commands/harnesses/update.js +73 -10
  30. package/dist/commands/login.d.ts +2 -1
  31. package/dist/commands/login.js +26 -7
  32. package/dist/commands/logout.js +3 -0
  33. package/dist/commands/machines/dedicated/show.js +25 -2
  34. package/dist/commands/outbox/list.d.ts +12 -1
  35. package/dist/commands/outbox/list.js +31 -2
  36. package/dist/commands/revoke-daemon.js +9 -0
  37. package/dist/commands/spaces/create.d.ts +1 -0
  38. package/dist/commands/spaces/create.js +17 -0
  39. package/dist/commands/spaces/show.js +5 -0
  40. package/dist/commands/spaces/update.d.ts +1 -0
  41. package/dist/commands/spaces/update.js +16 -0
  42. package/dist/commands/tasks/create.d.ts +6 -0
  43. package/dist/commands/tasks/create.js +31 -6
  44. package/dist/commands/tasks/show.js +45 -0
  45. package/dist/commands/tasks/start.d.ts +14 -0
  46. package/dist/commands/tasks/start.js +31 -1
  47. package/dist/commands/tasks/update.d.ts +1 -0
  48. package/dist/commands/tasks/update.js +29 -1
  49. package/dist/commands/tasks/upsert.d.ts +1 -0
  50. package/dist/commands/tasks/upsert.js +16 -0
  51. package/dist/commands/whoami.js +5 -3
  52. package/dist/lib/agent-config.d.ts +2 -0
  53. package/dist/lib/agent-config.js +14 -10
  54. package/dist/lib/agentic-stream.d.ts +88 -1
  55. package/dist/lib/agentic-stream.js +330 -9
  56. package/dist/lib/auth-core-init.d.ts +2 -10
  57. package/dist/lib/auth-core-init.js +20 -67
  58. package/dist/lib/auth-failure.d.ts +33 -0
  59. package/dist/lib/auth-failure.js +68 -0
  60. package/dist/lib/auth-storage.d.ts +24 -0
  61. package/dist/lib/auth-storage.js +342 -11
  62. package/dist/lib/cli-id.d.ts +22 -0
  63. package/dist/lib/cli-id.js +56 -0
  64. package/dist/lib/cli-identity.d.ts +49 -0
  65. package/dist/lib/cli-identity.js +159 -0
  66. package/dist/lib/commitments.d.ts +25 -1
  67. package/dist/lib/commitments.js +46 -1
  68. package/dist/lib/config.d.ts +17 -0
  69. package/dist/lib/config.js +15 -3
  70. package/dist/lib/daemonBroker.d.ts +37 -1
  71. package/dist/lib/daemonBroker.js +146 -22
  72. package/dist/lib/daemonBrokerRefusal.d.ts +51 -0
  73. package/dist/lib/daemonBrokerRefusal.js +127 -0
  74. package/dist/lib/dedicated-machines.d.ts +33 -2
  75. package/dist/lib/dedicated-machines.js +50 -1
  76. package/dist/lib/delegated-cli.js +20 -1
  77. package/dist/lib/exec-runtime-binary.d.ts +3 -1
  78. package/dist/lib/exec-runtime-binary.js +4 -2
  79. package/dist/lib/first-party-harness-broker.js +10 -5
  80. package/dist/lib/harnesses.d.ts +10 -0
  81. package/dist/lib/keychain.d.ts +39 -0
  82. package/dist/lib/keychain.js +228 -39
  83. package/dist/lib/label-ref.d.ts +34 -0
  84. package/dist/lib/label-ref.js +68 -1
  85. package/dist/lib/live-runtime-binary.d.ts +40 -0
  86. package/dist/lib/live-runtime-binary.js +193 -0
  87. package/dist/lib/login.d.ts +23 -0
  88. package/dist/lib/login.js +67 -21
  89. package/dist/lib/message-intent.js +3 -1
  90. package/dist/lib/oauthLogin.js +2 -8
  91. package/dist/lib/refresh.js +6 -2
  92. package/dist/lib/tasks.d.ts +20 -0
  93. package/dist/lib/tasks.js +87 -8
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +2 -0
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +26 -1
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +28 -4
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +23 -5
  98. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +41 -3
  99. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
  100. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +25 -3
  101. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.d.ts +32 -0
  102. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.js +268 -0
  103. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.d.ts +2 -2
  104. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +11 -10
  105. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +36 -0
  106. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +156 -0
  107. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +8 -0
  108. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +8 -1
  109. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +22 -0
  110. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +61 -2
  111. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +2 -0
  112. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +25 -1
  113. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +28 -4
  114. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +22 -4
  115. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +41 -3
  116. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
  117. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -1
  118. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.d.ts +32 -0
  119. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.js +257 -0
  120. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.d.ts +2 -2
  121. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +11 -10
  122. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +36 -0
  123. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +147 -0
  124. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +8 -0
  125. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +8 -1
  126. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +22 -0
  127. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +58 -1
  128. package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
  129. package/dist/node_modules/@skrr-ai/data-provider/index.js +3139 -3097
  130. package/oclif.manifest.json +28591 -28438
  131. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import type { TSkyCodeUsageSnapshot } from '@skrr-ai/data-provider';
2
2
  import { BaseCommand } from '../../base-command';
3
+ import type { WorkspaceBalance } from '../../lib/balance';
3
4
  import { type RuntimeMonthlySpend } from '../../lib/harnesses';
4
5
  /**
5
6
  * The hosted-runtime monthly cap rows, as lines.
@@ -8,6 +9,32 @@ import { type RuntimeMonthlySpend } from '../../lib/harnesses';
8
9
  * the wording is part of the observable contract and is pinned directly.
9
10
  */
10
11
  export declare function renderRuntimeSpend(spend: RuntimeMonthlySpend[], log: (line: string) => void): void;
12
+ /**
13
+ * The workspace the next session on this machine is billed to, and why that one.
14
+ *
15
+ * `selected`: `workspaces use` / `--workspace` / a daemon handoff put it in
16
+ * `OVERSKY_WORKSPACE_ID`. `default`: nothing is selected, so the session carries
17
+ * the caller's server-side default workspace — the same resolution `whoami` and
18
+ * `workspaces list` describe ("resolves to your default workspace").
19
+ */
20
+ export interface SessionBillingTarget {
21
+ workspaceId: string;
22
+ name?: string;
23
+ source: 'selected' | 'default';
24
+ }
25
+ /**
26
+ * The account the NEXT session bills against, as lines — printed FIRST (OSK-12122).
27
+ *
28
+ * `skrr balance` opened on the personal account, so a user whose sessions bill
29
+ * the default workspace read `Credits: 999,503,672,326 ($999,503.67)` while every
30
+ * session was refused against that workspace's $0. The screen that exists to
31
+ * answer "why was I refused for credit" answered for the one account the
32
+ * refusal was not about, and nothing on it said which account that was.
33
+ *
34
+ * `balance` absent means the read failed: say so, never print a figure.
35
+ * Pure so the wording is pinned directly, like `renderRuntimeSpend`.
36
+ */
37
+ export declare function renderSessionBillingAccount(target: SessionBillingTarget, balance: WorkspaceBalance | null, log: (line: string) => void, bin?: string): void;
11
38
  /**
12
39
  * Your plan's ceilings, as lines.
13
40
  *
@@ -66,6 +93,13 @@ export default class BalanceShow extends BaseCommand {
66
93
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
67
94
  };
68
95
  run(): Promise<void>;
96
+ /**
97
+ * Which workspace the next session carries: the selected one, else the
98
+ * caller's default. Fail-open — an unreadable listing with nothing selected
99
+ * returns null and the screen falls back to the personal view it always had,
100
+ * rather than guessing an account.
101
+ */
102
+ private resolveSessionBillingTarget;
69
103
  /**
70
104
  * Your plan's ceilings, beside the balance they are spent before.
71
105
  *
@@ -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
  *
@@ -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) {
@@ -15,6 +15,9 @@ function effectivePolicyLines(policy) {
15
15
  const governance = policy.governance || {};
16
16
  const owner = policy.owner || {};
17
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));
18
21
  if (governance.blocked) {
19
22
  lines.push(`Workspace status: blocked${governance.blockedReason ? ` — ${governance.blockedReason}` : ''}`);
20
23
  }
@@ -56,7 +59,18 @@ function effectivePolicyLines(policy) {
56
59
  // part that is NOT enforced, because that is the one an operator granting
57
60
  // it has to see.
58
61
  const observation = policy.observation;
59
- if (observation?.sensing) {
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) {
60
74
  lines.push(`Sensing shell: ${observation.shell?.granted
61
75
  ? 'granted — read-only filesystem on the Hosted Machine; network NOT fenced (a command can still change remote state); withheld on your own machine'
62
76
  : 'not granted'}`);
@@ -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}`);
@@ -84,10 +86,18 @@ function formatNextTick(projection) {
84
86
  // dispatch-refusal loop can't hide behind an on_track projection.
85
87
  if (projection.health || projection.liveness) {
86
88
  const census = projection.liveness?.terminalDispositionCensus;
89
+ const plural = (count) => (count === 1 ? '' : 's');
87
90
  const detail = [
88
91
  projection.liveness?.diagnosisKind,
89
92
  census?.workFailures
90
- ? `${census.workFailures} recent run failure${census.workFailures === 1 ? '' : 's'}`
93
+ ? `${census.workFailures} recent run failure${plural(census.workFailures)}`
94
+ : null,
95
+ // A run that never started is not a failure of the work (OSK-12137).
96
+ census?.machineFailures
97
+ ? `${census.machineFailures} run${plural(census.machineFailures)} never started`
98
+ : null,
99
+ census?.contractFailures
100
+ ? `${census.contractFailures} result${plural(census.contractFailures)} rejected`
91
101
  : null,
92
102
  ]
93
103
  .filter(Boolean)
@@ -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}`);
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
4
4
  const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../base-command");
6
6
  const config_1 = require("../lib/config");
7
+ const cli_id_1 = require("../lib/cli-id");
7
8
  const keychain_1 = require("../lib/keychain");
8
9
  const cli_runtime_1 = require("../lib/cli-runtime");
9
10
  const runtime_context_1 = require("../lib/runtime-context");
@@ -48,7 +49,7 @@ class Context extends base_command_1.BaseCommand {
48
49
  profile: (0, keychain_1.getActiveProfile)(),
49
50
  config: {
50
51
  path: (0, config_1.getConfigPath)(),
51
- cliId: this.cliConfig.cliId ?? null,
52
+ cliId: (0, cli_id_1.resolveCliId)(this.cliConfig) ?? null,
52
53
  },
53
54
  auth: {
54
55
  signedIn: Boolean(this.resolvedCredential?.token),
@@ -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,53 @@
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
+ const live_runtime_binary_1 = require("../../lib/live-runtime-binary");
6
+ /**
7
+ * `skrr daemon config` — set this machine's daemon configuration, including
8
+ * each tool category's permission mode (OSK-12099). Before this the only way
9
+ * to set a category was to guess the separate `skrrd` binary exists; every
10
+ * other daemon verb was forwarded and this one was not.
11
+ */
12
+ class DaemonConfig extends core_1.Command {
13
+ static aliases = ['daemons:config'];
14
+ static summary = "Set the local daemon's configuration, e.g. a tool category's permission mode";
15
+ static description = (0, exec_runtime_binary_1.forwardedFlagsNote)('config');
16
+ static strict = false; // forward all args verbatim — see run()
17
+ static examples = [
18
+ '<%= config.bin %> daemon config --mode bash=manual',
19
+ '<%= config.bin %> daemon config --mode bash=auto --mode browser=auto',
20
+ '<%= config.bin %> daemon config --require-approvals',
21
+ ];
22
+ async run() {
23
+ // `this.argv`, NOT `this.parse()`: oclif would reject every `skrrd config`
24
+ // flag this wrapper has not redeclared (OSK-296).
25
+ const argv = this.argv;
26
+ try {
27
+ // Config writes the file the LIVE daemon reloads, so it must be parsed by
28
+ // the live daemon's own binary — the same routing status uses (OSK-12102).
29
+ // Forwarding to the first `skrrd` on PATH let an older build accept
30
+ // `--auto-approve-bash banana` as false and reject `--mode`, then write
31
+ // the live daemon's config anyway (OSK-12099 verifier).
32
+ const pathBinary = (0, exec_runtime_binary_1.findRuntimeBinary)();
33
+ const decision = pathBinary
34
+ ? (0, live_runtime_binary_1.decideStatusBinary)(pathBinary, (0, live_runtime_binary_1.profileFromArgs)(argv), (0, live_runtime_binary_1.defaultLiveRuntimeDeps)())
35
+ : null;
36
+ const note = decision ? (0, live_runtime_binary_1.describeStatusBinary)(decision) : null;
37
+ if (note)
38
+ process.stderr.write(`${note}\n`);
39
+ const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['config', ...argv], {
40
+ binary: decision?.binary,
41
+ });
42
+ this.exit(code);
43
+ }
44
+ catch (err) {
45
+ if (err.message === 'runtime-binary-not-installed') {
46
+ this.log((0, exec_runtime_binary_1.notInstalledMessage)());
47
+ this.exit(127);
48
+ }
49
+ throw err;
50
+ }
51
+ }
52
+ }
53
+ 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');
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.readinessPointer = readinessPointer;
4
4
  const core_1 = require("@oclif/core");
5
5
  const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
6
+ const live_runtime_binary_1 = require("../../lib/live-runtime-binary");
6
7
  /**
7
8
  * The status report is the runtime's, and it describes the daemon: running,
8
9
  * connected, which credential. Whether the coding-agent CLIs on the machine can
@@ -41,7 +42,20 @@ class DaemonStatus extends core_1.Command {
41
42
  // mistake, one directory over.
42
43
  const argv = this.argv;
43
44
  try {
44
- const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['status', ...argv]);
45
+ // Status describes the LIVE daemon, so render it with the live daemon's
46
+ // own binary when that is a different build from the first `skrrd` on
47
+ // PATH, and say which one rendered it either way (OSK-12102). The note
48
+ // goes to stderr so `--json` stays one document on stdout.
49
+ const pathBinary = (0, exec_runtime_binary_1.findRuntimeBinary)();
50
+ const decision = pathBinary
51
+ ? (0, live_runtime_binary_1.decideStatusBinary)(pathBinary, (0, live_runtime_binary_1.profileFromArgs)(argv), (0, live_runtime_binary_1.defaultLiveRuntimeDeps)())
52
+ : null;
53
+ const note = decision ? (0, live_runtime_binary_1.describeStatusBinary)(decision) : null;
54
+ if (note)
55
+ process.stderr.write(`${note}\n`);
56
+ const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['status', ...argv], {
57
+ binary: decision?.binary,
58
+ });
45
59
  const pointer = readinessPointer(argv, code);
46
60
  if (pointer)
47
61
  this.log(pointer);
@@ -43,6 +43,7 @@ const auth_core_1 = require("@skrr-ai/auth-core");
43
43
  const base_command_1 = require("../base-command");
44
44
  const auth_storage_1 = require("../lib/auth-storage");
45
45
  const config_1 = require("../lib/config");
46
+ const cli_id_1 = require("../lib/cli-id");
46
47
  const keychain_1 = require("../lib/keychain");
47
48
  const cli_runtime_1 = require("../lib/cli-runtime");
48
49
  const command_manifest_1 = require("../lib/command-manifest");
@@ -272,6 +273,9 @@ class Doctor extends base_command_1.BaseCommand {
272
273
  ? ['auth_token_present_but_user_lookup_failed']
273
274
  : []),
274
275
  ...(!checks.authToken ? ['not_signed_in_or_no_token_available'] : []),
276
+ ...(credential.ownerStatus === 'foreign'
277
+ ? ['stored_credential_belongs_to_another_skrr_config_run_skrr_login']
278
+ : []),
275
279
  ];
276
280
  const payload = {
277
281
  ok: issues.length === 0,
@@ -286,13 +290,16 @@ class Doctor extends base_command_1.BaseCommand {
286
290
  profile: (0, keychain_1.getActiveProfile)(),
287
291
  config: {
288
292
  path: (0, config_1.getConfigPath)(),
289
- cliId: this.cliConfig.cliId ?? null,
293
+ cliId: (0, cli_id_1.resolveCliId)(this.cliConfig) ?? null,
290
294
  },
291
295
  auth: {
292
296
  signedIn: Boolean(account),
293
297
  tokenSource: this.credentialSourceLabel(),
294
298
  tokenKind: this.resolvedCredential?.kind ?? null,
295
299
  refreshAvailable: Boolean(credential.bundle?.refreshToken),
300
+ // OSK-12135 — why a stored credential was not used, when it belongs to
301
+ // another skrr config on this machine. Null when there is nothing to say.
302
+ storedCredentialOwner: (0, auth_storage_1.describeForeignCredential)(credential),
296
303
  accessExpiresAt: accessExpiresAt ? new Date(accessExpiresAt).toISOString() : null,
297
304
  accessExpired: accessExpiresAt ? accessExpiresAt <= now : null,
298
305
  refreshExpiresAt: refreshExpiresAt ? new Date(refreshExpiresAt).toISOString() : null,
@@ -1,4 +1,58 @@
1
1
  import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * Why the server fell back to its curated list — `sourceReason` on
4
+ * `GET /api/harnesses/:id/models`, present only when `source` is `curated`.
5
+ * The server owns the vocabulary (`MODEL_SOURCE_REASONS` in
6
+ * `api/server/services/Harness/engineModelCatalog.js`); a value this build
7
+ * does not recognise still prints, verbatim, rather than vanishing.
8
+ */
9
+ export type HarnessModelSourceReason = 'no_live_catalog_for_harness' | 'daemon_offline' | 'live_catalog_unsupported' | 'live_probe_failed';
10
+ export interface HarnessModelsReport {
11
+ provider?: string | null;
12
+ source?: string;
13
+ sourceReason?: HarnessModelSourceReason | string;
14
+ sourceDetail?: string;
15
+ /** The harness row's own stored default model; empty when unset. */
16
+ defaultModel?: string | null;
17
+ /** The harness row's own stored default effort; empty when unset. */
18
+ defaultReasoningEffort?: string | null;
19
+ models?: Array<{
20
+ value?: string;
21
+ label?: string;
22
+ description?: string;
23
+ efforts?: string[];
24
+ /** The effort this model starts at when none is chosen. */
25
+ defaultEffort?: string | null;
26
+ /** The harness's OWN recommendation (Codex `model/list`), not skrr's. */
27
+ isDefault?: boolean;
28
+ }>;
29
+ }
30
+ /**
31
+ * What `default` means on this harness, in lines under the table (OSK-12114).
32
+ *
33
+ * Three candidates can answer "which model runs on `default`": the harness
34
+ * row's stored default, the harness's own configuration on that machine (for
35
+ * Codex, `model` in its config.toml), and the vendor's recommendation that the
36
+ * live list marks. They win in that order, and the list alone cannot see the
37
+ * middle one — so say the order rather than imply the marked model runs.
38
+ */
39
+ export declare function describeModelDefaults(report: HarnessModelsReport | undefined): string[];
40
+ /**
41
+ * Stated under every curated answer: the list is ours, not the harness's
42
+ * admission rule. Without it `curated` reads as "these are the only choices"
43
+ * — the web card has always said this; the CLI never did.
44
+ */
45
+ export declare const CURATED_ACCEPTS_ANY_ID = "Any model id the harness itself accepts works too, even if it is not listed \u2014 set it with `skrr agents update <agent> --model <id>`.";
46
+ /**
47
+ * Why a curated answer is curated, as a clause. `codex · curated` used to
48
+ * print identically for an offline daemon, a daemon too old to probe, and a
49
+ * probe that timed out — three different next actions (OSK-12062).
50
+ */
51
+ export declare function curatedReasonClause(report: HarnessModelsReport | undefined): string;
52
+ /** One line saying where the list came from. */
53
+ export declare function describeModelSource(report: HarnessModelsReport | undefined): string;
54
+ /** What to print when the answer carries no models at all. */
55
+ export declare function describeEmptyModels(report: HarnessModelsReport | undefined): string;
2
56
  /**
3
57
  * Which models this harness can actually run.
4
58
  *