@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
@@ -24,7 +24,11 @@ exports.editDistance = editDistance;
24
24
  exports.nearestCommands = nearestCommands;
25
25
  exports.helpSubjectId = helpSubjectId;
26
26
  exports.describeCommandMiss = describeCommandMiss;
27
+ exports.analyzeCommandMiss = analyzeCommandMiss;
27
28
  exports.lookupFromConfig = lookupFromConfig;
29
+ exports.resolveVerbSynonym = resolveVerbSynonym;
30
+ exports.typedCommandWords = typedCommandWords;
31
+ exports.commandMissEnvelope = commandMissEnvelope;
28
32
  /** A uuid-ish token, which is what a stray space id looks like. */
29
33
  const LOOKS_LIKE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
30
34
  const DAEMON_READINESS = [
@@ -57,8 +61,49 @@ function describeAnsweredElsewhere(config, resolved, extras) {
57
61
  if (targets.length === 0)
58
62
  return null;
59
63
  const spoken = `${config.bin} ${resolved.split(':').join(' ')}`;
60
- return (`\`${spoken}\` has no \`${extras[0]}\`. Depending on what you want to know:\n` +
61
- targets.map((t) => ` \`${config.bin} ${t.usage}\` — ${t.answers}`).join('\n'));
64
+ return {
65
+ message: `\`${spoken}\` has no \`${extras[0]}\`. Depending on what you want to know:`,
66
+ fix: targets.map((t) => ` \`${config.bin} ${t.usage}\` — ${t.answers}`).join('\n'),
67
+ suggestions: targets.map((t) => `${config.bin} ${t.usage}`),
68
+ };
69
+ }
70
+ const WHOAMI = {
71
+ id: 'whoami',
72
+ answers: 'who you are signed in as, which server, and where the credential came from',
73
+ };
74
+ const LOGIN = { id: 'login', answers: 'sign in' };
75
+ const LOGOUT = { id: 'logout', answers: 'sign out' };
76
+ const FLAT_COMMAND_FOR_TOPIC = {
77
+ auth: {
78
+ '*': [WHOAMI, LOGIN, LOGOUT],
79
+ status: [WHOAMI],
80
+ whoami: [WHOAMI],
81
+ me: [WHOAMI],
82
+ user: [WHOAMI],
83
+ account: [WHOAMI],
84
+ info: [WHOAMI],
85
+ show: [WHOAMI],
86
+ login: [LOGIN],
87
+ signin: [LOGIN],
88
+ 'sign-in': [LOGIN],
89
+ logout: [LOGOUT],
90
+ signout: [LOGOUT],
91
+ 'sign-out': [LOGOUT],
92
+ },
93
+ };
94
+ function describeFlatRedirect(config, parts, spokenId) {
95
+ const topic = FLAT_COMMAND_FOR_TOPIC[parts[0]?.toLowerCase() ?? ''];
96
+ // A real topic or command of that name answers for itself.
97
+ if (!topic || config.topicNames.includes(parts[0]) || config.findCommand(parts[0]))
98
+ return null;
99
+ const targets = (topic[(parts[1] ?? '*').toLowerCase()] ?? []).filter((target) => config.findCommand(target.id) !== undefined);
100
+ if (targets.length === 0)
101
+ return null;
102
+ return {
103
+ message: `\`${spokenId}\` is not a command; this CLI has no \`${parts[0]}\` topic.`,
104
+ fix: targets.map((t) => ` \`${config.bin} ${t.id}\` — ${t.answers}`).join('\n'),
105
+ suggestions: targets.map((t) => `${config.bin} ${t.id}`),
106
+ };
62
107
  }
63
108
  /** Cheap edit distance, bounded — this runs only on a miss. */
64
109
  function editDistance(a, b) {
@@ -135,8 +180,13 @@ function helpSubjectId(tokens, config) {
135
180
  }
136
181
  return taken.join(':');
137
182
  }
183
+ /** The text form: exactly what a miss has always printed. */
184
+ function describeCommandMiss(config, id) {
185
+ const miss = analyzeCommandMiss(config, id);
186
+ return miss.fix ? `${miss.message}\n${miss.fix}` : miss.message;
187
+ }
138
188
  /**
139
- * The message for a colon-joined id that did not resolve.
189
+ * The miss for a colon-joined id that did not resolve.
140
190
  *
141
191
  * Two shapes, in order:
142
192
  *
@@ -147,7 +197,7 @@ function helpSubjectId(tokens, config) {
147
197
  * suggest the nearest siblings under the same topic before falling back to
148
198
  * the whole command list.
149
199
  */
150
- function describeCommandMiss(config, id) {
200
+ function analyzeCommandMiss(config, id) {
151
201
  const parts = String(id ?? '')
152
202
  .split(':')
153
203
  .filter(Boolean);
@@ -169,9 +219,12 @@ function describeCommandMiss(config, id) {
169
219
  ? ' It takes `--space <id>`.'
170
220
  : '';
171
221
  const plural = extras.length === 1 ? 'argument' : 'arguments';
172
- return (`\`${spoken}\` exists, but does not take the positional ${plural} ` +
173
- `${extras.map((e) => `\`${e}\``).join(' ')}.${hint}\n` +
174
- `See \`${spoken} --help\`.`);
222
+ return {
223
+ message: `\`${spoken}\` exists, but does not take the positional ${plural} ` +
224
+ `${extras.map((e) => `\`${e}\``).join(' ')}.${hint}`,
225
+ fix: `See \`${spoken} --help\`.`,
226
+ suggestions: [spoken],
227
+ };
175
228
  }
