@skrr-ai/cli 0.1.107 → 0.1.109

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 (175) hide show
  1. package/README.md +10 -0
  2. package/dist/base-command.d.ts +59 -0
  3. package/dist/base-command.js +118 -7
  4. package/dist/commands/agents/update.d.ts +14 -0
  5. package/dist/commands/agents/update.js +40 -1
  6. package/dist/commands/commitments/accounting.js +7 -2
  7. package/dist/commands/commitments/cost.d.ts +11 -0
  8. package/dist/commands/commitments/cost.js +26 -3
  9. package/dist/commands/commitments/create.js +2 -1
  10. package/dist/commands/commitments/operating-review.d.ts +15 -0
  11. package/dist/commands/commitments/operating-review.js +103 -50
  12. package/dist/commands/commitments/update.js +12 -5
  13. package/dist/commands/doctor.js +9 -0
  14. package/dist/commands/goals/list.js +6 -1
  15. package/dist/commands/grants/rotate.js +4 -0
  16. package/dist/commands/login.js +42 -14
  17. package/dist/commands/machines/list.js +28 -6
  18. package/dist/commands/projects/show.js +1 -1
  19. package/dist/commands/revoke-daemon.js +2 -1
  20. package/dist/commands/spaces/workflows/archive.d.ts +1 -0
  21. package/dist/commands/spaces/workflows/archive.js +1 -0
  22. package/dist/commands/spaces/workflows/artifact.d.ts +1 -0
  23. package/dist/commands/spaces/workflows/artifact.js +1 -0
  24. package/dist/commands/spaces/workflows/cancel-node.d.ts +1 -0
  25. package/dist/commands/spaces/workflows/cancel-node.js +1 -0
  26. package/dist/commands/spaces/workflows/cancel.d.ts +1 -0
  27. package/dist/commands/spaces/workflows/cancel.js +6 -2
  28. package/dist/commands/spaces/workflows/create.d.ts +1 -0
  29. package/dist/commands/spaces/workflows/create.js +7 -1
  30. package/dist/commands/spaces/workflows/events.d.ts +1 -0
  31. package/dist/commands/spaces/workflows/events.js +1 -0
  32. package/dist/commands/spaces/workflows/fixtures/create.d.ts +1 -0
  33. package/dist/commands/spaces/workflows/fixtures/create.js +1 -0
  34. package/dist/commands/spaces/workflows/fixtures/list.d.ts +1 -0
  35. package/dist/commands/spaces/workflows/fixtures/list.js +1 -0
  36. package/dist/commands/spaces/workflows/lifecycle-diagnostics.d.ts +1 -0
  37. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +1 -0
  38. package/dist/commands/spaces/workflows/list.d.ts +1 -0
  39. package/dist/commands/spaces/workflows/list.js +1 -0
  40. package/dist/commands/spaces/workflows/node-types.d.ts +2 -0
  41. package/dist/commands/spaces/workflows/node-types.js +24 -6
  42. package/dist/commands/spaces/workflows/pause.d.ts +1 -0
  43. package/dist/commands/spaces/workflows/pause.js +1 -0
  44. package/dist/commands/spaces/workflows/plan/show.d.ts +1 -0
  45. package/dist/commands/spaces/workflows/plan/show.js +1 -0
  46. package/dist/commands/spaces/workflows/plan/update.d.ts +1 -0
  47. package/dist/commands/spaces/workflows/plan/update.js +1 -0
  48. package/dist/commands/spaces/workflows/proposals/apply.d.ts +1 -0
  49. package/dist/commands/spaces/workflows/proposals/apply.js +1 -0
  50. package/dist/commands/spaces/workflows/proposals/confirm.d.ts +1 -0
  51. package/dist/commands/spaces/workflows/proposals/confirm.js +1 -0
  52. package/dist/commands/spaces/workflows/proposals/create.d.ts +1 -0
  53. package/dist/commands/spaces/workflows/proposals/create.js +1 -0
  54. package/dist/commands/spaces/workflows/proposals/dismiss.d.ts +1 -0
  55. package/dist/commands/spaces/workflows/proposals/dismiss.js +1 -0
  56. package/dist/commands/spaces/workflows/proposals/list.d.ts +1 -0
  57. package/dist/commands/spaces/workflows/proposals/list.js +1 -0
  58. package/dist/commands/spaces/workflows/publish.d.ts +1 -0
  59. package/dist/commands/spaces/workflows/publish.js +9 -0
  60. package/dist/commands/spaces/workflows/reconcile.d.ts +1 -0
  61. package/dist/commands/spaces/workflows/reconcile.js +1 -0
  62. package/dist/commands/spaces/workflows/resume.d.ts +1 -0
  63. package/dist/commands/spaces/workflows/resume.js +1 -0
  64. package/dist/commands/spaces/workflows/retry.d.ts +1 -0
  65. package/dist/commands/spaces/workflows/retry.js +1 -0
  66. package/dist/commands/spaces/workflows/run.d.ts +1 -0
  67. package/dist/commands/spaces/workflows/run.js +14 -7
  68. package/dist/commands/spaces/workflows/runs.d.ts +1 -0
  69. package/dist/commands/spaces/workflows/runs.js +1 -0
  70. package/dist/commands/spaces/workflows/show.d.ts +25 -0
  71. package/dist/commands/spaces/workflows/show.js +42 -4
  72. package/dist/commands/spaces/workflows/signal.d.ts +12 -0
  73. package/dist/commands/spaces/workflows/signal.js +40 -2
  74. package/dist/commands/spaces/workflows/start.d.ts +1 -0
  75. package/dist/commands/spaces/workflows/start.js +1 -0
  76. package/dist/commands/spaces/workflows/task-execution/create.d.ts +1 -0
  77. package/dist/commands/spaces/workflows/task-execution/create.js +1 -0
  78. package/dist/commands/spaces/workflows/test.d.ts +1 -0
  79. package/dist/commands/spaces/workflows/test.js +1 -0
  80. package/dist/commands/spaces/workflows/triggers/create.d.ts +1 -0
  81. package/dist/commands/spaces/workflows/triggers/create.js +1 -0
  82. package/dist/commands/spaces/workflows/triggers/fire.d.ts +1 -0
  83. package/dist/commands/spaces/workflows/triggers/fire.js +1 -0
  84. package/dist/commands/spaces/workflows/triggers/list.d.ts +1 -0
  85. package/dist/commands/spaces/workflows/triggers/list.js +1 -0
  86. package/dist/commands/spaces/workflows/triggers/update.d.ts +1 -0
  87. package/dist/commands/spaces/workflows/triggers/update.js +1 -0
  88. package/dist/commands/spaces/workflows/update.d.ts +1 -0
  89. package/dist/commands/spaces/workflows/update.js +4 -0
  90. package/dist/commands/spaces/workflows/validate.d.ts +1 -0
  91. package/dist/commands/spaces/workflows/validate.js +15 -0
  92. package/dist/commands/tasks/activity.js +6 -2
  93. package/dist/commands/tasks/bulk-update.d.ts +1 -0
  94. package/dist/commands/tasks/bulk-update.js +7 -0
  95. package/dist/commands/tasks/complete.js +2 -1
  96. package/dist/commands/tasks/create-tree.d.ts +69 -0
  97. package/dist/commands/tasks/create-tree.js +413 -47
  98. package/dist/commands/tasks/list.d.ts +2 -0
  99. package/dist/commands/tasks/list.js +19 -1
  100. package/dist/commands/tasks/rearm-recurrence.d.ts +22 -0
  101. package/dist/commands/tasks/rearm-recurrence.js +68 -0
  102. package/dist/commands/tasks/show.d.ts +1 -0
  103. package/dist/commands/tasks/show.js +17 -3
  104. package/dist/commands/tasks/timeline.js +4 -0
  105. package/dist/commands/tasks/update.d.ts +1 -0
  106. package/dist/commands/tasks/update.js +6 -1
  107. package/dist/commands/triggers/create.js +8 -2
  108. package/dist/commands/triggers/parse-intent.d.ts +7 -0
  109. package/dist/commands/triggers/parse-intent.js +31 -9
  110. package/dist/commands/workspaces/grants.js +23 -3
  111. package/dist/commands/workspaces/members.d.ts +20 -0
  112. package/dist/commands/workspaces/members.js +64 -0
  113. package/dist/commands/workspaces/stewardship.js +14 -2
  114. package/dist/help.js +12 -0
  115. package/dist/hooks/command-not-found.js +27 -1
  116. package/dist/lib/command-miss.d.ts +52 -2
  117. package/dist/lib/command-miss.js +187 -18
  118. package/dist/lib/commitment-loop-status.d.ts +8 -0
  119. package/dist/lib/commitment-loop-status.js +45 -0
  120. package/dist/lib/commitment-product.js +13 -1
  121. package/dist/lib/commitments.d.ts +16 -2
  122. package/dist/lib/commitments.js +48 -8
  123. package/dist/lib/daemon-command-hint.d.ts +15 -0
  124. package/dist/lib/daemon-command-hint.js +21 -0
  125. package/dist/lib/daemon-setup.js +10 -1
  126. package/dist/lib/daemonBrokerRefusal.js +3 -2
  127. package/dist/lib/daemonHandoff.js +2 -1
  128. package/dist/lib/deployed-build.d.ts +43 -0
  129. package/dist/lib/deployed-build.js +66 -0
  130. package/dist/lib/grants.d.ts +27 -1
  131. package/dist/lib/grants.js +39 -21
  132. package/dist/lib/json-mode.d.ts +11 -0
  133. package/dist/lib/json-mode.js +19 -0
  134. package/dist/lib/json-output.d.ts +25 -0
  135. package/dist/lib/json-output.js +30 -0
  136. package/dist/lib/list-output.d.ts +9 -0
  137. package/dist/lib/list-output.js +20 -1
  138. package/dist/lib/local-daemon-readiness.d.ts +90 -0
  139. package/dist/lib/local-daemon-readiness.js +143 -0
  140. package/dist/lib/machines.d.ts +117 -0
  141. package/dist/lib/machines.js +139 -0
  142. package/dist/lib/managed-instruction-local.d.ts +2 -0
  143. package/dist/lib/managed-instruction-local.js +2 -0
  144. package/dist/lib/profile-machine-identity-guard.d.ts +18 -0
  145. package/dist/lib/profile-machine-identity-guard.js +29 -0
  146. package/dist/lib/request-field-flags.d.ts +20 -0
  147. package/dist/lib/request-field-flags.js +34 -0
  148. package/dist/lib/stewardship-output.d.ts +1 -1
  149. package/dist/lib/stewardship-output.js +26 -30
  150. package/dist/lib/task-batch.d.ts +61 -4
  151. package/dist/lib/task-batch.js +316 -80
  152. package/dist/lib/task-economics.d.ts +5 -0
  153. package/dist/lib/task-economics.js +15 -0
  154. package/dist/lib/task-recurrence-outcome.d.ts +6 -6
  155. package/dist/lib/task-recurrence-outcome.js +27 -3
  156. package/dist/lib/tasks.d.ts +39 -0
  157. package/dist/lib/tasks.js +132 -0
  158. package/dist/lib/triggers.d.ts +31 -1
  159. package/dist/lib/triggers.js +55 -1
  160. package/dist/lib/workflow-issues.d.ts +14 -0
  161. package/dist/lib/workflow-issues.js +32 -0
  162. package/dist/lib/workflow-run-view.d.ts +55 -0
  163. package/dist/lib/workflow-run-view.js +105 -0
  164. package/dist/lib/workspaces.d.ts +31 -4
  165. package/dist/lib/workspaces.js +43 -0
  166. package/dist/lib/write-target.d.ts +29 -0
  167. package/dist/lib/write-target.js +72 -0
  168. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.d.ts +51 -0
  169. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/workspaceIsolationProbeWire.js +50 -0
  170. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.d.ts +51 -0
  171. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/workspaceIsolationProbeWire.js +46 -0
  172. package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
  173. package/dist/node_modules/@skrr-ai/data-provider/index.js +7896 -7524
  174. package/oclif.manifest.json +376 -52
  175. package/package.json +19 -1
