@skrr-ai/cli 0.1.108 → 0.1.110

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 (202) hide show
  1. package/README.md +10 -0
  2. package/dist/base-command.d.ts +74 -0
  3. package/dist/base-command.js +182 -24
  4. package/dist/commands/agents/triggers/create.js +1 -1
  5. package/dist/commands/agents/triggers/update.js +1 -1
  6. package/dist/commands/agents/update.d.ts +25 -0
  7. package/dist/commands/agents/update.js +98 -3
  8. package/dist/commands/commitments/accounting.js +7 -2
  9. package/dist/commands/commitments/cost.d.ts +11 -0
  10. package/dist/commands/commitments/cost.js +26 -3
  11. package/dist/commands/commitments/create.js +2 -1
  12. package/dist/commands/commitments/operating-review.d.ts +15 -0
  13. package/dist/commands/commitments/operating-review.js +103 -50
  14. package/dist/commands/commitments/update.js +12 -5
  15. package/dist/commands/daemon/install.js +17 -1
  16. package/dist/commands/daemon/login.js +14 -0
  17. package/dist/commands/doctor.js +9 -0
  18. package/dist/commands/goals/list.js +6 -1
  19. package/dist/commands/grants/rotate.js +4 -0
  20. package/dist/commands/harnesses/prune.d.ts +26 -0
  21. package/dist/commands/harnesses/prune.js +172 -0
  22. package/dist/commands/login.js +53 -14
  23. package/dist/commands/machines/list.js +28 -6
  24. package/dist/commands/projects/show.js +1 -1
  25. package/dist/commands/revoke-daemon.js +2 -1
  26. package/dist/commands/spaces/workflows/archive.d.ts +1 -0
  27. package/dist/commands/spaces/workflows/archive.js +1 -0
  28. package/dist/commands/spaces/workflows/artifact.d.ts +1 -0
  29. package/dist/commands/spaces/workflows/artifact.js +1 -0
  30. package/dist/commands/spaces/workflows/cancel-node.d.ts +1 -0
  31. package/dist/commands/spaces/workflows/cancel-node.js +1 -0
  32. package/dist/commands/spaces/workflows/cancel.d.ts +1 -0
  33. package/dist/commands/spaces/workflows/cancel.js +6 -2
  34. package/dist/commands/spaces/workflows/create.d.ts +1 -0
  35. package/dist/commands/spaces/workflows/create.js +7 -1
  36. package/dist/commands/spaces/workflows/events.d.ts +1 -0
  37. package/dist/commands/spaces/workflows/events.js +1 -0
  38. package/dist/commands/spaces/workflows/fixtures/create.d.ts +1 -0
  39. package/dist/commands/spaces/workflows/fixtures/create.js +1 -0
  40. package/dist/commands/spaces/workflows/fixtures/list.d.ts +1 -0
  41. package/dist/commands/spaces/workflows/fixtures/list.js +1 -0
  42. package/dist/commands/spaces/workflows/lifecycle-diagnostics.d.ts +1 -0
  43. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +1 -0
  44. package/dist/commands/spaces/workflows/list.d.ts +1 -0
  45. package/dist/commands/spaces/workflows/list.js +1 -0
  46. package/dist/commands/spaces/workflows/node-types.d.ts +2 -0
  47. package/dist/commands/spaces/workflows/node-types.js +24 -6
  48. package/dist/commands/spaces/workflows/pause.d.ts +1 -0
  49. package/dist/commands/spaces/workflows/pause.js +1 -0
  50. package/dist/commands/spaces/workflows/plan/show.d.ts +1 -0
  51. package/dist/commands/spaces/workflows/plan/show.js +1 -0
  52. package/dist/commands/spaces/workflows/plan/update.d.ts +1 -0
  53. package/dist/commands/spaces/workflows/plan/update.js +1 -0
  54. package/dist/commands/spaces/workflows/proposals/apply.d.ts +1 -0
  55. package/dist/commands/spaces/workflows/proposals/apply.js +1 -0
  56. package/dist/commands/spaces/workflows/proposals/confirm.d.ts +1 -0
  57. package/dist/commands/spaces/workflows/proposals/confirm.js +1 -0
  58. package/dist/commands/spaces/workflows/proposals/create.d.ts +1 -0
  59. package/dist/commands/spaces/workflows/proposals/create.js +1 -0
  60. package/dist/commands/spaces/workflows/proposals/dismiss.d.ts +1 -0
  61. package/dist/commands/spaces/workflows/proposals/dismiss.js +1 -0
  62. package/dist/commands/spaces/workflows/proposals/list.d.ts +1 -0
  63. package/dist/commands/spaces/workflows/proposals/list.js +1 -0
  64. package/dist/commands/spaces/workflows/publish.d.ts +1 -0
  65. package/dist/commands/spaces/workflows/publish.js +9 -0
  66. package/dist/commands/spaces/workflows/reconcile.d.ts +1 -0
  67. package/dist/commands/spaces/workflows/reconcile.js +1 -0
  68. package/dist/commands/spaces/workflows/resume.d.ts +1 -0
  69. package/dist/commands/spaces/workflows/resume.js +1 -0
  70. package/dist/commands/spaces/workflows/retry.d.ts +1 -0
  71. package/dist/commands/spaces/workflows/retry.js +1 -0
  72. package/dist/commands/spaces/workflows/run.d.ts +1 -0
  73. package/dist/commands/spaces/workflows/run.js +14 -7
  74. package/dist/commands/spaces/workflows/runs.d.ts +1 -0
  75. package/dist/commands/spaces/workflows/runs.js +1 -0
  76. package/dist/commands/spaces/workflows/show.d.ts +25 -0
  77. package/dist/commands/spaces/workflows/show.js +42 -4
  78. package/dist/commands/spaces/workflows/signal.d.ts +12 -0
  79. package/dist/commands/spaces/workflows/signal.js +40 -2
  80. package/dist/commands/spaces/workflows/start.d.ts +1 -0
  81. package/dist/commands/spaces/workflows/start.js +4 -1
  82. package/dist/commands/spaces/workflows/task-execution/create.d.ts +1 -0
  83. package/dist/commands/spaces/workflows/task-execution/create.js +1 -0
  84. package/dist/commands/spaces/workflows/test.d.ts +1 -0
  85. package/dist/commands/spaces/workflows/test.js +1 -0
  86. package/dist/commands/spaces/workflows/triggers/create.d.ts +1 -0
  87. package/dist/commands/spaces/workflows/triggers/create.js +1 -0
  88. package/dist/commands/spaces/workflows/triggers/fire.d.ts +1 -0
  89. package/dist/commands/spaces/workflows/triggers/fire.js +1 -0
  90. package/dist/commands/spaces/workflows/triggers/list.d.ts +1 -0
  91. package/dist/commands/spaces/workflows/triggers/list.js +1 -0
  92. package/dist/commands/spaces/workflows/triggers/update.d.ts +1 -0
  93. package/dist/commands/spaces/workflows/triggers/update.js +1 -0
  94. package/dist/commands/spaces/workflows/update.d.ts +1 -0
  95. package/dist/commands/spaces/workflows/update.js +4 -0
  96. package/dist/commands/spaces/workflows/validate.d.ts +1 -0
  97. package/dist/commands/spaces/workflows/validate.js +27 -2
  98. package/dist/commands/tasks/activity.js +17 -3
  99. package/dist/commands/tasks/bulk-update.d.ts +1 -0
  100. package/dist/commands/tasks/bulk-update.js +7 -0
  101. package/dist/commands/tasks/complete.js +2 -1
  102. package/dist/commands/tasks/create-tree.d.ts +69 -0
  103. package/dist/commands/tasks/create-tree.js +413 -47
  104. package/dist/commands/tasks/list.d.ts +2 -0
  105. package/dist/commands/tasks/list.js +25 -3
  106. package/dist/commands/tasks/rearm-recurrence.d.ts +22 -0
  107. package/dist/commands/tasks/rearm-recurrence.js +87 -0
  108. package/dist/commands/tasks/show.d.ts +9 -0
  109. package/dist/commands/tasks/show.js +79 -33
  110. package/dist/commands/tasks/timeline.js +4 -0
  111. package/dist/commands/tasks/unschedule.js +5 -1
  112. package/dist/commands/tasks/update.d.ts +1 -0
  113. package/dist/commands/tasks/update.js +17 -2
  114. package/dist/commands/triggers/audit.js +12 -3
  115. package/dist/commands/triggers/create.js +9 -3
  116. package/dist/commands/triggers/fire-detail.d.ts +7 -0
  117. package/dist/commands/triggers/fire-detail.js +30 -12
  118. package/dist/commands/triggers/fire.js +13 -3
  119. package/dist/commands/triggers/parse-intent.d.ts +7 -0
  120. package/dist/commands/triggers/parse-intent.js +31 -9
  121. package/dist/commands/triggers/update.js +1 -1
  122. package/dist/commands/workspaces/grants.js +20 -3
  123. package/dist/commands/workspaces/members.d.ts +20 -0
  124. package/dist/commands/workspaces/members.js +64 -0
  125. package/dist/commands/workspaces/stewardship.js +14 -2
  126. package/dist/help.js +12 -0
  127. package/dist/hooks/command-not-found.js +27 -1
  128. package/dist/lib/command-miss.d.ts +52 -2
  129. package/dist/lib/command-miss.js +187 -18
  130. package/dist/lib/commitment-loop-status.d.ts +8 -0
  131. package/dist/lib/commitment-loop-status.js +45 -0
  132. package/dist/lib/commitment-product.js +5 -2
  133. package/dist/lib/commitments.d.ts +18 -3
  134. package/dist/lib/commitments.js +85 -45
  135. package/dist/lib/daemon-command-hint.d.ts +15 -0
  136. package/dist/lib/daemon-command-hint.js +21 -0
  137. package/dist/lib/daemon-machine-identity-copy.d.ts +14 -0
  138. package/dist/lib/daemon-machine-identity-copy.js +27 -0
  139. package/dist/lib/daemon-machine-identity.d.ts +27 -0
  140. package/dist/lib/daemon-machine-identity.js +61 -0
  141. package/dist/lib/daemon-setup.js +10 -1
  142. package/dist/lib/daemonBrokerRefusal.js +3 -2
  143. package/dist/lib/daemonHandoff.d.ts +19 -1
  144. package/dist/lib/daemonHandoff.js +61 -1
  145. package/dist/lib/deployed-build.d.ts +43 -0
  146. package/dist/lib/deployed-build.js +66 -0
  147. package/dist/lib/grants.d.ts +27 -1
  148. package/dist/lib/grants.js +39 -21
  149. package/dist/lib/harnesses.d.ts +71 -0
  150. package/dist/lib/harnesses.js +70 -0
  151. package/dist/lib/json-mode.d.ts +11 -0
  152. package/dist/lib/json-mode.js +19 -0
  153. package/dist/lib/json-output.d.ts +25 -0
  154. package/dist/lib/json-output.js +30 -0
  155. package/dist/lib/list-output.d.ts +10 -1
  156. package/dist/lib/list-output.js +22 -1
  157. package/dist/lib/local-daemon-readiness.d.ts +90 -0
  158. package/dist/lib/local-daemon-readiness.js +143 -0
  159. package/dist/lib/machines.d.ts +117 -0
  160. package/dist/lib/machines.js +139 -0
  161. package/dist/lib/managed-instruction-local.d.ts +2 -0
  162. package/dist/lib/managed-instruction-local.js +2 -0
  163. package/dist/lib/profile-machine-identity-guard.d.ts +18 -0
  164. package/dist/lib/profile-machine-identity-guard.js +39 -0
  165. package/dist/lib/request-field-flags.d.ts +20 -0
  166. package/dist/lib/request-field-flags.js +34 -0
  167. package/dist/lib/stewardship-output.d.ts +1 -1
  168. package/dist/lib/stewardship-output.js +26 -30
  169. package/dist/lib/task-batch.d.ts +61 -4
  170. package/dist/lib/task-batch.js +316 -80
  171. package/dist/lib/task-economics.d.ts +15 -0
  172. package/dist/lib/task-economics.js +39 -2
  173. package/dist/lib/task-recurrence-outcome.d.ts +6 -6
  174. package/dist/lib/task-recurrence-outcome.js +27 -3
  175. package/dist/lib/tasks.d.ts +39 -0
  176. package/dist/lib/tasks.js +132 -0
  177. package/dist/lib/triggers.d.ts +42 -1
  178. package/dist/lib/triggers.js +98 -2
  179. package/dist/lib/workflow-issues.d.ts +14 -0
  180. package/dist/lib/workflow-issues.js +32 -0
  181. package/dist/lib/workflow-run-view.d.ts +59 -0
  182. package/dist/lib/workflow-run-view.js +110 -0
  183. package/dist/lib/workspaces.d.ts +48 -4
  184. package/dist/lib/workspaces.js +55 -0
  185. package/dist/lib/write-target.d.ts +29 -0
  186. package/dist/lib/write-target.js +72 -0
  187. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  188. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +5 -3
  189. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +60 -0
  190. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +130 -6
  191. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.d.ts +51 -0
  192. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.js +50 -0
  193. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  194. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
  195. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +60 -0
  196. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +128 -6
  197. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.d.ts +51 -0
  198. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.js +46 -0
  199. package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
  200. package/dist/node_modules/@skrr-ai/data-provider/index.js +7523 -7140
  201. package/oclif.manifest.json +473 -59
  202. package/package.json +28 -1