176
229
  // Topic names are candidates too, so a near-miss on a bare topic gets routed
177
230
  // to the topic rather than nothing (OSK-4869): `skrr harness` (singular) is
@@ -200,10 +253,16 @@ function describeCommandMiss(config, id) {
200
253
  // the honest thing to report is the disagreement, which has a known cause and
201
254
  // a next step.
202
255
  if (config.commandIDs.includes(typedId) || config.topicNames.includes(typedId)) {
203
- return (`\`${spokenId}\` is registered but could not be loaded from this build.\n` +
204
- `Its compiled output is missing or stale — run \`npm --prefix cli run build\` in a ` +
205
- `development checkout, or reinstall the CLI.`);
256
+ return {
257
+ message: `\`${spokenId}\` is registered but could not be loaded from this build.`,
258
+ fix: `Its compiled output is missing or stale — run \`npm --prefix cli run build\` in a ` +
259
+ `development checkout, or reinstall the CLI.`,
260
+ suggestions: [],
261
+ };
206
262
  }
263
+ const flat = describeFlatRedirect(config, parts, spokenId);
264
+ if (flat)
265
+ return flat;
207
266
  const siblings = config.commandIDs.filter((c) => c.startsWith(`${parts[0]}:`));
208
267
  const suggestions = (siblings.length
209
268
  ? nearestCommands(parts.slice(0, 2).join(':'), siblings)
@@ -213,10 +272,12 @@ function describeCommandMiss(config, id) {
213
272
  // than the id, so it costs a suggestion slot as well as the reader's trust.
214
273
  .filter((candidate) => candidate !== typedId);
215
274
  if (suggestions.length) {
216
- return (`\`${spokenId}\` is not a command.` +
217
- `\nDid you mean: ${suggestions
218
- .map((s) => `\`${config.bin} ${s.split(':').join(' ')}\``)
219
- .join(', ')}?`);
275
+ const spoken = suggestions.map((s) => `${config.bin} ${s.split(':').join(' ')}`);
276
+ return {
277
+ message: `\`${spokenId}\` is not a command.`,
278
+ fix: `Did you mean: ${spoken.map((s) => `\`${s}\``).join(', ')}?`,
279
+ suggestions: spoken,
280
+ };
220
281
  }
221
282
  // The topic the reader typed is real, and nothing in it is spelled like what
222
283
  // they asked for — a different word for it, not a typo. "See the topics"
@@ -233,11 +294,18 @@ function describeCommandMiss(config, id) {
233
294
  .sort()
234
295
  .slice(0, MAX_TOPIC_LISTING);
235
296
  const more = siblings.length > shown.length ? ', …' : '';
236
- return (`\`${spokenId}\` is not a command.\n` +
237
- `\`${config.bin} ${topic}\` has: ${shown.map((c) => `\`${c}\``).join(', ')}${more}.\n` +
238
- `See \`${config.bin} ${topic} --help\`.`);
297
+ return {
298
+ message: `\`${spokenId}\` is not a command.`,
299
+ fix: `\`${config.bin} ${topic}\` has: ${shown.map((c) => `\`${c}\``).join(', ')}${more}.\n` +
300
+ `See \`${config.bin} ${topic} --help\`.`,
301
+ suggestions: shown.map((c) => `${config.bin} ${topic} ${c}`),
302
+ };
239
303
  }
240
- return `\`${spokenId}\` is not a command.\nRun \`${config.bin} --help\` to see the topics.`;
304
+ return {
305
+ message: `\`${spokenId}\` is not a command.`,
306
+ fix: `Run \`${config.bin} --help\` to see the topics.`,
307
+ suggestions: [],
308
+ };
241
309
  }
242
310
  /** Enough to scan at a glance; the topic's own `--help` has the rest. */
243
311
  const MAX_TOPIC_LISTING = 10;
@@ -252,3 +320,104 @@ function lookupFromConfig(config) {
252
320
  : [],
253
321
  };
254
322
  }
