@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
@@ -13,6 +13,9 @@ exports.listQuotaAccounts = listQuotaAccounts;
13
13
  exports.getQuotaHistory = getQuotaHistory;
14
14
  exports.updateHarness = updateHarness;
15
15
  exports.forgetHarness = forgetHarness;
16
+ exports.pruneHarnesses = pruneHarnesses;
17
+ exports.parseHarnessPruneAge = parseHarnessPruneAge;
18
+ exports.harnessPruneReasonSentence = harnessPruneReasonSentence;
16
19
  exports.setHarnessAlias = setHarnessAlias;
17
20
  exports.setDefaultHarness = setDefaultHarness;
18
21
  exports.mintInstallToken = mintInstallToken;
@@ -173,6 +176,73 @@ async function updateHarness(id, patch) {
173
176
  async function forgetHarness(id) {
174
177
  return (0, api_fetch_1.apiFetch)(`/api/harnesses/${encodeURIComponent(id)}`, { method: 'DELETE' });
175
178
  }
179
+ /**
180
+ * Remove the caller's superseded harness rows - or, by default, say which would go.
181
+ *
182
+ * ONE route for both: the server makes the decision once and this only asks for it
183
+ * to be shown (`dryRun: true`, also its own default) or carried out. The caller
184
+ * never lists rows and picks, so there is no client-side rule to drift from the
185
+ * server's. A write, so `apiFetch` gives it an idempotency key and a retried
186
+ * request replays the recorded answer instead of running again.
187
+ */
188
+ async function pruneHarnesses(options = { dryRun: true }) {
189
+ return (0, api_fetch_1.apiFetch)('/api/harnesses/prune', {
190
+ method: 'POST',
191
+ body: {
192
+ dryRun: options.dryRun,
193
+ ...(options.limit !== undefined ? { limit: options.limit } : {}),
194
+ ...(options.minUnseenMs !== undefined ? { minUnseenMs: options.minUnseenMs } : {}),
195
+ },
196
+ });
197
+ }
198
+ /**
199
+ * `--older-than` takes a whole number with a unit: `90m`, `36h`, `7d`. A bare
200
+ * number is hours. Returns milliseconds. The server never goes below its offline
201
+ * window whatever this says.
202
+ */
203
+ function parseHarnessPruneAge(value) {
204
+ const match = value.trim().match(/^(\d+)\s*(m|h|d)?$/i);
205
+ if (!match) {
206
+ throw new Error(`Invalid --older-than value "${value}". Use a number with m, h or d, such as 90m, 36h or 7d.`);
207
+ }
208
+ const amount = Number(match[1]);
209
+ const unit = (match[2] || 'h').toLowerCase();
210
+ const perUnit = unit === 'd' ? 86_400_000 : unit === 'h' ? 3_600_000 : 60_000;
211
+ return amount * perUnit;
212
+ }
213
+ /** One sentence per reason a row was kept. The only place they are worded. */
214
+ function harnessPruneReasonSentence(row) {
215
+ switch (row.reason) {
216
+ case 'recent':
217
+ return 'seen too recently to be sure it is gone';
218
+ case 'latest_registration':
219
+ return 'the newest registration of its provider on that machine';
220
+ case 'no_live_sibling':
221
+ return 'nothing has replaced it - no daemon on that machine is online for this provider';
222
+ case 'pinned': {
223
+ const names = (row.detail?.agents ?? []).map((a) => a.name ?? a.id);
224
+ const more = row.detail?.moreAgents ?? 0;
225
+ const others = row.detail?.otherAuthors ?? 0;
226
+ const parts = [];
227
+ if (names.length > 0) {
228
+ parts.push(`${names.join(', ')}${more > 0 ? ` and ${more} more` : ''}`);
229
+ }
230
+ if (others > 0)
231
+ parts.push(`${others} agent${others === 1 ? '' : 's'} of other people`);
232
+ return `agents are pinned to it${parts.length > 0 ? ` (${parts.join('; ')})` : ''}`;
233
+ }
234
+ case 'granted':
235
+ return 'it is lent to a workspace';
236
+ case 'default':
237
+ return 'it is your default harness';
238
+ case 'aliased':
239
+ return 'you gave it a name';
240
+ case 'changed_since_plan':
241
+ return 'it changed while this ran (came back online, or was pinned)';
242
+ default:
243
+ return String(row.reason ?? 'kept');
244
+ }
245
+ }
176
246
  /**
177
247
  * A per-USER display name. Distinct from `updateHarness({name})`, which renames
178
248
  * the harness for everyone it is shared with — this only changes what YOU see,
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Whether a `skrr` invocation asked for JSON — ONE definition.
3
+ *
4
+ * `BaseCommand` decides this for a command that is running. A command that was
5
+ * never found has no `BaseCommand`: the `command_not_found` hook runs before one
6
+ * exists, so it asked nothing and printed prose to stderr, and a script that
7
+ * piped `--json` output to `jq` got "parse error: Invalid numeric literal"
8
+ * instead of the refusal (OSK-16434). Both now read the same rule, so a flag
9
+ * added to one cannot be missing from the other.
10
+ */
11
+ export declare function wantsJsonFromArgv(argv: readonly string[], env?: NodeJS.ProcessEnv): boolean;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.wantsJsonFromArgv = wantsJsonFromArgv;
4
+ /**
5
+ * Whether a `skrr` invocation asked for JSON — ONE definition.
6
+ *
7
+ * `BaseCommand` decides this for a command that is running. A command that was
8
+ * never found has no `BaseCommand`: the `command_not_found` hook runs before one
9
+ * exists, so it asked nothing and printed prose to stderr, and a script that
10
+ * piped `--json` output to `jq` got "parse error: Invalid numeric literal"
11
+ * instead of the refusal (OSK-16434). Both now read the same rule, so a flag
12
+ * added to one cannot be missing from the other.
13
+ */
14
+ function wantsJsonFromArgv(argv, env = process.env) {
15
+ return (argv.includes('--json') ||
16
+ argv.includes('--ndjson') ||
17
+ env.SKY_JSON === '1' ||
18
+ env.OVERSKY_CLI_JSON === '1');
19
+ }
@@ -28,4 +28,29 @@ export declare function stripStorageFields(value: Json): Json;
28
28
  * untouched, as is JSON with nothing to strip, so existing formatting survives.
29
29
  */
30
30
  export declare function stripStorageFieldsFromText(text: string): string;
31
+ /**
32
+ * The outcome marker for a `--json` RESULT document (OSK-16425).
33
+ *
34
+ * A failure prints `{ok:false, error:{...}}` on stdout, so that agents get the
35
+ * refusal structured. A success printed whatever the server sent, with no `ok`
36
+ * at all — so a script could not test one field for both, and a projection such
37
+ * as `jq '{id, title}'` turned the error document into `{"id":null,"title":null}`,
38
+ * which reads as an empty success. The reporter's own diagnosis of a failed
39
+ * command was wrong for exactly that reason.
40
+ *
41
+ * Adding `ok` here, to a document a command already holds, is truthful by
42
+ * construction: the caller knows whether the command succeeded when it prints.
43
+ * It is NOT done to text in `BaseCommand.log`, the way storage fields are
44
+ * stripped, because `log` cannot see the exit status to come: `doctor`,
45
+ * `tasks wait`, `outbox retry` and `code install` print a document and then
46
+ * exit non-zero, and stamping `ok:true` on those would assert a success the
47
+ * exit code denies.
48
+ *
49
+ * Only an object can carry it. An array is returned as it is, and so is an
50
+ * object that already says `ok` — that command's own meaning wins. `ok` is put
51
+ * first so it is the first thing read.
52
+ */
53
+ export declare function withOk<T>(value: T, ok?: boolean): T | ({
54
+ ok: boolean;
55
+ } & Record<string, unknown>);
31
56
  export {};
@@ -20,6 +20,7 @@
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.stripStorageFields = stripStorageFields;
22
22
  exports.stripStorageFieldsFromText = stripStorageFieldsFromText;
23
+ exports.withOk = withOk;
23
24
  function usableId(value) {
24
25
  return (typeof value === 'string' && value.length > 0) || typeof value === 'number';
25
26
  }
@@ -76,3 +77,32 @@ function stripStorageFieldsFromText(text) {
76
77
  return text;
77
78
  return JSON.stringify(stripped, null, text.includes('\n') ? 2 : undefined);
78
79
  }
80
+ /**
81
+ * The outcome marker for a `--json` RESULT document (OSK-16425).
82
+ *
83
+ * A failure prints `{ok:false, error:{...}}` on stdout, so that agents get the
84
+ * refusal structured. A success printed whatever the server sent, with no `ok`
85
+ * at all — so a script could not test one field for both, and a projection such
86
+ * as `jq '{id, title}'` turned the error document into `{"id":null,"title":null}`,
87
+ * which reads as an empty success. The reporter's own diagnosis of a failed
88
+ * command was wrong for exactly that reason.
89
+ *
90
+ * Adding `ok` here, to a document a command already holds, is truthful by
91
+ * construction: the caller knows whether the command succeeded when it prints.
92
+ * It is NOT done to text in `BaseCommand.log`, the way storage fields are
93
+ * stripped, because `log` cannot see the exit status to come: `doctor`,
94
+ * `tasks wait`, `outbox retry` and `code install` print a document and then
95
+ * exit non-zero, and stamping `ok:true` on those would assert a success the
96
+ * exit code denies.
97
+ *
98
+ * Only an object can carry it. An array is returned as it is, and so is an
99
+ * object that already says `ok` — that command's own meaning wins. `ok` is put
100
+ * first so it is the first thing read.
101
+ */
102
+ function withOk(value, ok = true) {
103
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
104
+ return value;
105
+ if (Object.prototype.hasOwnProperty.call(value, 'ok'))
106
+ return value;
107
+ return { ok, ...value };
108
+ }
@@ -18,7 +18,7 @@ export type ListEnvelope = {
18
18
  * Documented in docs/cli/SKRR_TASKS.md (List output for agents).
19
19
  */
20
20
  export declare const COMPACT_FIELDS: {
21
- readonly task: readonly ["id", "identifier", "ref", "title", "status", "statusType", "priority", "estimate", "assigneeAgentIds", "assigneeUserIds", "parentId", "spaceId", "projectId", "goalId", "dueDate", "scheduledFor", "labelIds", "tags", "externalKey", "version", "executionMode", "nextAction", "createdAt", "updatedAt", "url"];
21
+ readonly task: readonly ["id", "identifier", "ref", "title", "status", "statusType", "priority", "estimate", "assigneeAgentIds", "assigneeUserIds", "parentId", "spaceId", "projectId", "goalId", "dueDate", "scheduledFor", "labelIds", "tags", "externalKey", "version", "executionMode", "nextAction", "economicsRank", "createdAt", "updatedAt", "url"];
22
22
  readonly space: readonly ["id", "identifier", "title", "status", "parentSpaceId", "leadAgentId", "labelIds", "archivedAt", "createdAt", "updatedAt", "url"];
23
23
  readonly project: readonly ["id", "identifier", "title", "spaceId", "externalKey", "taskCounts", "archivedAt", "createdAt", "updatedAt", "url"];
24
24
  };
@@ -64,6 +64,15 @@ export declare function limitNote(requested: number | undefined, rowCount: numbe
64
64
  };
65
65
  /** The text-mode twin of a warning: one `Note:` line a person can act on. */
66
66
  export declare function warningLines(warnings: readonly ListWarning[] | undefined): string[];
67
+ /**
68
+ * The note a TABLE prints when it was given a JSON-only projection flag. `--compact` and
69
+ * `--fields` shape JSON output; on the table they did nothing and said nothing, so `--compact`
70
+ * looked broken (OSK-15374). Empty when neither flag was given.
71
+ */
72
+ export declare function textModeProjectionNotes(flags: {
73
+ fields?: string;
74
+ compact?: boolean;
75
+ }): string[];
67
76
  export type ListOutput = {
68
77
  stdout: string[];
69
78
  stderr: string[];
@@ -7,6 +7,7 @@ exports.toListEnvelope = toListEnvelope;
7
7
  exports.shapeListResponse = shapeListResponse;
8
8
  exports.limitNote = limitNote;
9
9
  exports.warningLines = warningLines;
10
+ exports.textModeProjectionNotes = textModeProjectionNotes;
10
11
  exports.renderListOutput = renderListOutput;
11
12
  /**
12
13
  * Shared output shaping for `skrr` LIST commands (agent-surface audit
@@ -29,6 +30,7 @@ exports.renderListOutput = renderListOutput;
29
30
  * Pure functions, no oclif: the commands own I/O, this owns the shape.
30
31
  */
31
32
  const core_1 = require("@oclif/core");
33
+ const json_output_1 = require("./json-output");
32
34
  /**
33
35
  * The navigation projection per resource: enough to identify a row, decide
34
36
  * whether it matters and fetch it with `show`. Allowlists, not drop-lists, so a
@@ -59,6 +61,8 @@ exports.COMPACT_FIELDS = {
59
61
  'version',
60
62
  'executionMode',
61
63
  'nextAction',
64
+ // Present only on a `--rank economics` page: the score the row was sorted by (OSK-16476).
65
+ 'economicsRank',
62
66
  'createdAt',
63
67
  'updatedAt',
64
68
  'url',
@@ -206,7 +210,11 @@ function shapeListResponse(response, options) {
206
210
  if (limit.warning)
207
211
  warnings.push(limit.warning);
208
212
  return {
209
- ...envelope,
213
+ // A list that reached this shaper succeeded; a failure never does, and prints
214
+ // `{ok:false, error}`. Saying so lets a script test one field for both, and
215
+ // keeps a projection of a failure from reading as an empty list (OSK-16425).
216
+ // The server's own `ok`, if it sent one, is kept.
217
+ ...(0, json_output_1.withOk)(envelope),
210
218
  data,
211
219
  ...(limit.applied !== undefined ? { limit_applied: limit.applied } : {}),
212
220
  ...(options.filterApplied ? { filter_applied: options.filterApplied } : {}),
@@ -247,6 +255,19 @@ function limitNote(requested, rowCount, hasMore) {
247
255
  function warningLines(warnings) {
248
256
  return (warnings ?? []).map((w) => `Note: ${w.hint}`);
249
257
  }
258
+ /**
259
+ * The note a TABLE prints when it was given a JSON-only projection flag. `--compact` and
260
+ * `--fields` shape JSON output; on the table they did nothing and said nothing, so `--compact`
261
+ * looked broken (OSK-15374). Empty when neither flag was given.
262
+ */
263
+ function textModeProjectionNotes(flags) {
264
+ const given = [flags.fields ? '--fields' : null, flags.compact ? '--compact' : null].filter(Boolean);
265
+ if (given.length === 0)
266
+ return [];
267
+ return [
268
+ `Note: ${given.join(' and ')} ${given.length > 1 ? 'shape' : 'shapes'} JSON output only; the table is unchanged. Add --json (or --ndjson) to apply ${given.length > 1 ? 'them' : 'it'}.`,
269
+ ];
270
+ }
250
271
  /**
251
272
  * Render a shaped list for `--json` (pretty envelope) or `--ndjson` (rows one per
252
273
  * line, `_meta` on stderr). Pure; the caller writes the lines.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Whether the local daemon is actually signed in and connected — as opposed to
3
+ * having been handed a credential (OSK-16437).
4
+ *
5
+ * `skrr login` used to print "The local daemon was signed in too" the moment
6
+ * `accept-handoff` exited 0. That means the credential was WRITTEN. It says
7
+ * nothing about a daemon using it, and a handoff that wrote a credential for a
8
+ * service that was not running, or that the server then refused, left a login
9
+ * that announced success on one line and failed to verify it on the next, with
10
+ * nothing saying which was true. `skrr daemon start` learned the same lesson
11
+ * ("a supervisor's successful `kickstart` means only that it accepted the
12
+ * request") and asks the daemon itself; this is that question for login.
13
+ *
14
+ * Two probes, in order of how directly they answer:
15
+ *
16
+ * 1. the loopback identity endpoint of THIS profile's daemon, which says it is
17
+ * connected as this account on this server — the only proof;
18
+ * 2. `skrrd status --json`, asked only once the first has not answered, to say
19
+ * WHY: the server refused the credential, the service is not running, or it
20
+ * is running and has not connected yet. Those have different fixes.
21
+ */
22
+ export type LocalDaemonReadiness = {
23
+ state: 'connected';
24
+ daemonId: string;
25
+ } | {
26
+ state: 'reauth_required';
27
+ profile: string;
28
+ reason: string | null;
29
+ command: string;
30
+ } | {
31
+ state: 'not_running';
32
+ profile: string;
33
+ runtimeStatus: string | null;
34
+ } | {
35
+ state: 'not_connected';
36
+ profile: string;
37
+ } | {
38
+ state: 'unreadable';
39
+ profile: string;
40
+ };
41
+ export interface ReadinessDeps {
42
+ /** One look at the loopback identity endpoint; the daemon id when it is connected as us. */
43
+ inspect: () => Promise<string | null>;
44
+ /** `skrrd status --json` for this profile. */
45
+ status: () => Promise<{
46
+ stdout: string;
47
+ code: number;
48
+ }>;
49
+ now: () => number;
50
+ sleep: (ms: number) => Promise<void>;
51
+ }
52
+ export interface ConfirmOptions {
53
+ /** How long a daemon that is still starting gets. */
54
+ waitMs?: number;
55
+ pollMs?: number;
56
+ /** How long a service that reads `stopped` is given before it is believed. */
57
+ stoppedGraceMs?: number;
58
+ /** Progress, printed once and only when the first look found nothing. */
59
+ log?: (line: string) => void;
60
+ deps?: Partial<ReadinessDeps>;
61
+ }
62
+ export declare function confirmLocalDaemonSignedIn(options?: ConfirmOptions): Promise<LocalDaemonReadiness>;
63
+ /**
64
+ * What `skrr login` prints about a daemon that was handed a credential and did
65
+ * not come up with it: what happened, why, and the one command. Pure, so every
66
+ * state can be read without a daemon.
67
+ */
68
+ export declare function describeUnreadyLocalDaemon(readiness: Exclude<LocalDaemonReadiness, {
69
+ state: 'connected';
70
+ }>, commands: {
71
+ status: string;
72
+ logs: string;
73
+ start: string;
74
+ }): string[];
75
+ /**
76
+ * The end of `skrr login` for a daemon that was handed a credential: wait for it
77
+ * to connect, then say ONE true thing. "Signed in too" only for a daemon that
78
+ * verified connected as this account; otherwise what happened, why, and the one
79
+ * command — and never both, which is what the login used to do.
80
+ */
81
+ export declare function reportLocalDaemonAfterHandoff(options: {
82
+ /** `skrr login` may have been handed the daemon's account/deployment line already. */
83
+ deliveredDetail: string;
84
+ bin: string;
85
+ log: (line: string) => void;
86
+ confirm?: typeof confirmLocalDaemonSignedIn;
87
+ }): Promise<{
88
+ daemonId?: string;
89
+ notReady: boolean;
90
+ }>;
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.confirmLocalDaemonSignedIn = confirmLocalDaemonSignedIn;
4
+ exports.describeUnreadyLocalDaemon = describeUnreadyLocalDaemon;
5
+ exports.reportLocalDaemonAfterHandoff = reportLocalDaemonAfterHandoff;
6
+ const exec_runtime_binary_1 = require("./exec-runtime-binary");
7
+ const managed_instruction_local_1 = require("./managed-instruction-local");
8
+ const keychain_1 = require("./keychain");
9
+ const config_1 = require("./config");
10
+ const runtime_start_verification_1 = require("./runtime-start-verification");
11
+ const daemon_command_hint_1 = require("./daemon-command-hint");
12
+ /** Each probe reads its inputs when called, so an injected one never touches real state. */
13
+ function defaultDeps() {
14
+ return {
15
+ inspect: () => (0, managed_instruction_local_1.inspectLocalInstructionDaemon)((0, keychain_1.getActiveProfile)(), (0, config_1.loadConfig)().baseURL),
16
+ status: async () => {
17
+ const profile = (0, keychain_1.getActiveProfile)();
18
+ const args = [
19
+ 'status',
20
+ '--json',
21
+ ...(profile !== keychain_1.DEFAULT_PROFILE ? ['--profile', profile] : []),
22
+ ];
23
+ try {
24
+ const out = await (0, exec_runtime_binary_1.execRuntimeBinaryCapture)(args, { timeoutMs: 15_000 });
25
+ return { stdout: out.stdout, code: out.code };
26
+ }
27
+ catch {
28
+ return { stdout: '', code: 1 };
29
+ }
30
+ },
31
+ now: () => Date.now(),
32
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
33
+ };
34
+ }
35
+ async function confirmLocalDaemonSignedIn(options = {}) {
36
+ const deps = { ...defaultDeps(), ...options.deps };
37
+ const waitMs = options.waitMs ?? 20_000;
38
+ const pollMs = options.pollMs ?? 500;
39
+ const stoppedGraceMs = options.stoppedGraceMs ?? 6_000;
40
+ const profile = (0, keychain_1.getActiveProfile)();
41
+ const started = deps.now();
42
+ let announced = false;
43
+ let lastDiagnosisAt = -Infinity;
44
+ let verdict = { state: 'not_connected', profile };
45
+ for (;;) {
46
+ const daemonId = await deps.inspect();
47
+ if (daemonId)
48
+ return { state: 'connected', daemonId };
49
+ if (!announced) {
50
+ options.log?.('Waiting for the local daemon to connect...');
51
+ announced = true;
52
+ }
53
+ const elapsed = deps.now() - started;
54
+ // Ask WHY at a measured pace — each look spawns `skrrd` — and stop waiting
55
+ // the moment the answer is one no amount of waiting changes.
56
+ if (deps.now() - lastDiagnosisAt >= 2_000) {
57
+ lastDiagnosisAt = deps.now();
58
+ const status = await deps.status();
59
+ const verification = (0, runtime_start_verification_1.verifyRuntimeStart)(status.stdout, status.code, ['--profile', profile]);
60
+ if (verification.status === 'reauth_required') {
61
+ return {
62
+ state: 'reauth_required',
63
+ profile: verification.profile,
64
+ reason: verification.reason,
65
+ command: verification.recoveryCommand,
66
+ };
67
+ }
68
+ if (verification.status === 'not_running') {
69
+ verdict = {
70
+ state: 'not_running',
71
+ profile: verification.profile,
72
+ runtimeStatus: verification.runtimeStatus,
73
+ };
74
+ // A service that was just started reads `stopped` for a moment between
75
+ // launchd accepting the request and the process existing; only believe
76
+ // it once it has had that long.
77
+ if (elapsed >= stoppedGraceMs)
78
+ return verdict;
79
+ }
80
+ else if (verification.status === 'unreadable_status') {
81
+ verdict = { state: 'unreadable', profile: verification.profile };
82
+ }
83
+ else {
84
+ verdict = { state: 'not_connected', profile };
85
+ }
86
+ }
87
+ if (elapsed >= waitMs)
88
+ return verdict;
89
+ await deps.sleep(Math.min(pollMs, Math.max(0, waitMs - elapsed)));
90
+ }
91
+ }
92
+ /**
93
+ * What `skrr login` prints about a daemon that was handed a credential and did
94
+ * not come up with it: what happened, why, and the one command. Pure, so every
95
+ * state can be read without a daemon.
96
+ */
97
+ function describeUnreadyLocalDaemon(readiness, commands) {
98
+ switch (readiness.state) {
99
+ case 'reauth_required':
100
+ return [
101
+ `The local daemon received its sign-in but cannot use it: ${readiness.reason === 'TOKEN_MISSING'
102
+ ? 'it started without a credential'
103
+ : `the server refused it (${readiness.reason ?? 'UNKNOWN'})`}.`,
104
+ ` Sign it in with: ${readiness.command}`,
105
+ ];
106
+ case 'not_running':
107
+ return [
108
+ `The local daemon received its sign-in but is not running (service status: ${readiness.runtimeStatus ?? 'unknown'}).`,
109
+ ` See why with \`${commands.logs}\`, then start it with \`${commands.start}\`.`,
110
+ ];
111
+ case 'not_connected':
112
+ return [
113
+ 'The local daemon received its sign-in and is running, but has not connected yet.',
114
+ ` Check it with \`${commands.status}\`.`,
115
+ ];
116
+ case 'unreadable':
117
+ return [
118
+ 'The local daemon received its sign-in, but its status could not be read to confirm it.',
119
+ ` Check it with \`${commands.status}\`.`,
120
+ ];
121
+ }
122
+ }
123
+ /**
124
+ * The end of `skrr login` for a daemon that was handed a credential: wait for it
125
+ * to connect, then say ONE true thing. "Signed in too" only for a daemon that
126
+ * verified connected as this account; otherwise what happened, why, and the one
127
+ * command — and never both, which is what the login used to do.
128
+ */
129
+ async function reportLocalDaemonAfterHandoff(options) {
130
+ const readiness = await (options.confirm ?? confirmLocalDaemonSignedIn)({ log: options.log });
131
+ if (readiness.state === 'connected') {
132
+ options.log(`The local daemon was signed in too — ${options.deliveredDetail}.`);
133
+ return { daemonId: readiness.daemonId, notReady: false };
134
+ }
135
+ for (const line of describeUnreadyLocalDaemon(readiness, {
136
+ status: (0, daemon_command_hint_1.daemonCommandHint)('status', { bin: options.bin }),
137
+ logs: (0, daemon_command_hint_1.daemonCommandHint)('logs', { bin: options.bin }),
138
+ start: (0, daemon_command_hint_1.daemonCommandHint)('start', { bin: options.bin }),
139
+ })) {
140
+ options.log(line);
141
+ }
142
+ return { notReady: true };
143
+ }
@@ -53,6 +53,35 @@ export interface MachineHarnessRow {
53
53
  * them.
54
54
  */
55
55
  superseded?: boolean;
56
+ /**
57
+ * Recorded `online`, but no heartbeat for longer than the server's stale
58
+ * threshold (`MachineContact.staleAfterMs`). Present only when true. The server
59
+ * decides this once; a row's own `status` stays what was recorded
60
+ * (OSK-16442).
61
+ */
62
+ stale?: boolean;
63
+ /** How long that row has been silent, in ms. Present with `stale`. */
64
+ staleForMs?: number;
65
+ }
66
+ /**
67
+ * What a machine's heartbeats say, apart from what its rows are recorded as
68
+ * (OSK-16442). A daemon that was stopped or frozen stays recorded `online` for a
69
+ * minute or more before the server gives up on it, so `status` alone said
70
+ * `online` for a process that had not been heard from in a minute.
71
+ *
72
+ * - `live`: a row is online and heard from recently.
73
+ * - `stale`: rows are recorded online, but none has been heard from for
74
+ * `staleAfterMs`. The machine's `status` is `degraded` meanwhile.
75
+ * - `offline`: nothing is recorded online.
76
+ *
77
+ * The thresholds are the server's, sent so this CLI never restates them. Absent
78
+ * on a server older than this field and on a lease-backed machine.
79
+ */
80
+ export interface MachineContact {
81
+ state: 'live' | 'stale' | 'offline';
82
+ staleForMs: number | null;
83
+ staleAfterMs: number;
84
+ offlineAfterMs: number;
56
85
  }
57
86
  export interface Machine {
58
87
  id: string;
@@ -69,6 +98,7 @@ export interface Machine {
69
98
  * none will ever arrive.
70
99
  */
71
100
  statusReason?: string | null;
101
+ contact?: MachineContact | null;
72
102
  machineUuid: string | null;
73
103
  stableDaemonId: string | null;
74
104
  daemonId: string | null;
@@ -120,6 +150,93 @@ export declare function machineStateLabel(machine: Machine): string;
120
150
  export declare function hiddenMachinesSummary(hidden: readonly Machine[]): string;
121
151
  /** Is this a machine a user could still use, or bring back? */
122
152
  export declare function isReachableMachine(machine: Machine): boolean;
153
+ /**
154
+ * A computer somebody installed a daemon on, as opposed to a hosted sandbox.
155
+ *
156
+ * A daemon launched on a person's own computer registers as
157
+ * `daemon_<hash>_<source>[_<profile>]` (`parseDaemonId`); a hosted or cloud
158
+ * machine's daemon carries an id minted by the control plane
159
+ * (`hosted-machine-…`, `cloud-coding-…`) that names no launch source. A lease
160
+ * decides a managed machine's own state, so it is never one of these.
161
+ */
162
+ export declare function isOwnComputer(machine: Machine): boolean;
163
+ /**
164
+ * What the default `machines list` keeps: everything reachable, and every one of
165
+ * the person's own computers while it is offline (OSK-16442).
166
+ *
167
+ * The default hides offline machines because hosted sandboxes accumulate by the
168
+ * hundreds and buried the one that was usable (OSK-7639). Applied to a computer
169
+ * it did something else: the moment its last daemon stopped, the whole row left
170
+ * the list, in text and in `--json`, so "my computer is offline" and "my computer
171
+ * is gone" read the same. A computer is a handful of rows however long it is
172
+ * kept, and `offline` with its last-seen time is the answer a person asks the
173
+ * list for, so it stays; only what can no longer be used is left behind `--all`.
174
+ */
175
+ export declare function isListedByDefault(machine: Machine): boolean;
176
+ /**
177
+ * One daemon registration on a machine, as its own record (OSK-16440).
178
+ *
179
+ * A machine row aggregates every harness registration ever made under one
180
+ * hardware identity. The daemon that made each is only a string on a harness
181
+ * row, and the profile it ran as is a suffix of that string — so a script that
182
+ * wanted "the daemon that just registered" had to take the first unfamiliar
183
+ * `daemonId` out of a flat list that, depending on whether anything on the
184
+ * computer happened to be online, held 4 rows or 98 of them.
185
+ */
186
+ export interface MachineDaemonSummary {
187
+ daemonId: string;
188
+ /** The profile the daemon ran as, read from its id; null for an id that names none (a hosted box). */
189
+ profile: string | null;
190
+ /** How it was launched (`cli`, `launchd`, …); null when the id does not say. */
191
+ source: string | null;
192
+ /**
193
+ * `online` when any registration is online and heard from recently, `stale`
194
+ * when some are recorded online but none has been heard from lately, `offline`
195
+ * otherwise.
196
+ */
197
+ status: 'online' | 'stale' | 'offline';
198
+ lastSeenAt: string | null;
199
+ harnessCount: number;
200
+ /** Of those, how many a newer registration of the same provider replaced. */
201
+ supersededCount: number;
202
+ /** The profile this CLI is running as. */
203
+ activeProfile: boolean;
204
+ }
205
+ export declare function parseDaemonId(daemonId: string): {
206
+ source: string;
207
+ profile: string;
208
+ } | null;
209
+ export declare function summarizeDaemons(harnesses: readonly MachineHarnessRow[], activeProfile: string): MachineDaemonSummary[];
210
+ /** Whether the machine's identity is THIS computer's (the default profile's, or a profile qualified from it). */
211
+ export declare function isThisComputer(machine: Machine, hardwareMachineUuid: string): boolean;
212
+ export interface MachineListView extends Machine {
213
+ /** This machine is the computer the CLI is running on. */
214
+ thisComputer: boolean;
215
+ /** One record per daemon registration; see {@link MachineDaemonSummary}. */
216
+ daemons: MachineDaemonSummary[];
217
+ /**
218
+ * Harness rows left out of `harnesses` because a newer registration replaced
219
+ * them (default view only). `supersededHarnessCount` still reports the
220
+ * server's own total, so nothing is silently subtracted.
221
+ */
222
+ hiddenHarnessCount?: number;
223
+ }
224
+ /**
225
+ * What `machines list` shows of one machine (OSK-16440).
226
+ *
227
+ * The default list is "what is reachable now", and a machine's own history is
228
+ * not part of that. A machine row carries every harness registration it ever
229
+ * had; one of them being online made the whole 98-row history appear in a list
230
+ * that, a minute earlier, had held four rows — the same command, a different
231
+ * answer, for no reason a reader could see. So the default view keeps the
232
+ * registrations that still stand and says how many it left out; `--all` keeps
233
+ * everything.
234
+ */
235
+ export declare function machineListView(machine: Machine, options: {
236
+ all: boolean;
237
+ hardwareMachineUuid: string;
238
+ activeProfile: string;
239
+ }): MachineListView;
123
240
  /** The user-editable slice. Identities, capabilities and lifecycle are not. */
124
241
  export interface MachinePreferencesPatch {
125
242
  name?: string;