@@ -2,6 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AGENT_FENCE_RETRY_CODES = void 0;
4
4
  exports.agentUpdateWarnings = agentUpdateWarnings;
5
+ exports.agentRowFromUpdate = agentRowFromUpdate;
6
+ exports.alwaysOnReceiptLine = alwaysOnReceiptLine;
7
+ exports.writeReceiptWarnings = writeReceiptWarnings;
5
8
  exports.retryOnceOnAgentFence = retryOnceOnAgentFence;
6
9
  exports.defaultPermissionModePatch = defaultPermissionModePatch;
7
10
  exports.defaultPermissionModeNote = defaultPermissionModeNote;
@@ -184,7 +187,9 @@ class AgentsUpdate extends base_command_1.BaseCommand {
184
187
  this.error(err.message, { exit: 1 });
185
188
  }
186
189
  if (Object.keys(payload).length === 0) {
187
- this.error('Nothing to update. Pass at least one flag (e.g. --name) or --from-json.', { exit: 1 });
190
+ this.error('Nothing to update. Pass at least one flag (e.g. --name) or --from-json.', {
191
+ exit: 1,
192
+ });
188
193
  }
189
194
  let updated;
190
195
  try {
@@ -209,8 +214,26 @@ class AgentsUpdate extends base_command_1.BaseCommand {
209
214
  this.log(JSON.stringify((0, web_url_1.withAgentUrl)(this.cliConfig.baseURL, updated), null, 2));
210
215
  return;
211
216
  }
212
- const a = updated;
213
- this.log(`Updated agent ${a.id ?? a._id ?? args.id} — ${a.name ?? '(no name)'}`);
217
+ // The Always-On control replies with its own envelope, which names no agent.
218
+ // Name it from the Agent's own row, not "(no name)" (OSK-16477).
219
+ const a = agentRowFromUpdate(updated) ?? { id: args.id };
220
+ if (typeof a.name !== 'string') {
221
+ try {
222
+ const row = (await data_provider_1.dataService.getAgentById({ agent_id: args.id }));
223
+ if (row &&
224
+ typeof row === 'object' &&
225
+ typeof row.name === 'string') {
226
+ a.name = row.name;
227
+ }
228
+ }
229
+ catch {
230
+ // The update already happened; a failed name read only costs the label.
231
+ }
232
+ }
233
+ this.log(`Updated agent ${a.id ?? a._id ?? args.id} — ${typeof a.name === 'string' ? a.name || '(no name)' : '(name unavailable)'}`);
234
+ const alwaysOnLine = alwaysOnReceiptLine(payload, updated);
235
+ if (alwaysOnLine)
236
+ this.log(alwaysOnLine);
214
237
  this.log(`URL: ${(0, web_url_1.withAgentUrl)(this.cliConfig.baseURL, { ...a, id: a.id ?? a._id ?? args.id }).url ?? '-'}`);
215
238
  const modeNote = defaultPermissionModeNote(flags['default-permission-mode'], a);
216
239
  if (modeNote)
@@ -240,9 +263,18 @@ exports.default = AgentsUpdate;
240
263
  * through `--from-json` was reported as "not an agent field" after the server
241
264
  * had cleared it on purpose (OSK-12167).
242
265
  *
266
+ * All of that is the fallback for a server that predates `writeReceipt`. When
267
+ * the response carries a receipt, the server's own account of each key is used
268
+ * instead and nothing is inferred from the response's shape (OSK-15070).
269
+ *
243
270
  * Exported for tests.
244
271
  */
245
272
  function agentUpdateWarnings(payload, updated) {
273
+ // A server that reports what it did with each key is the authority; the
274
+ // shape comparison below is only for a server older than the receipt.
275
+ const receipt = (0, data_provider_1.readAgentWriteReceipt)(updated);
276
+ if (receipt)
277
+ return writeReceiptWarnings(payload, receipt);
246
278
  const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
247
279
  const controlReply = isRecord(updated) &&
248
280
  typeof updated.agentId === 'string' &&
@@ -273,6 +305,69 @@ function agentUpdateWarnings(payload, updated) {
273
305
  ...(unapplied.length > 0 ? [(0, create_1.unappliedPayloadKeysWarning)(unapplied)] : []),
274
306
  ];
275
307
  }
308
+ /**
309
+ * The Agent row inside an update reply: the reply itself for an ordinary
310
+ * PATCH, `agent` inside the Always-On control envelope, or null when the
311
+ * control reply carries none (a PATCH of `isAlwaysOn` alone). Exported for tests.
312
+ */
313
+ function agentRowFromUpdate(updated) {
314
+ if (!updated || typeof updated !== 'object' || Array.isArray(updated))
315
+ return null;
316
+ const reply = updated;
317
+ const isControlReply = typeof reply.agentId === 'string' && reply.state !== null && typeof reply.state === 'object';
318
+ if (!isControlReply)
319
+ return { ...reply };
320
+ const agent = reply.agent;
321
+ return agent && typeof agent === 'object' && !Array.isArray(agent)
322
+ ? { ...agent, id: agent.id ?? reply.agentId }
323
+ : { id: reply.agentId };
324
+ }
325
+ /**
326
+ * One line for an `isAlwaysOn` write, from the receipt: applied, or already in
327
+ * that state so nothing changed (OSK-16477). Exported for tests.
328
+ */
329
+ function alwaysOnReceiptLine(payload, updated) {
330
+ if (typeof payload.isAlwaysOn !== 'boolean')
331
+ return null;
332
+ const receipt = (0, data_provider_1.readAgentWriteReceipt)(updated);
333
+ if (!receipt)
334
+ return null;
335
+ const state = payload.isAlwaysOn ? 'on' : 'off';
336
+ if (receipt.unchanged.some((entry) => entry.field === 'isAlwaysOn')) {
337
+ return `Always-On: already ${state}; nothing changed.`;
338
+ }
339
+ if (receipt.applied.some((entry) => entry.field === 'isAlwaysOn')) {
340
+ return `Always-On: turned ${state}.`;
341
+ }
342
+ return null;
343
+ }
344
+ /**
345
+ * Warnings from the server's `writeReceipt` (OSK-15070). The receipt names
346
+ * every key the write saw as applied (and through which control: the legacy
347
+ * `isAlwaysOn` goes to the Always-On control and is applied, not "not kept") or
348
+ * ignored with a reason. A sent key it does not mention never reached the write.
349
+ *
350
+ * Exported for tests.
351
+ */
352
+ function writeReceiptWarnings(payload, receipt) {
353
+ const explained = receipt.ignored
354
+ .filter((entry) => entry.reason !== 'not_an_agent_field')
355
+ .map((entry) => `${entry.field}: ${entry.detail || 'ignored by the server'}`);
356
+ const notFields = receipt.ignored
357
+ .filter((entry) => entry.reason === 'not_an_agent_field')
358
+ .map((entry) => entry.field);
359
+ const unreported = (0, data_provider_1.unreportedAgentWriteFields)(payload, receipt);
360
+ const warnings = [...explained];
361
+ if (notFields.length > 0)
362
+ warnings.push((0, create_1.unappliedPayloadKeysWarning)(notFields));
363
+ if (unreported.length > 0) {
364
+ const one = unreported.length === 1;
365
+ warnings.push(`The server did not receive ${unreported.join(', ')}: a request filter removed ` +
366
+ `${one ? 'it' : 'them'} before the write, so nothing was changed for ${one ? 'it' : 'them'}. ` +
367
+ '`skrr agents show <id> --json` shows what the agent stores.');
368
+ }
369
+ return warnings;
370
+ }
276
371
  /**
277
372
  * The per-Agent mutation fence's "another request holds this right now"
278
373
  * refusals. `updateAgent` takes a short lease on the Agent before it writes, so
@@ -4,6 +4,7 @@ exports.formatAccountingReadout = formatAccountingReadout;
4
4
  const core_1 = require("@oclif/core");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const commitments_1 = require("../../lib/commitments");
7
+ const cost_1 = require("./cost");
7
8
  /**
8
9
  * `skrr commitments accounting <id>` — the allowance-accounting SHADOW readout.
9
10
  *
@@ -92,14 +93,18 @@ function formatAccountingReadout(readout) {
92
93
  // a priced window and an unpriced one read alike (OSK-10940).
93
94
  const costStatus = readout.costStatus ?? 'not_applicable';
94
95
  const costCaveat = {
95
- reported: 'every run reported a cost',
96
+ reported: "every run's cost is known",
96
97
  partial: 'some runs reported no cost — the total is a FLOOR',
97
98
  unavailable: 'no run reported a cost — the total is meaningless',
98
99
  not_applicable: 'no closed runs in the window',
99
100
  };
100
101
  lines.push(`Window cost: $${(Number(readout.costUsdMicros || 0) / 1_000_000).toFixed(4)}` +
101
102
  ` — ${costStatus}: ${costCaveat[costStatus] ?? 'unrecognized cost status'}` +
102
- ` (${Number(readout.costReportedRunCount || 0)} of ${Number(readout.costRunCount || 0)} runs priced)`);
103
+ ` (${Number(readout.costReportedRunCount || 0)} of ${Number(readout.costRunCount || 0)} runs priced` +
104
+ `, ${Number(readout.costNoSpendRunCount || 0)} refused before starting)`);
105
+ const noSpendNote = (0, cost_1.noSpendInferenceNote)(Number(readout.costNoSpendRunCount || 0), Number(readout.costNoSpendInferredRunCount || 0));
106
+ if (noSpendNote)
107
+ lines.push(noSpendNote);
103
108
  const multiplier = readout.recommendedAttemptMultiplier;
104
109
  lines.push('');
105
110
  lines.push(`Attempt multiplier: ${multiplier == null ? 'not derivable yet' : multiplier}` +
@@ -60,3 +60,14 @@ export default class CommitmentsCost extends BaseCommand {
60
60
  * formed a belief by the time they reach the caveat.
61
61
  */
62
62
  export declare function formatCostWindow(data: CommitmentCostWindow): string[];
63
+ /**
64
+ * The line that says when "cost nothing" rests on inference (OSK-15500).
65
+ *
66
+ * A Run refused before any model turn records that fact when it is refused.
67
+ * Runs written before the fact was recorded are judged by the platform's legacy
68
+ * inference (admission-denied status, a pre-start refusal code, cancelled before
69
+ * any claim). Both are counted as $0 by the live budget, the spend ledger and
70
+ * this read alike — but a reader must be able to tell a recorded fact from an
71
+ * inferred one, so an inferred share is always printed, never folded in.
72
+ */
73
+ export declare function noSpendInferenceNote(noSpend: number, inferred: number): string | null;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveCostWindow = resolveCostWindow;
4
4
  exports.formatCostWindow = formatCostWindow;
5
+ exports.noSpendInferenceNote = noSpendInferenceNote;
5
6
  const core_1 = require("@oclif/core");
6
7
  const base_command_1 = require("../../base-command");
7
8
  const commitments_1 = require("../../lib/commitments");
@@ -113,18 +114,40 @@ function formatCostWindow(data) {
113
114
  const micros = Number(data.costUsdMicros || 0);
114
115
  const runs = Number(data.runCount || 0);
115
116
  const unattributed = Number(data.unavailableRunCount || 0);
117
+ const noSpend = Number(data.noSpendRunCount || 0);
116
118
  const status = data.costStatus ?? 'not_applicable';
117
119
  const caveat = {
118
- reported: 'every run in the window reported a cost',
120
+ reported: "every run's cost in the window is known",
119
121
  partial: 'some runs reported no cost — the total below is a FLOOR',
120
122
  unavailable: 'no run in the window reported a cost — the total below is meaningless',
121
123
  not_applicable: 'no closed runs in the window',
122
124
  };
123
- return [
125
+ const lines = [
124
126
  `Cost for ${data.commitmentId ?? '-'}`,
125
127
  `Window: ${data.from ?? '-'} → ${data.to ?? '-'} (${Number(data.dayCount || 0)} closed day(s))`,
126
128
  `Attribution: ${status} — ${caveat[status] ?? 'unrecognized cost status'}`,
127
129
  `Cost: $${(micros / 1_000_000).toFixed(4)}`,
128
- `Runs: ${runs} (${Number(data.reportedRunCount || 0)} with cost, ${unattributed} without)`,
130
+ `Runs: ${runs} (${Number(data.reportedRunCount || 0)} with cost, ` +
131
+ `${noSpend} refused before starting, ${unattributed} without)`,
129
132
  ];
133
+ const note = noSpendInferenceNote(noSpend, Number(data.noSpendInferredRunCount || 0));
134
+ if (note)
135
+ lines.push(note);
136
+ return lines;
137
+ }
138
+ /**
139
+ * The line that says when "cost nothing" rests on inference (OSK-15500).
140
+ *
141
+ * A Run refused before any model turn records that fact when it is refused.
142
+ * Runs written before the fact was recorded are judged by the platform's legacy
143
+ * inference (admission-denied status, a pre-start refusal code, cancelled before
144
+ * any claim). Both are counted as $0 by the live budget, the spend ledger and
145
+ * this read alike — but a reader must be able to tell a recorded fact from an
146
+ * inferred one, so an inferred share is always printed, never folded in.
147
+ */
148
+ function noSpendInferenceNote(noSpend, inferred) {
149
+ if (!(inferred > 0))
150
+ return null;
151
+ return (`Note: ${inferred} of the ${noSpend} run(s) refused before starting are counted ` +
152
+ 'as $0 by inference (recorded before refusals were stored), not from a recorded refusal');
130
153
  }
@@ -156,7 +156,8 @@ function createCadenceFromFlags({ eventDriven, cadence: cadenceInput, cadenceMod
156
156
  * For a natural-language draft instead of flags, use `skrr commitments plan`.
157
157
  */
158
158
  class CommitmentsCreate extends base_command_1.BaseCommand {
159
- static description = 'Create a commitment (a durable agent contract)';
159
+ static description = 'Create a commitment (a durable agent contract). Its cadence is manual, an interval or daily clock times; ' +
160
+ 'it has no weekday, holiday, business-day or trading-calendar fields';
160
161
  static examples = [
161
162
  '<%= config.bin %> commitments create --agent <agent-id> --title "Ship v2" --outcome "v2 is live for all users" --cadence 6h',
162
163
  '<%= config.bin %> commitments create --agent <agent-id> --title "Hiring loop" --outcome "Every candidate hears back in 48h" --watch tasks --watch email --cadence 12h',
@@ -1,5 +1,7 @@
1
+ import type { CommitmentLoopStatus } from '@skrr-ai/data-provider';
1
2
  import { BaseCommand } from '../../base-command';
2
3
  type SystemCommitmentRow = {
4
+ id?: string;
3
5
  systemRole?: string;
4
6
  status?: string;
5
7
  lastCheckedAt?: string;
@@ -13,6 +15,19 @@ type SystemCommitmentRow = {
13
15
  * (OSK-15438): an Agent that cannot run cannot have asked.
14
16
  */
15
17
  export declare function noQuestionsSentence(alwaysOn: boolean, roles: SystemCommitmentRow[]): string;
18
+ /**
19
+ * What the roster said about this Agent's loops, or why it could not be read. The review's own rows
20
+ * carry only the stored lifecycle status; whether a loop is armed, when it next wakes, why it last
21
+ * stopped and whether it can read its sources come from the roster (OSK-15438, OSK-15412).
22
+ */
23
+ export type OperatingReviewLoops = {
24
+ rows: Map<string, CommitmentLoopStatus>;
25
+ truncated: boolean;
26
+ } | {
27
+ error: string;
28
+ };
29
+ /** The review as text. Pure, so the whole output can be pinned. */
30
+ export declare function formatOperatingReview(review: Record<string, unknown>, loops: OperatingReviewLoops): string[];
16
31
  export default class CommitmentsOperatingReview extends BaseCommand {
17
32
  static description: string;
18
33
  static args: {
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noQuestionsSentence = noQuestionsSentence;
4
+ exports.formatOperatingReview = formatOperatingReview;
4
5
  const core_1 = require("@oclif/core");
5
6
  const base_command_1 = require("../../base-command");
7
+ const commitment_loop_status_1 = require("../../lib/commitment-loop-status");
6
8
  const commitments_1 = require("../../lib/commitments");
7
9
  /** What each silent loop is for, in the owner's terms rather than the role key. */
8
10
  const ROLE_PURPOSE = {
@@ -39,6 +41,79 @@ function noQuestionsSentence(alwaysOn, roles) {
39
41
  ? 'None. It has not told you anything is missing.'
40
42
  : 'None recorded yet. None of its loops has checked, so this says nothing about whether anything is missing.';
41
43
  }
44
+ /** The review as text. Pure, so the whole output can be pinned. */
45
+ function formatOperatingReview(review, loops) {
46
+ const lines = [];
47
+ const roles = review.systemCommitments || [];
48
+ const alwaysOn = review.alwaysOn === true;
49
+ const questions = review.openQuestions || [];
50
+ const portfolio = review.portfolio || {};
51
+ const metrics = review.metrics || {};
52
+ lines.push(`Agent ${review.agentId}`);
53
+ lines.push('');
54
+ // A loop's own status says `active`, but a system-managed loop is only
55
+ // given a scheduling trigger while the Agent is Always-On. Printing
56
+ // `active` beside "last checked never" without saying that read as
57
+ // "nothing has happened yet" rather than "this cannot happen", and nothing
58
+ // led the owner to the switch that decides it.
59
+ if (!alwaysOn) {
60
+ lines.push('Always-On is OFF — these loops are provisioned but will not run.');
61
+ lines.push(` skrr agents always-on enable ${review.agentId}`);
62
+ lines.push('');
63
+ }
64
+ lines.push('Background loops');
65
+ if (roles.length === 0) {
66
+ lines.push(' (none provisioned)');
67
+ }
68
+ if ('error' in loops) {
69
+ // "Cannot see" must not read as "nothing to report": say what is missing and what it hides.
70
+ lines.push(` Loop detail unavailable (${loops.error}). The states below are the stored lifecycle only; ` +
71
+ 'they do not say whether a loop is armed, what it can read, or why it last stopped.');
72
+ }
73
+ for (const role of roles) {
74
+ const key = role.systemRole || '?';
75
+ const purpose = ROLE_PURPOSE[key] ? ` — ${ROLE_PURPOSE[key]}` : '';
76
+ const contact = role.maxUserNudgesPerDay && role.maxUserNudgesPerDay > 0
77
+ ? `may contact you ${role.maxUserNudgesPerDay}/day`
78
+ : 'never contacts you';
79
+ // `idle` replaces `active` only. A paused loop is paused for its own
80
+ // reason — Relationship Pulse ships paused as a separate consent
81
+ // decision — and collapsing that into `idle` would hide a distinction
82
+ // the owner needs when they do turn Always-On on.
83
+ const state = !alwaysOn && role.status === 'active' ? 'idle' : role.status || '?';
84
+ lines.push(` ${state.padEnd(7)} ${key}${purpose}`);
85
+ lines.push(` last checked ${ago(role.lastCheckedAt)} · ${contact}`);
86
+ if ('rows' in loops) {
87
+ const row = role.id ? loops.rows.get(role.id) : undefined;
88
+ if (row) {
89
+ for (const line of (0, commitment_loop_status_1.formatLoopSummary)(row))
90
+ lines.push(` ${line}`);
91
+ }
92
+ else {
93
+ lines.push(` loop detail: not in the roster read${loops.truncated ? ' (the roster is bounded and this Agent holds more)' : ''}; run \`skrr commitments status ${role.id ?? '<id>'}\``);
94
+ }
95
+ }
96
+ }
97
+ lines.push('');
98
+ lines.push(`Open questions from this agent (${questions.length})`);
99
+ if (questions.length === 0) {
100
+ lines.push(` ${noQuestionsSentence(alwaysOn, roles)}`);
101
+ }
102
+ for (const q of questions) {
103
+ lines.push(` • ${q.question}`);
104
+ if (q.whyItMatters)
105
+ lines.push(` why: ${q.whyItMatters}`);
106
+ lines.push(` asked ${ago(q.askedAt)}`);
107
+ }
108
+ lines.push('');
109
+ const drafts = portfolio.draftCommitmentCount ?? 0;
110
+ lines.push(`Business commitments: ${portfolio.activeCommitmentCount ?? 0} active, ${portfolio.pausedCommitmentCount ?? 0} paused${drafts > 0 ? `, ${drafts} draft` : ''}`);
111
+ if (typeof metrics.awaitingApproval === 'number') {
112
+ lines.push(`Proposals awaiting your approval: ${metrics.awaitingApproval}`);
113
+ }
114
+ lines.push(`Every commitment it holds, loop by loop: skrr commitments roster --agent ${review.agentId}`);
115
+ return lines;
116
+ }
42
117
  class CommitmentsOperatingReview extends base_command_1.BaseCommand {
43
118
  static description = 'Read an agent’s system-managed commitment operating review';
44
119
  static args = {
@@ -60,61 +135,39 @@ class CommitmentsOperatingReview extends base_command_1.BaseCommand {
60
135
  catch (error) {
61
136
  this.handleApiError(error);
62
137
  }
138
+ // The roster is a second read; its failure must not take the review down with it, and must not
139
+ // pass for "nothing to report" either.
140
+ let loops;
141
+ try {
142
+ const roster = await commitments_1.commitmentApi.roster(args.agentId);
143
+ loops = {
144
+ rows: new Map((Array.isArray(roster.commitments) ? roster.commitments : []).map((row) => [
145
+ row.commitmentId,
146
+ row,
147
+ ])),
148
+ truncated: roster.truncated === true,
149
+ };
150
+ }
151
+ catch (error) {
152
+ loops = { error: error instanceof Error ? error.message : String(error) };
153
+ }
63
154
  if (flags.json) {
64
- this.log(JSON.stringify(result, null, 2));
155
+ const review = result;
156
+ const roles = review.systemCommitments || [];
157
+ this.log(JSON.stringify('error' in loops
158
+ ? { ...review, loopStatusError: loops.error }
159
+ : {
160
+ ...review,
161
+ loopStatus: roles
162
+ .map((role) => (role.id ? loops.rows.get(role.id) : undefined))
163
+ .filter((row) => row !== undefined),
164
+ }, null, 2));
65
165
  return;
66
166
  }
67
167
  // Without this the command printed raw JSON in both modes, which is why the
68
168
  // meta loops read as machinery rather than as something an owner supervises.
69
- const review = result;
70
- const roles = review.systemCommitments || [];
71
- const alwaysOn = review.alwaysOn === true;
72
- const questions = review.openQuestions || [];
73
- const portfolio = review.portfolio || {};
74
- this.log(`Agent ${review.agentId}`);
75
- this.log('');
76
- // A loop's own status says `active`, but a system-managed loop is only
77
- // given a scheduling trigger while the Agent is Always-On. Printing
78
- // `active` beside "last checked never" without saying that read as
79
- // "nothing has happened yet" rather than "this cannot happen", and nothing
80
- // led the owner to the switch that decides it.
81
- if (!alwaysOn) {
82
- this.log('Always-On is OFF — these loops are provisioned but will not run.');
83
- this.log(` skrr agents always-on enable ${review.agentId}`);
84
- this.log('');
85
- }
86
- this.log('Background loops');
87
- if (roles.length === 0) {
88
- this.log(' (none provisioned)');
89
- }
90
- for (const role of roles) {
91
- const key = role.systemRole || '?';
92
- const purpose = ROLE_PURPOSE[key] ? ` — ${ROLE_PURPOSE[key]}` : '';
93
- const contact = role.maxUserNudgesPerDay && role.maxUserNudgesPerDay > 0
94
- ? `may contact you ${role.maxUserNudgesPerDay}/day`
95
- : 'never contacts you';
96
- // `idle` replaces `active` only. A paused loop is paused for its own
97
- // reason — Relationship Pulse ships paused as a separate consent
98
- // decision — and collapsing that into `idle` would hide a distinction
99
- // the owner needs when they do turn Always-On on.
100
- const state = !alwaysOn && role.status === 'active' ? 'idle' : role.status || '?';
101
- this.log(` ${state.padEnd(7)} ${key}${purpose}`);
102
- this.log(` last checked ${ago(role.lastCheckedAt)} · ${contact}`);
103
- }
104
- this.log('');
105
- this.log(`Open questions from this agent (${questions.length})`);
106
- if (questions.length === 0) {
107
- this.log(` ${noQuestionsSentence(alwaysOn, roles)}`);
108
- }
109
- for (const q of questions) {
110
- this.log(` • ${q.question}`);
111
- if (q.whyItMatters)
112
- this.log(` why: ${q.whyItMatters}`);
113
- this.log(` asked ${ago(q.askedAt)}`);
114
- }
115
- this.log('');
116
- const drafts = portfolio.draftCommitmentCount ?? 0;
117
- this.log(`Business commitments: ${portfolio.activeCommitmentCount ?? 0} active, ${portfolio.pausedCommitmentCount ?? 0} paused${drafts > 0 ? `, ${drafts} draft` : ''}`);
169
+ for (const line of formatOperatingReview(result, loops))
170
+ this.log(line);
118
171
  }
119
172
  }
120
173
  exports.default = CommitmentsOperatingReview;
@@ -31,7 +31,8 @@ const commitments_1 = require("../../lib/commitments");
31
31
  * the contract untouched.
32
32
  */
33
33
  class CommitmentsUpdate extends base_command_1.BaseCommand {
34
- static description = "Update a commitment's contract (title, target, cadence, sources)";
34
+ static description = "Update a commitment's contract (title, target, cadence, sources). A cadence is manual, an interval or daily clock times; " +
35
+ 'it has no weekday, holiday, business-day or trading-calendar fields';
35
36
  static examples = [
36
37
  '<%= config.bin %> commitments update <commitment-id> --cadence 12h',
37
38
  '<%= config.bin %> commitments update <commitment-id> --title "Ship v2 (Q3)" --outcome "v2 is live for all users"',
@@ -275,9 +276,13 @@ JSON
275
276
  // only for a binding it still names; explicit JSON is the authored base.
276
277
  const declarations = Array.isArray(payload.watchedSources)
277
278
  ? (0, commitments_1.normalizeWatchedSourceDeclarations)(payload.watchedSources)
278
- : current?.watchedSources ?? [];
279
+ : (current?.watchedSources ?? []);
279
280
  payload.watchedSources = built.map((next) => {
280
- const scopeKey = next.source === 'space' ? 'spaceId' : next.source === 'monitor' ? 'monitorId' : undefined;
281
+ const scopeKey = next.source === 'space'
282
+ ? 'spaceId'
283
+ : next.source === 'monitor'
284
+ ? 'monitorId'
285
+ : undefined;
281
286
  const nextConfig = next.config;
282
287
  const prior = declarations.find((binding) => binding?.source === next.source &&
283
288
  (!scopeKey || binding.config?.[scopeKey] === nextConfig?.[scopeKey]));
@@ -301,7 +306,9 @@ JSON
301
306
  storedCadence[key] = existing[key];
302
307
  }
303
308
  if (payload.cadence !== undefined) {
304
- if (!payload.cadence || typeof payload.cadence !== 'object' || Array.isArray(payload.cadence)) {
309
+ if (!payload.cadence ||
310
+ typeof payload.cadence !== 'object' ||
311
+ Array.isArray(payload.cadence)) {
305
312
  this.error('cadence must be a JSON object when using cadence flags.', { exit: 1 });
306
313
  }
307
314
  }
@@ -403,7 +410,7 @@ JSON
403
410
  this.log(JSON.stringify((0, web_url_1.withCommitmentUrl)(this.cliConfig.baseURL, commitment), null, 2));
404
411
  return;
405
412
  }
406
- this.log('Updated commitment:');
413
+ this.log((0, commitments_1.describeCommitmentRevision)(commitment?.revision));
407
414
  (0, commitments_1.renderCommitmentSummary)(commitment, (line) => this.log(line));
408
415
  }
409
416
  }
@@ -5,6 +5,9 @@ const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
5
5
  const daemon_binding_1 = require("../../lib/daemon-binding");
6
6
  const daemon_installer_1 = require("../../lib/daemon-installer");
7
7
  const daemon_setup_1 = require("../../lib/daemon-setup");
8
+ const daemonHandoff_1 = require("../../lib/daemonHandoff");
9
+ const daemon_machine_identity_copy_1 = require("../../lib/daemon-machine-identity-copy");
10
+ const live_runtime_binary_1 = require("../../lib/live-runtime-binary");
8
11
  /**
9
12
  * `skrr daemon install` — install the local runtime's OS service.
10
13
  *
@@ -83,7 +86,20 @@ class DaemonInstall extends core_1.Command {
83
86
  // moment the binding is chosen, and leaving it to a separate command
84
87
  // is how a CLI and its daemon end up on different deployments
85
88
  // (OSK-279) without anyone doing anything wrong.
86
- const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['install', ...(0, daemon_binding_1.bindToCliSession)(argv)]);
89
+ const installArgs = (0, daemon_binding_1.bindToCliSession)(argv);
90
+ // A service installed from a daemon that predates per-profile machine
91
+ // identities runs that daemon for good, and registers the profile on the
92
+ // default profile's machine row (OSK-S-440). Ask the binary about to run.
93
+ // `--help` and `--json` are not installs.
94
+ if (!installArgs.some((arg) => ['--help', '-h'].includes(arg))) {
95
+ const skew = await (0, daemonHandoff_1.checkLocalDaemonMachineIdentity)((0, live_runtime_binary_1.profileFromArgs)(installArgs));
96
+ if (skew) {
97
+ for (const line of (0, daemon_machine_identity_copy_1.describeDaemonMachineIdentitySkew)(skew))
98
+ this.logToStderr(line);
99
+ this.exit(78);
100
+ }
101
+ }
102
+ const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['install', ...installArgs]);
87
103
  if (code === 0 &&
88
104
  !argv.some((arg) => ['--json', '--help', '-h'].includes(arg))) {
89
105
  this.log('Coding-agent instructions: sign in with `skrr login`, or review installation choices with `skrr instructions setup`.');
@@ -4,6 +4,8 @@ const core_1 = require("@oclif/core");
4
4
  const daemon_binding_1 = require("../../lib/daemon-binding");
5
5
  const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
6
6
  const live_runtime_binary_1 = require("../../lib/live-runtime-binary");
7
+ const daemonHandoff_1 = require("../../lib/daemonHandoff");
8
+ const daemon_machine_identity_copy_1 = require("../../lib/daemon-machine-identity-copy");
7
9
  /**
8
10
  * `skrr daemon login` — authenticate the local runtime.
9
11
  *
@@ -53,6 +55,18 @@ class DaemonLogin extends core_1.Command {
53
55
  if (decision?.kind === 'forward-live') {
54
56
  this.warn(`Signing in with the live daemon binary ${decision.binary}.`);
55
57
  }
58
+ // The binary about to sign this profile in must register it under the
59
+ // machine identity the profile has (OSK-S-440): a daemon that predates
60
+ // per-profile identities would bind this login to the default profile's
61
+ // machine row. `--help` is not a login.
62
+ if (!boundArgv.some((arg) => ['--help', '-h'].includes(arg))) {
63
+ const skew = await (0, daemonHandoff_1.checkLocalDaemonMachineIdentity)((0, live_runtime_binary_1.profileFromArgs)(boundArgv), decision?.binary ?? pathBinary);
64
+ if (skew) {
65
+ for (const line of (0, daemon_machine_identity_copy_1.describeDaemonMachineIdentitySkew)(skew))
66
+ this.logToStderr(line);
67
+ this.exit(78);
68
+ }
69
+ }
56
70
  // `--cwd` is deliberately NOT defaulted here. `skrrd login` resolves it
57
71
  // from the installed service definition, which is the only authoritative
58
72
  // answer; guessing it from the CLI's cwd is exactly the mistake that made
@@ -49,6 +49,7 @@ const cli_runtime_1 = require("../lib/cli-runtime");
49
49
  const command_manifest_1 = require("../lib/command-manifest");
50
50
  const completion_index_1 = require("../lib/completion-index");
51
51
  const runtime_context_1 = require("../lib/runtime-context");
52
+ const deployed_build_1 = require("../lib/deployed-build");
52
53
  const outbox_1 = require("../lib/outbox");
53
54
  /**
54
55
  * What each check actually verifies, in the words a reader thinks in.
@@ -230,6 +231,9 @@ class Doctor extends base_command_1.BaseCommand {
230
231
  }
231
232
  }
232
233
  }
234
+ // OSK-15497 — which code the server is running, API and web separately.
235
+ // Public reads, no credential; a failure is reported, never an issue.
236
+ const deployed = await (0, deployed_build_1.readDeployedBuild)(this.cliConfig.baseURL);
233
237
  const now = Date.now();
234
238
  const jwtExp = this.resolvedCredential?.token
235
239
  ? (0, auth_core_1.decodeJwtExpiry)(this.resolvedCredential.token)
@@ -287,6 +291,9 @@ class Doctor extends base_command_1.BaseCommand {
287
291
  command: process.argv.slice(1).join(' '),
288
292
  },
289
293
  server: this.cliConfig.baseURL,
294
+ // The commit the server's API image and its published web shell were
295
+ // each built from. They differ whenever a release changed only one.
296
+ deployed,
290
297
  profile: (0, keychain_1.getActiveProfile)(),
291
298
  config: {
292
299
  path: (0, config_1.getConfigPath)(),
@@ -351,6 +358,8 @@ class Doctor extends base_command_1.BaseCommand {
351
358
  }
352
359
  this.log(`skrr CLI ${payload.cli.version} at ${root}`);
353
360
  this.log(`Server: ${payload.server}`);
361
+ for (const line of (0, deployed_build_1.deployedBuildLines)(deployed))
362
+ this.log(line);
354
363
  this.log(`Auth: ${payload.auth.signedIn
355
364
  ? `${account?.email ?? account?.id}`
356
365
  : serverReachability === 'unavailable'
@@ -5,6 +5,7 @@ const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const web_url_1 = require("../../lib/web-url");
7
7
  const format_1 = require("../../lib/format");
8
+ const list_output_1 = require("../../lib/list-output");
8
9
  class GoalsList extends base_command_1.BaseCommand {
9
10
  static description = 'List goals';
10
11
  static examples = [
@@ -72,7 +73,11 @@ class GoalsList extends base_command_1.BaseCommand {
72
73
  this.handleApiError(err);
73
74
  }
74
75
  if (flags.json) {
75
- this.log(JSON.stringify((0, web_url_1.withDataUrls)(this.cliConfig.baseURL, response, web_url_1.goalUrlFrom), null, 2));
76
+ // The one list envelope (`{object:'list', data, has_more, next_cursor}`), so
77
+ // a script reads `.data` here exactly as it does for tasks and projects. The
78
+ // space-scoped endpoint is not paginated and answers a bare `{data}`; filling
79
+ // the missing fields is additive, so nothing that read `.data` changes.
80
+ this.log(JSON.stringify((0, web_url_1.withDataUrls)(this.cliConfig.baseURL, (0, list_output_1.toListEnvelope)(response) ?? response, web_url_1.goalUrlFrom), null, 2));
76
81
  return;
77
82
  }
78
83
  // Both listGoals and listSpaceGoals return { data: Goal[] }. The general
@@ -51,6 +51,10 @@ class GrantsRotate extends base_command_1.BaseCommand {
51
51
  }
52
52
  this.log(`Rotated grant ${grant.id}.`);
53
53
  this.log(` variables: ${grant.credential?.envKeys.join(', ') || '—'}`);
54
+ // When the server recorded the new value: the proof the rotation landed,
55
+ // without anything derived from the value itself (OSK-15358).
56
+ if (grant.credential?.setAt)
57
+ this.log(` value set: ${grant.credential.setAt}`);
54
58
  if (!grant.access) {
55
59
  this.log(' access: not declared (rotation clears the declared class; use --access to re-declare)');
56
60
  }