323
+ /**
324
+ * Verbs this CLI spells two ways across its topics (OSK-16434).
325
+ *
326
+ * `skrr tasks` and `skrr machines` say `list` and `show`; `skrr wiki` and
327
+ * `skrr machines dedicated` say `ls`, and `get` is what most CLIs call `show`.
328
+ * Which a topic uses is history, not meaning, so a reader who guessed the other
329
+ * spelling was refused with a suggestion list that did not contain the command
330
+ * they meant (`tasks get` answered "did you mean tasks chat, tasks list, tasks
331
+ * open" — no `show`).
332
+ *
333
+ * Read-only verbs only, and only as a fallback: a spelling a topic really has
334
+ * always wins, and nothing here can run a command that changes anything.
335
+ */
336
+ const VERB_SYNONYMS = [
337
+ ['list', 'ls'],
338
+ ['show', 'get'],
339
+ ];
340
+ function synonymsOf(verb) {
341
+ const lower = verb.toLowerCase();
342
+ const group = VERB_SYNONYMS.find((g) => g.includes(lower));
343
+ return group ? group.filter((v) => v !== lower) : [];
344
+ }
345
+ /**
346
+ * The real command a mis-spelled READ verb means, when a synonym of it exists
347
+ * under the same topic path.
348
+ *
349
+ * `words` are the leading, non-flag tokens as the reader typed them. The scan
350
+ * walks down the real topic path; at the first word that is not part of it, that
351
+ * word is the verb, and only a synonym that is itself a command qualifies. A
352
+ * word that is a real command, or a prefix of one, is never reinterpreted.
353
+ */
354
+ function resolveVerbSynonym(config, words) {
355
+ const usable = new Set();
356
+ for (const id of [...config.commandIDs, ...config.topicNames]) {
357
+ const parts = id.split(':');
358
+ for (let i = 1; i <= parts.length; i += 1)
359
+ usable.add(parts.slice(0, i).join(':'));
360
+ }
361
+ for (let i = 1; i < words.length; i += 1) {
362
+ const prefix = words.slice(0, i).join(':');
363
+ if (!usable.has(prefix))
364
+ return null;
365
+ if (usable.has(`${prefix}:${words[i]}`))
366
+ continue;
367
+ for (const synonym of synonymsOf(words[i])) {
368
+ const candidate = `${prefix}:${synonym}`;
369
+ if (config.findCommand(candidate) === undefined)
370
+ continue;
371
+ const spoken = (verb) => `${config.bin} ${[...words.slice(0, i), verb].join(' ')}`;
372
+ return {
373
+ id: candidate,
374
+ rest: words.slice(i + 1),
375
+ typed: spoken(words[i]),
376
+ resolved: spoken(synonym),
377
+ };
378
+ }
379
+ return null;
380
+ }
381
+ return null;
382
+ }
383
+ /**
384
+ * The words a person typed for the command, recovered from the process argv.
385
+ *
386
+ * oclif joins the words it takes for a command with `:`, so a positional that
387
+ * itself contains a colon cannot be told apart from two words in the id. The
388
+ * raw argv can: its leading tokens, joined, are the id.
389
+ */
390
+ function typedCommandWords(id, rawArgv) {
391
+ const words = [];
392
+ for (const token of rawArgv) {
393
+ if (token.startsWith('-'))
394
+ break;
395
+ words.push(token);
396
+ if (words.join(':') === id)
397
+ return words;
398
+ }
399
+ return String(id ?? '')
400
+ .split(':')
401
+ .filter(Boolean);
402
+ }
403
+ /**
404
+ * What `--json` prints for a miss: the same `{ok:false, error}` every other
405
+ * refusal prints, so a script that parses refusals parses this one — instead of
406
+ * `jq` reading prose and failing with "Invalid numeric literal", which is how
407
+ * the report found it.
408
+ */
409
+ function commandMissEnvelope(config, id, words) {
410
+ const miss = analyzeCommandMiss(config, id);
411
+ return {
412
+ ok: false,
413
+ error: {
414
+ code: 'COMMAND_NOT_FOUND',
415
+ message: miss.message,
416
+ retryable: false,
417
+ command: null,
418
+ typed: `${config.bin} ${words.join(' ')}`.trim(),
419
+ ...(miss.fix ? { fix: miss.fix } : {}),
420
+ ...(miss.suggestions.length > 0 ? { suggestions: miss.suggestions } : {}),
421
+ },
422
+ };
423
+ }
@@ -12,5 +12,13 @@ import { type CommitmentLoopStatus, type CommitmentRoster } from '@skrr-ai/data-
12
12
  export declare function loopStateLabel(state: unknown): string;
13
13
  /** Roster table: one row per Commitment. */
14
14
  export declare function formatCommitmentRoster(roster: Partial<CommitmentRoster>): string[];