@@ -57,7 +57,20 @@ export declare function nearestCommands(id: string, all: string[], limit?: numbe
57
57
  */
58
58
  export declare function helpSubjectId(tokens: readonly string[], config: CommandLookup): string;
59
59
  /**
60
- * The message for a colon-joined id that did not resolve.
60
+ * A command miss, in parts: what is wrong, what to do, and the commands that
61
+ * would do it. The text a person reads is `message` and `fix` on separate lines;
62
+ * a script reads `suggestions` without parsing either (OSK-16434).
63
+ */
64
+ export type CommandMiss = {
65
+ message: string;
66
+ fix?: string;
67
+ /** Full commands, `skrr tasks show` — never prose. */
68
+ suggestions: string[];
69
+ };
70
+ /** The text form: exactly what a miss has always printed. */
71
+ export declare function describeCommandMiss(config: CommandLookup, id: string): string;
72
+ /**
73
+ * The miss for a colon-joined id that did not resolve.
61
74
  *
62
75
  * Two shapes, in order:
63
76
  *
@@ -68,7 +81,7 @@ export declare function helpSubjectId(tokens: readonly string[], config: Command
68
81
  * suggest the nearest siblings under the same topic before falling back to
69
82
  * the whole command list.
70
83
  */
71
- export declare function describeCommandMiss(config: CommandLookup, id: string): string;
84
+ export declare function analyzeCommandMiss(config: CommandLookup, id: string): CommandMiss;
72
85
  /** Adapt an oclif `Config` to the lookup shape above. */
73
86
  export declare function lookupFromConfig(config: {
74
87
  bin: string;
@@ -80,3 +93,40 @@ export declare function lookupFromConfig(config: {
80
93
  name?: string;
81
94
  }>;
82
95
  }): CommandLookup;
96
+ export type VerbSynonym = {
97
+ /** The command that really exists, colon-joined: `tasks:show`. */
98
+ id: string;
99
+ /** What the reader typed after the verb: the arguments of the real command. */
100
+ rest: string[];
101
+ /** The two spellings, spoken: `skrr tasks get` and `skrr tasks show`. */
102
+ typed: string;
103
+ resolved: string;
104
+ };
105
+ /**
106
+ * The real command a mis-spelled READ verb means, when a synonym of it exists
107
+ * under the same topic path.
108
+ *
109
+ * `words` are the leading, non-flag tokens as the reader typed them. The scan
110
+ * walks down the real topic path; at the first word that is not part of it, that
111
+ * word is the verb, and only a synonym that is itself a command qualifies. A
112
+ * word that is a real command, or a prefix of one, is never reinterpreted.
113
+ */
114
+ export declare function resolveVerbSynonym(config: CommandLookup, words: readonly string[]): VerbSynonym | null;
115
+ /**
116
+ * The words a person typed for the command, recovered from the process argv.
117
+ *
118
+ * oclif joins the words it takes for a command with `:`, so a positional that
119
+ * itself contains a colon cannot be told apart from two words in the id. The
120
+ * raw argv can: its leading tokens, joined, are the id.
121
+ */
122
+ export declare function typedCommandWords(id: string, rawArgv: readonly string[]): string[];
123
+ /**
124
+ * What `--json` prints for a miss: the same `{ok:false, error}` every other
125
+ * refusal prints, so a script that parses refusals parses this one — instead of
126
+ * `jq` reading prose and failing with "Invalid numeric literal", which is how
127
+ * the report found it.
128
+ */
129
+ export declare function commandMissEnvelope(config: CommandLookup, id: string, words: readonly string[]): {
130
+ ok: false;
131
+ error: Record<string, unknown>;
132
+ };
@@ -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 = [];
@@ -58,10 +58,22 @@ function renderExecutionReceipt(receipt, log, verbose = false) {
58
58
  log(` ${evidence.label}: ${location}`);
59
59
  }
60
60
  }
61
+ // "Next" names an action only when the receipt has one. A reason that only
62
+ // restates what happened (a typed blocker's summary, which servers before
63
+ // OSK-16429 sent as the reason) is narrative, not something to do: the actor
64
+ // is printed without it and the narrative stays with the blocker.
65
+ 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
+ : '';
61
69
  if (receipt.nextActor)
62
- log(`Next: ${receipt.nextActor.label || receipt.nextActor.kind}${receipt.nextActor.reason ? ` — ${receipt.nextActor.reason}` : ''}`);
70
+ log(`Next: ${receipt.nextActor.label || receipt.nextActor.kind}${action ? ` — ${action}` : ''}`);
63
71
  if (receipt.blocker) {
64
72
  log(`Blocker: ${receipt.blocker.code}`);
73
+ // The headline already carries a typed blocker's summary; say it again only
74
+ // when it differs.
75
+ if (receipt.blocker.summary && receipt.blocker.summary !== receipt.summary)
76
+ log(` Detail: ${receipt.blocker.summary}`);
65
77
  for (const evidenceRef of receipt.blocker.evidenceRefs || [])
66
78
  log(` Evidence: ${evidenceRef}`);
67
79
  }
@@ -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
  /**
@@ -846,6 +854,12 @@ export declare function withLiveAssessment(commitment: CommitmentView, operating
846
854
  executorBlocked?: unknown;
847
855
  firstActionableBlocker?: unknown;
848
856
  } | null): CommitmentView;
857
+ /**
858
+ * The first line of `commitments update` text output. An update that changed
859
+ * nothing says so and names the version it left alone (OSK-16199); a server
860
+ * that predates the receipt is reported as updated, exactly as before.
861
+ */
862
+ export declare function describeCommitmentRevision(revision: CommitmentRevisionReceipt | undefined): string;
849
863
  export declare function renderCommitmentSummary(commitment: CommitmentView, log: (line: string) => void,
850
864
  /**
851
865
  * 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
  *
@@ -1036,6 +1048,19 @@ function withLiveAssessment(commitment, operatingState) {
1036
1048
  assessment: live,
1037
1049
  };
1038
1050
  }
1051
+ /**
1052
+ * The first line of `commitments update` text output. An update that changed
1053
+ * nothing says so and names the version it left alone (OSK-16199); a server
1054
+ * that predates the receipt is reported as updated, exactly as before.
1055
+ */
1056
+ function describeCommitmentRevision(revision) {
1057
+ if (!revision || typeof revision.changed !== 'boolean')
1058
+ return 'Updated commitment:';
1059
+ if (!revision.changed) {
1060
+ return `No change: the commitment already matches; it stays at version ${revision.version}.`;
1061
+ }
1062
+ return `Updated commitment (version ${revision.previousVersion} -> ${revision.version}):`;
1063
+ }
1039
1064
  function renderCommitmentSummary(commitment, log,
1040
1065
  /**
1041
1066
  * The most recent check, when the caller has one. Optional because pause /
@@ -1092,7 +1117,7 @@ scheduleIssues) {
1092
1117
  ? (0, data_provider_2.readCommitmentAssessment)(commitment.assessment)
1093
1118
  : undefined;
1094
1119
  log(`Assessment: ${assessment?.label ?? 'Unknown'}`);
1095
- log(`Evidence: ${assessment?.evidence ?? 'unavailable'}`);
1120
+ log(`Evidence: ${describeAssessmentEvidence(assessment?.evidence)}`);
1096
1121
  log(`Operation: ${describeOperation(assessment?.operation, assessment?.operationReason)}`);
1097
1122
  for (const line of describeOperationFix(commitment, assessment?.operation))
1098
1123
  log(` ${line.label}: ${line.text}`);
@@ -1237,15 +1262,22 @@ function renderCheckList(checks, log) {
1237
1262
  log('No checks recorded.');
1238
1263
  return;
1239
1264
  }
1265
+ // A compacted Check's open loops and action plan were stripped, so an empty
1266
+ // list there is "not retained", never "none" (OSK-15148). Its retained
1267
+ // explanation stands in for the stripped `whyNow`.
1268
+ const retainedCount = (c, list) => c.historyDetail && !list?.length ? '-' : String(list?.length ?? 0);
1240
1269
  (0, format_1.renderTable)(checks.map((c) => ({
1241
1270
  started: c.startedAt ?? '-',
1242
1271
  kind: c.checkKind ?? '-',
1243
1272
  status: c.status ?? '-',
1244
1273
  posture: c.decision?.posture ?? '-',
1245
- loops: String(c.openLoops?.length ?? 0),
1246
- actions: String(c.actionPlan?.length ?? 0),
1274
+ loops: retainedCount(c, c.openLoops),
1275
+ actions: retainedCount(c, c.actionPlan),
1247
1276
  woke: describeWakeDecision(c),
1248
- why: c.decision?.whyNow ?? c.reason ?? '',
1277
+ why: c.decision?.whyNow ??
1278
+ c.decision?.summary ??
1279
+ c.reason ??
1280
+ (c.historyDetail ? '(detail not retained)' : ''),
1249
1281
  id: c.id ?? '-',
1250
1282
  })), [
1251
1283
  { key: 'started', header: 'STARTED', maxWidth: 24 },
@@ -1261,6 +1293,9 @@ function renderCheckList(checks, log) {
1261
1293
  // meant re-running the command with `--json` (OSK-6175).
1262
1294
  { key: 'id', header: 'ID', verbatim: true },
1263
1295
  ], log);
1296
+ if (checks.some((c) => c.historyDetail)) {
1297
+ log("'-' = not retained: an older check's detailed history was compacted; its stop and retained reason remain.");
1298
+ }
1264
1299
  }
1265
1300
  function records(value) {
1266
1301
  return Array.isArray(value)
@@ -1636,10 +1671,15 @@ function formatCycleLatency(cycle) {
1636
1671
  : history?.state === 'unavailable'
1637
1672
  ? [' Detailed history is unavailable; why it is missing was not recorded.']
1638
1673
  : [];
1639
- const missingFor = (stage) => (latency?.pendingStages ? latency.pendingStages.includes(stage) : latency?.pending)
1640
- ? 'pending'
1641
- : 'unmeasurable';
1642
- if (latency?.measured === false) {
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
+ }