15
+ /**
16
+ * One loop in a few lines, for a surface that lists many (`commitments operating-review`). It carries
17
+ * the same facts as `formatCommitmentLoopStatus` but in the order an owner asks them: is it armed,
18
+ * when does it next wake, why did it last stop, can it read what it watches, what did it run today.
19
+ * Every line is present when its fact is unknown, because "cannot see" must not read as "nothing to
20
+ * report" (OSK-15438, OSK-15412). Lines are returned unindented; the caller indents them.
21
+ */
22
+ export declare function formatLoopSummary(status: CommitmentLoopStatus): string[];
15
23
  /** One Commitment's loop, in full. */
16
24
  export declare function formatCommitmentLoopStatus(status: CommitmentLoopStatus): string[];
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loopStateLabel = loopStateLabel;
4
4
  exports.formatCommitmentRoster = formatCommitmentRoster;
5
+ exports.formatLoopSummary = formatLoopSummary;
5
6
  exports.formatCommitmentLoopStatus = formatCommitmentLoopStatus;
6
7
  /**
7
8
  * Text rendering for `skrr commitments roster` / `skrr commitments status`.
@@ -111,6 +112,50 @@ function formatCommitmentRoster(roster) {
111
112
  }
112
113
  return lines;
113
114
  }
115
+ /**
116
+ * One loop in a few lines, for a surface that lists many (`commitments operating-review`). It carries
117
+ * the same facts as `formatCommitmentLoopStatus` but in the order an owner asks them: is it armed,
118
+ * when does it next wake, why did it last stop, can it read what it watches, what did it run today.
119
+ * Every line is present when its fact is unknown, because "cannot see" must not read as "nothing to
120
+ * report" (OSK-15438, OSK-15412). Lines are returned unindented; the caller indents them.
121
+ */
122
+ function formatLoopSummary(status) {
123
+ const lines = [];
124
+ const detail = status.loopDetail || (0, data_provider_1.describeCommitmentLoopState)(status.loopState);
125
+ lines.push(`loop: ${loopStateLabel(status.loopState)} — ${detail}${status.loopReason ? ` (${status.loopReason})` : ''}`);
126
+ const wake = status.nextWakes?.[0];
127
+ lines.push(`next wake: ${wake
128
+ ? `${when(wake.at)} ${wake.kind}`
129
+ : status.loopState === 'unknown'
130
+ ? 'could not be read'
131
+ : 'none — nothing is scheduled to wake it'}`);
132
+ if (status.lastStop) {
133
+ const stop = status.lastStop;
134
+ lines.push(`last stop: ${stop.question}${stop.reason ? ` — ${stop.reason}` : ''}${stop.detail ? `: ${stop.detail}` : ''}`);
135
+ if (stop.fix)
136
+ lines.push(`fix: ${stop.fix}`);
137
+ }
138
+ else {
139
+ lines.push('last stop: none recorded');
140
+ }
141
+ const watching = Array.isArray(status.watching) ? status.watching : [];
142
+ const trouble = watching
143
+ .filter((row) => row.access !== 'readable' || row.completeness === 'partial')
144
+ .map((row) => {
145
+ const access = row.access === 'blocked'
146
+ ? 'CANNOT READ'
147
+ : row.access === 'unknown'
148
+ ? 'not known'
149
+ : 'PARTIAL read';
150
+ return `${row.source} ${access}${row.reason ? ` (${row.reason})` : ''}`;
151
+ });
152
+ lines.push(`sources: ${!watching.length ? 'none declared' : trouble.length ? trouble.join('; ') : 'all readable'}`);
153
+ lines.push(`today: ${runs(status)} runs · spend ${spend(status)}`);
154
+ if (status.readErrors?.length) {
155
+ lines.push(`unread: ${status.readErrors.join(', ')} could not be read; treat the above as unknown`);
156
+ }
157
+ return lines;
158
+ }
114
159
  /** One Commitment's loop, in full. */
115
160
  function formatCommitmentLoopStatus(status) {
116
161
  const lines = [];
@@ -62,9 +62,12 @@ function renderExecutionReceipt(receipt, log, verbose = false) {
62
62
  // restates what happened (a typed blocker's summary, which servers before
63
63
  // OSK-16429 sent as the reason) is narrative, not something to do: the actor
64
64
  // is printed without it and the narrative stays with the blocker.
65
+ // A bare reason code (servers before OSK-16451 sent `runtime_connection_unverifiable`
66
+ // there) is not an action either: the actor is printed alone.
65
67
  const narrative = [receipt.summary, receipt.blocker?.summary].filter(Boolean);
66
- const action = receipt.nextActor?.reason && !narrative.includes(receipt.nextActor.reason)
67
- ? receipt.nextActor.reason
68
+ const reason = receipt.nextActor?.reason;
69
+ const action = reason && !narrative.includes(reason) && !/^[A-Za-z0-9_.:-]+$/.test(reason.trim())
70
+ ? reason
68
71
  : '';
69
72
  if (receipt.nextActor)
70
73
  log(`Next: ${receipt.nextActor.label || receipt.nextActor.kind}${action ? ` — ${action}` : ''}`);
@@ -18,7 +18,7 @@ import { type CommitmentObservationsQuery, type CommitmentObservationsResponse }
18
18
  * "hold this state", derived from a Compass invariant — not authored here).
19
19
  */
20
20
  import { COMMITMENT_WATCHED_SOURCES, COMMITMENT_TEMPLATE_IDS, COMMITMENT_CADENCE_MODES, COMMITMENT_CADENCE_TIME_PATTERN, COMMITMENT_CADENCE_MAX_TIMES, COMMITMENT_EXECUTION_ISOLATION_MODES, COMMITMENT_STATUSES, COMMITMENT_KIND_VALUES } from '@skrr-ai/data-provider';
21
- import type { CommitmentCheck, CommitmentChecksResponse, Commitment, CommitmentActionProposal, CommitmentListResponse, CommitmentPlanResponse, CommitmentScheduleIssue, CommitmentStatusResponse, CommitmentWorkLink, CommitmentExecutionReceiptListResponse, CommitmentExecutionReceiptQuarterlyMetricsResponse, CommitmentCapabilitySource, CommitmentDeliveryPatchResponse, CommitmentDeliveryBundleResponse, CommitmentCyclesResponse, CommitmentExplanationResponse, CommitmentRunPermissions, CommitmentLoopStatus, CommitmentRoster } from '@skrr-ai/data-provider';
21
+ import type { CommitmentCheck, CommitmentChecksResponse, Commitment, CommitmentRevisionReceipt, CommitmentUpdateResponse, CommitmentActionProposal, CommitmentListResponse, CommitmentPlanResponse, CommitmentScheduleIssue, CommitmentStatusResponse, CommitmentWorkLink, CommitmentExecutionReceiptListResponse, CommitmentExecutionReceiptQuarterlyMetricsResponse, CommitmentCapabilitySource, CommitmentDeliveryPatchResponse, CommitmentDeliveryBundleResponse, CommitmentCyclesResponse, CommitmentExplanationResponse, CommitmentRunPermissions, CommitmentLoopStatus, CommitmentRoster } from '@skrr-ai/data-provider';
22
22
  import { withQuery } from './triggers';
23
23
  export { COMMITMENT_WATCHED_SOURCES, COMMITMENT_TEMPLATE_IDS, COMMITMENT_CADENCE_MODES, COMMITMENT_CADENCE_TIME_PATTERN, COMMITMENT_CADENCE_MAX_TIMES, COMMITMENT_STATUSES, COMMITMENT_KIND_VALUES, };
24
24
  export type { CommitmentCheck, CommitmentChecksResponse, Commitment, CommitmentActionProposal, CommitmentListResponse, CommitmentPlanResponse, CommitmentStatusResponse, CommitmentWorkLink, };
@@ -276,7 +276,7 @@ export declare const commitmentApi: {
276
276
  }) => Promise<Record<string, unknown>>;
277
277
  readinessReceipt: (id: string, receiptId: string) => Promise<Record<string, unknown>>;
278
278
  create: (body: Record<string, unknown>) => Promise<Commitment>;
279
- update: (id: string, body: Record<string, unknown>) => Promise<Commitment>;
279
+ update: (id: string, body: Record<string, unknown>) => Promise<CommitmentUpdateResponse>;
280
280
  draftPreflight: (body: Record<string, unknown>) => Promise<CommitmentPreflightReport>;
281
281
  workLinks: (id: string, includeInactive?: boolean) => Promise<{
282
282
  object: "commitment_work_link_list";
@@ -553,6 +553,10 @@ export interface CommitmentAccountingReadout {
553
553
  costRunCount?: number;
554
554
  costReportedRunCount?: number;
555
555
  costUnavailableRunCount?: number;
556
+ /** Runs refused before any model turn: they cost nothing (OSK-15500). */
557
+ costNoSpendRunCount?: number;
558
+ /** …of which the verdict rests on inference, not a recorded refusal. */
559
+ costNoSpendInferredRunCount?: number;
556
560
  costStatus?: 'reported' | 'partial' | 'unavailable' | 'not_applicable';
557
561
  recommendedAttemptMultiplier?: number | null;
558
562
  multiplierDerivation?: string;
@@ -573,6 +577,10 @@ export interface CommitmentCostWindow {
573
577
  runCount?: number;
574
578
  reportedRunCount?: number;
575
579
  unavailableRunCount?: number;
580
+ /** Runs refused before any model turn: they cost nothing (OSK-15500). */
581
+ noSpendRunCount?: number;
582
+ /** …of which the verdict rests on inference, not a recorded refusal. */
583
+ noSpendInferredRunCount?: number;
576
584
  costStatus?: 'reported' | 'partial' | 'unavailable' | 'not_applicable';
577
585
  }
578
586
  /**
@@ -824,8 +832,9 @@ export declare function describeOperation(operation: string | null | undefined,
824
832
  * under "Fix:" read as a remedy nobody wrote (OSK-16230). That narrative is
825
833
  * labelled `Blocker:` instead, and no remedy is made up for it.
826
834
  */
835
+ type OperationFixLabel = 'Fix' | 'Blocker' | 'Last run';
827
836
  export declare function describeOperationFix(commitment: CommitmentView, operation: string | null | undefined): Array<{
828
- label: 'Fix' | 'Blocker';
837
+ label: OperationFixLabel;
829
838
  text: string;
830
839
  }>;
831
840
  /**
@@ -846,6 +855,12 @@ export declare function withLiveAssessment(commitment: CommitmentView, operating
846
855
  executorBlocked?: unknown;
847
856
  firstActionableBlocker?: unknown;
848
857
  } | null): CommitmentView;
858
+ /**
859
+ * The first line of `commitments update` text output. An update that changed
860
+ * nothing says so and names the version it left alone (OSK-16199); a server
861
+ * that predates the receipt is reported as updated, exactly as before.
862
+ */
863
+ export declare function describeCommitmentRevision(revision: CommitmentRevisionReceipt | undefined): string;
849
864
  export declare function renderCommitmentSummary(commitment: CommitmentView, log: (line: string) => void,
850
865
  /**
851
866
  * The most recent check, when the caller has one. Optional because pause /
@@ -25,6 +25,7 @@ exports.describeOperation = describeOperation;
25
25
  exports.describeOperationFix = describeOperationFix;
26
26
  exports.describeStoppedBy = describeStoppedBy;
27
27
  exports.withLiveAssessment = withLiveAssessment;
28
+ exports.describeCommitmentRevision = describeCommitmentRevision;
28
29
  exports.renderCommitmentSummary = renderCommitmentSummary;
29
30
  exports.describeWakeDecision = describeWakeDecision;
30
31
  exports.renderCheckList = renderCheckList;
@@ -661,6 +662,17 @@ function buildWatchedSources(inputs) {
661
662
  }
662
663
  return normalizeWatchedSourceDeclarations(rows);
663
664
  }
665
+ /**
666
+ * The assessment's evidence state as one line. `unconfigured` read as if a source were broken (OSK-15412); it
667
+ * means no required success criterion is set, so nothing is being measured, which is a different thing to fix.
668
+ * The other states keep their one word.
669
+ */
670
+ function describeAssessmentEvidence(evidence) {
671
+ if (evidence === 'unconfigured') {
672
+ return 'unconfigured — no required success criterion is set, so nothing is being measured (not a failing source)';
673
+ }
674
+ return evidence ?? 'unavailable';
675
+ }
664
676
  /**
665
677
  * One line an author can act on, or nothing.
666
678
  *
@@ -946,40 +958,38 @@ function describeOperation(operation, reason) {
946
958
  };
947
959
  return `not running — ${meaning[status] ?? status}`;
948
960
  }
949
- /**
950
- * What to do about an operation that is not running, from the operating state's
951
- * first actionable blocker (`withLiveAssessment` carries it onto the view).
952
- *
953
- * `show` printed `Operation: blocked — typed_external_blocker` and nothing else,
954
- * so the one line a reader acts on was a code; the remediation the server
955
- * already composed sat only in `--json` and `doctor` (OSK-16212). Nothing is
956
- * printed while the operation is ready or working, where a fix would contradict
957
- * the line above it.
958
- *
959
- * Returns one or two lines. `Fix:` only when the blocker carries an action; a
960
- * typed blocker an agent returned carries only what happened, which printed
961
- * under "Fix:" read as a remedy nobody wrote (OSK-16230). That narrative is
962
- * labelled `Blocker:` instead, and no remedy is made up for it.
963
- */
964
961
  function describeOperationFix(commitment, operation) {
965
962
  if (operation === 'ready' || operation === 'working')
966
963
  return [];
967
964
  const blocker = commitment.firstActionableBlocker;
968
- if (!blocker)
969
- return [];
970
- const remediation = typeof blocker.remediation === 'string' ? blocker.remediation.trim() : '';
971
- const detail = typeof blocker.detail === 'string' ? blocker.detail.trim() : '';
972
- const who = typeof blocker.ownerLabel === 'string' && blocker.ownerLabel
973
- ? blocker.ownerLabel
974
- : typeof blocker.owner === 'string' && blocker.owner
975
- ? blocker.owner
976
- : '';
977
965
  const lines = [];
978
- // The detail is shown only when the fix does not already say it.
979
- if (detail && detail !== remediation)
980
- lines.push({ label: 'Blocker', text: `${detail}${!remediation && who ? ` (${who})` : ''}` });
981
- if (remediation)
982
- lines.push({ label: 'Fix', text: `${remediation}${who ? ` (${who})` : ''}` });
966
+ let remediation = '';
967
+ if (blocker) {
968
+ remediation = typeof blocker.remediation === 'string' ? blocker.remediation.trim() : '';
969
+ const detail = typeof blocker.detail === 'string' ? blocker.detail.trim() : '';
970
+ const who = typeof blocker.ownerLabel === 'string' && blocker.ownerLabel
971
+ ? blocker.ownerLabel
972
+ : typeof blocker.owner === 'string' && blocker.owner
973
+ ? blocker.owner
974
+ : '';
975
+ // The detail is shown only when the fix does not already say it.
976
+ if (detail && detail !== remediation)
977
+ lines.push({ label: 'Blocker', text: `${detail}${!remediation && who ? ` (${who})` : ''}` });
978
+ if (remediation)
979
+ lines.push({ label: 'Fix', text: `${remediation}${who ? ` (${who})` : ''}` });
980
+ }
981
+ // What refused the last Run is a second fact, not an alternative to the
982
+ // blocker: a paused Commitment whose Run was refused needs both a resume and
983
+ // the fix, or resuming only fails again (OSK-15389). The sentence is the
984
+ // server's, derived on read from the failure code; it is printed whenever it
985
+ // exists and says something the blocker's fix does not.
986
+ const liveness = commitment.state?.liveness;
987
+ const lastRunFix = liveness && liveness.current !== false && typeof liveness.diagnosisFix === 'string'
988
+ ? liveness.diagnosisFix.trim()
989
+ : '';
990
+ if (lastRunFix && lastRunFix !== remediation) {
991
+ lines.push({ label: lines.length ? 'Last run' : 'Fix', text: lastRunFix });
992
+ }
983
993
  return lines;
984
994
  }
985
995
  /**
@@ -1036,6 +1046,19 @@ function withLiveAssessment(commitment, operatingState) {
1036
1046
  assessment: live,
1037
1047
  };
1038
1048
  }
1049
+ /**
1050
+ * The first line of `commitments update` text output. An update that changed
1051
+ * nothing says so and names the version it left alone (OSK-16199); a server
1052
+ * that predates the receipt is reported as updated, exactly as before.
1053
+ */
1054
+ function describeCommitmentRevision(revision) {
1055
+ if (!revision || typeof revision.changed !== 'boolean')
1056
+ return 'Updated commitment:';
1057
+ if (!revision.changed) {
1058
+ return `No change: the commitment already matches; it stays at version ${revision.version}.`;
1059
+ }
1060
+ return `Updated commitment (version ${revision.previousVersion} -> ${revision.version}):`;
1061
+ }
1039
1062
  function renderCommitmentSummary(commitment, log,
1040
1063
  /**
1041
1064
  * The most recent check, when the caller has one. Optional because pause /
@@ -1092,7 +1115,7 @@ scheduleIssues) {
1092
1115
  ? (0, data_provider_2.readCommitmentAssessment)(commitment.assessment)
1093
1116
  : undefined;
1094
1117
  log(`Assessment: ${assessment?.label ?? 'Unknown'}`);
1095
- log(`Evidence: ${assessment?.evidence ?? 'unavailable'}`);
1118
+ log(`Evidence: ${describeAssessmentEvidence(assessment?.evidence)}`);
1096
1119
  log(`Operation: ${describeOperation(assessment?.operation, assessment?.operationReason)}`);
1097
1120
  for (const line of describeOperationFix(commitment, assessment?.operation))
1098
1121
  log(` ${line.label}: ${line.text}`);
@@ -1237,15 +1260,22 @@ function renderCheckList(checks, log) {
1237
1260
  log('No checks recorded.');
1238
1261
  return;
1239
1262
  }
1263
+ // A compacted Check's open loops and action plan were stripped, so an empty
1264
+ // list there is "not retained", never "none" (OSK-15148). Its retained
1265
+ // explanation stands in for the stripped `whyNow`.
1266
+ const retainedCount = (c, list) => c.historyDetail && !list?.length ? '-' : String(list?.length ?? 0);
1240
1267
  (0, format_1.renderTable)(checks.map((c) => ({
1241
1268
  started: c.startedAt ?? '-',
1242
1269
  kind: c.checkKind ?? '-',
1243
1270
  status: c.status ?? '-',
1244
1271
  posture: c.decision?.posture ?? '-',
1245
- loops: String(c.openLoops?.length ?? 0),
1246
- actions: String(c.actionPlan?.length ?? 0),
1272
+ loops: retainedCount(c, c.openLoops),
1273
+ actions: retainedCount(c, c.actionPlan),
1247
1274
  woke: describeWakeDecision(c),
1248
- why: c.decision?.whyNow ?? c.reason ?? '',
1275
+ why: c.decision?.whyNow ??
1276
+ c.decision?.summary ??
1277
+ c.reason ??
1278
+ (c.historyDetail ? '(detail not retained)' : ''),
1249
1279
  id: c.id ?? '-',
1250
1280
  })), [
1251
1281
  { key: 'started', header: 'STARTED', maxWidth: 24 },
@@ -1261,6 +1291,16 @@ function renderCheckList(checks, log) {
1261
1291
  // meant re-running the command with `--json` (OSK-6175).
1262
1292
  { key: 'id', header: 'ID', verbatim: true },
1263
1293
  ], log);
1294
+ // The legend states each distinct reason the server recorded, in the same
1295
+ // words explain and cycles use; it never guesses "compacted" for a Check
1296
+ // whose history is merely missing (OSK-15148).
1297
+ const reasons = [
1298
+ ...new Set(checks
1299
+ .map((c) => (0, data_provider_2.describeCommitmentHistoryDetail)(c.historyDetail))
1300
+ .filter((sentence) => Boolean(sentence))),
1301
+ ];
1302
+ if (reasons.length > 0)
1303
+ log(`'-' = not retained. ${reasons.join(' ')}`);
1264
1304
  }
1265
1305
  function records(value) {
1266
1306
  return Array.isArray(value)
@@ -1628,18 +1668,18 @@ function formatCommitmentCycles(page) {
1628
1668
  */
1629
1669
  function formatCycleLatency(cycle) {
1630
1670
  const latency = cycle.latency;
1631
- const history = cycle.historyDetail;
1632
- const disclosure = history?.state === 'compacted'
1633
- ? [
1634
- ` Detailed history was compacted${history.at ? ` at ${history.at}` : ''}; available reason and source timestamps were retained.`,
1635
- ]
1636
- : history?.state === 'unavailable'
1637
- ? [' Detailed history is unavailable; why it is missing was not recorded.']
1638
- : [];
1639
- const missingFor = (stage) => (latency?.pendingStages ? latency.pendingStages.includes(stage) : latency?.pending)
1640
- ? 'pending'
1641
- : 'unmeasurable';
1642
- if (latency?.measured === false) {
1671
+ // One sentence for every surface, from the server's own record (OSK-15148).
1672
+ const historySentence = (0, data_provider_2.describeCommitmentHistoryDetail)(cycle.historyDetail);
1673
+ const disclosure = historySentence ? [` ${historySentence}`] : [];
1674
+ // A hop whose inputs were compacted away without a retained projection is
1675
+ // "not retained": it may have been measured and shown before (OSK-15148).
1676
+ const unretained = new Set(latency?.unretainedStages || []);
1677
+ const missingFor = (stage) => unretained.has(stage)
1678
+ ? 'not retained'
1679
+ : (latency?.pendingStages ? latency.pendingStages.includes(stage) : latency?.pending)
1680
+ ? 'pending'
1681
+ : 'unmeasurable';
1682
+ if (latency?.measured === false && unretained.size === 0) {
1643
1683
  return [...disclosure, ` latency: ${latency.pending ? 'pending' : 'unmeasurable'}`];
1644
1684
  }
1645
1685
  return [
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `skrr daemon <verb> [args]` as a person can paste it for THIS profile.
3
+ *
4
+ * `skrr daemon login` with no `--profile` acts on whichever profile the shell it
5
+ * is typed in resolves, which is the default one unless something says
6
+ * otherwise. So a hint printed after `skrr login --profile expd` that said only
7
+ * `skrr daemon login` sent people to sign a different daemon in and leave the
8
+ * one they had just set up exactly as it was (OSK-16435). The profile is named
9
+ * only when it is not the default, the way every other hint here spells it.
10
+ */
11
+ export declare function daemonCommandHint(verb: string, options?: {
12
+ bin?: string;
13
+ extraArgs?: string;
14
+ profile?: string;
15
+ }): string;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.daemonCommandHint = daemonCommandHint;
4
+ const keychain_1 = require("./keychain");
5
+ /**
6
+ * `skrr daemon <verb> [args]` as a person can paste it for THIS profile.
7
+ *
8
+ * `skrr daemon login` with no `--profile` acts on whichever profile the shell it
9
+ * is typed in resolves, which is the default one unless something says
10
+ * otherwise. So a hint printed after `skrr login --profile expd` that said only
11
+ * `skrr daemon login` sent people to sign a different daemon in and leave the
12
+ * one they had just set up exactly as it was (OSK-16435). The profile is named
13
+ * only when it is not the default, the way every other hint here spells it.
14
+ */
15
+ function daemonCommandHint(verb, options = {}) {
16
+ const bin = options.bin ?? 'skrr';
17
+ const profile = options.profile ?? (0, keychain_1.getActiveProfile)();
18
+ const scoped = profile && profile !== keychain_1.DEFAULT_PROFILE ? ` --profile ${profile}` : '';
19
+ const extra = options.extraArgs ? ` ${options.extraArgs}` : '';
20
+ return `${bin} daemon ${verb}${scoped}${extra}`;
21
+ }