@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
@@ -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;
@@ -3,6 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.machineStateLabel = machineStateLabel;
4
4
  exports.hiddenMachinesSummary = hiddenMachinesSummary;
5
5
  exports.isReachableMachine = isReachableMachine;
6
+ exports.isOwnComputer = isOwnComputer;
7
+ exports.isListedByDefault = isListedByDefault;
8
+ exports.parseDaemonId = parseDaemonId;
9
+ exports.summarizeDaemons = summarizeDaemons;
10
+ exports.isThisComputer = isThisComputer;
11
+ exports.machineListView = machineListView;
6
12
  exports.liveHarnessCount = liveHarnessCount;
7
13
  exports.listMachines = listMachines;
8
14
  exports.getMachine = getMachine;
@@ -25,8 +31,20 @@ const api_fetch_1 = require("./api-fetch");
25
31
  * could come back says so, and a destroyed one cannot.
26
32
  */
27
33
  function machineStateLabel(machine) {
34
+ // `degraded` is the machine's status while a daemon is silent, and it reads the
35
+ // same as a sign-in problem or one provider being down. The server says which
36
+ // it is; the word a person reads is that one (OSK-16442).
37
+ if (machine.status === 'degraded' && machine.contact?.state === 'stale')
38
+ return 'stale';
28
39
  if (machine.status !== 'offline')
29
40
  return machine.status;
41
+ // A computer somebody installed a daemon on has no lease to resume and nothing
42
+ // to terminate, which is also exactly what a destroyed sandbox looks like, so
43
+ // the check below read every switched-off computer as `terminated`: the one
44
+ // word for "this can never come back", beside a machine that comes back the
45
+ // moment its daemon starts (OSK-16442).
46
+ if (isOwnComputer(machine))
47
+ return 'offline';
30
48
  const controls = machine.controls;
31
49
  if (controls && controls.canResume === false && controls.canTerminate === false) {
32
50
  return 'terminated';
@@ -65,6 +83,127 @@ function hiddenMachinesSummary(hidden) {
65
83
  function isReachableMachine(machine) {
66
84
  return machineStateLabel(machine) !== 'terminated' && machine.status !== 'offline';
67
85
  }
86
+ /**
87
+ * A computer somebody installed a daemon on, as opposed to a hosted sandbox.
88
+ *
89
+ * A daemon launched on a person's own computer registers as
90
+ * `daemon_<hash>_<source>[_<profile>]` (`parseDaemonId`); a hosted or cloud
91
+ * machine's daemon carries an id minted by the control plane
92
+ * (`hosted-machine-…`, `cloud-coding-…`) that names no launch source. A lease
93
+ * decides a managed machine's own state, so it is never one of these.
94
+ */
95
+ function isOwnComputer(machine) {
96
+ if (machine.lease)
97
+ return false;
98
+ return (machine.harnesses ?? []).some((row) => typeof row.daemonId === 'string' && parseDaemonId(row.daemonId) !== null);
99
+ }
100
+ /**
101
+ * What the default `machines list` keeps: everything reachable, and every one of
102
+ * the person's own computers while it is offline (OSK-16442).
103
+ *
104
+ * The default hides offline machines because hosted sandboxes accumulate by the
105
+ * hundreds and buried the one that was usable (OSK-7639). Applied to a computer
106
+ * it did something else: the moment its last daemon stopped, the whole row left
107
+ * the list, in text and in `--json`, so "my computer is offline" and "my computer
108
+ * is gone" read the same. A computer is a handful of rows however long it is
109
+ * kept, and `offline` with its last-seen time is the answer a person asks the
110
+ * list for, so it stays; only what can no longer be used is left behind `--all`.
111
+ */
112
+ function isListedByDefault(machine) {
113
+ if (isReachableMachine(machine))
114
+ return true;
115
+ return isOwnComputer(machine) && machineStateLabel(machine) === 'offline';
116
+ }
117
+ /**
118
+ * `daemon_<hash>_<source>_<profile>` — the closed set of launch sources is what
119
+ * makes the profile recoverable, since a profile name may itself contain an
120
+ * underscore and a source never does (`HarnessService`'s runtime key parses the
121
+ * same id the same way). An id that predates profiles ends at the source.
122
+ */
123
+ const DAEMON_ID_PATTERN = /^daemon_[0-9a-f]+_(cli|launchd|systemd|schtasks|electron)(?:_(.+))?$/;
124
+ function parseDaemonId(daemonId) {
125
+ const match = DAEMON_ID_PATTERN.exec(daemonId);
126
+ return match ? { source: match[1], profile: match[2] ?? 'default' } : null;
127
+ }
128
+ function newerStamp(a, b) {
129
+ if (!a)
130
+ return b;
131
+ if (!b)
132
+ return a;
133
+ return new Date(b).getTime() > new Date(a).getTime() ? b : a;
134
+ }
135
+ function summarizeDaemons(harnesses, activeProfile) {
136
+ const byDaemon = new Map();
137
+ for (const row of harnesses) {
138
+ if (!row.daemonId)
139
+ continue;
140
+ const parsed = parseDaemonId(row.daemonId);
141
+ const existing = byDaemon.get(row.daemonId);
142
+ const entry = existing ?? {
143
+ daemonId: row.daemonId,
144
+ profile: parsed?.profile ?? null,
145
+ source: parsed?.source ?? null,
146
+ status: 'offline',
147
+ lastSeenAt: null,
148
+ harnessCount: 0,
149
+ supersededCount: 0,
150
+ activeProfile: parsed !== null && parsed.profile === activeProfile,
151
+ };
152
+ entry.harnessCount += 1;
153
+ if (row.superseded === true)
154
+ entry.supersededCount += 1;
155
+ if (row.status === 'online') {
156
+ if (row.stale !== true)
157
+ entry.status = 'online';
158
+ else if (entry.status === 'offline')
159
+ entry.status = 'stale';
160
+ }
161
+ entry.lastSeenAt = newerStamp(entry.lastSeenAt, row.lastSeenAt);
162
+ byDaemon.set(row.daemonId, entry);
163
+ }
164
+ // Connected first, then silent, then gone, each by most recently seen: the one a
165
+ // person is looking for is on top.
166
+ const rank = { online: 0, stale: 1, offline: 2 };
167
+ return [...byDaemon.values()].sort((a, b) => {
168
+ if (a.status !== b.status)
169
+ return rank[a.status] - rank[b.status];
170
+ return new Date(b.lastSeenAt ?? 0).getTime() - new Date(a.lastSeenAt ?? 0).getTime();
171
+ });
172
+ }
173
+ /** Whether the machine's identity is THIS computer's (the default profile's, or a profile qualified from it). */
174
+ function isThisComputer(machine, hardwareMachineUuid) {
175
+ const uuid = machine.machineUuid;
176
+ if (!uuid || !hardwareMachineUuid)
177
+ return false;
178
+ return uuid === hardwareMachineUuid || uuid.startsWith(`${hardwareMachineUuid}:p-`);
179
+ }
180
+ /**
181
+ * What `machines list` shows of one machine (OSK-16440).
182
+ *
183
+ * The default list is "what is reachable now", and a machine's own history is
184
+ * not part of that. A machine row carries every harness registration it ever
185
+ * had; one of them being online made the whole 98-row history appear in a list
186
+ * that, a minute earlier, had held four rows — the same command, a different
187
+ * answer, for no reason a reader could see. So the default view keeps the
188
+ * registrations that still stand and says how many it left out; `--all` keeps
189
+ * everything.
190
+ */
191
+ function machineListView(machine, options) {
192
+ const rows = machine.harnesses ?? [];
193
+ const daemons = summarizeDaemons(rows, options.activeProfile);
194
+ const visibleRows = options.all ? rows : rows.filter((row) => row.superseded !== true);
195
+ const visibleDaemons = options.all
196
+ ? daemons
197
+ : daemons.filter((daemon) => daemon.harnessCount > daemon.supersededCount);
198
+ const hidden = rows.length - visibleRows.length;
199
+ return {
200
+ ...machine,
201
+ thisComputer: isThisComputer(machine, options.hardwareMachineUuid),
202
+ daemons: visibleDaemons,
203
+ harnesses: visibleRows,
204
+ ...(hidden > 0 ? { hiddenHarnessCount: hidden } : {}),
205
+ };
206
+ }
68
207
  /**
69
208
  * How many of a machine's harnesses still represent a capability it has.
70
209
  *
@@ -10,3 +10,5 @@ export declare function resolveLocalInstructionDaemon(options?: {
10
10
  waitMs?: number;
11
11
  log?: (message: string) => void;
12
12
  }): Promise<string | null>;
13
+ /** One look at this profile's loopback identity endpoint: the daemon id when it is connected as this account. */
14
+ export declare function inspectLocalInstructionDaemon(profile: string, serverUrl: string): Promise<string | null>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveLocalInstructionDaemon = resolveLocalInstructionDaemon;
4
+ exports.inspectLocalInstructionDaemon = inspectLocalInstructionDaemon;
4
5
  const loopback_http_1 = require("@skrr-ai/auth-core/loopback-http");
5
6
  const data_provider_1 = require("@skrr-ai/data-provider");
6
7
  const config_1 = require("./config");
@@ -25,6 +26,7 @@ async function resolveLocalInstructionDaemon(options = {}) {
25
26
  await new Promise((resolve) => setTimeout(resolve, Math.min(300, deadline - Date.now())));
26
27
  }
27
28
  }
29
+ /** One look at this profile's loopback identity endpoint: the daemon id when it is connected as this account. */
28
30
  async function inspectLocalInstructionDaemon(profile, serverUrl) {
29
31
  for (const candidate of (0, daemonBroker_1.resolveExactProfileBootstrapPaths)(profile)) {
30
32
  const bootstrap = (0, daemonBroker_1.readBootstrap)(candidate);
@@ -1,2 +1,20 @@
1
1
  /** An old profile may still share the default machine row and its device pin. */
2
2
  export declare function legacyProfileSharesDefaultMachine(): boolean;
3
+ /**
4
+ * Why a sign-in on this profile stopped, and what to do instead (OSK-16436).
5
+ *
6
+ * This said the profile "was created before profiles had separate machine
7
+ * identities". For someone whose `skrr login --profile work` ran minutes ago,
8
+ * under a CLI that then updated itself, that is false in time and unhelpful in
9
+ * substance: the profile is new to them and old to the program. What is true is
10
+ * a fact about the VERSION that set it up — the older one gave every profile the
11
+ * computer's machine identity, and this profile still holds a credential bound
12
+ * to it — and a consequence: signing in again re-pins the default profile's
13
+ * device key and signs its daemon out.
14
+ *
15
+ * The one command is the CLI's own (`skrr login --profile NAME`), which signs
16
+ * the background service in as well; `skrrd --profile NAME login` is the
17
+ * daemon-only spelling and is named second. The suggested name is concrete so
18
+ * the line can be pasted.
19
+ */
20
+ export declare function describeLegacySharedProfile(profile: string): string[];
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.legacyProfileSharesDefaultMachine = legacyProfileSharesDefaultMachine;
4
+ exports.describeLegacySharedProfile = describeLegacySharedProfile;
4
5
  const machine_uuid_1 = require("@skrr-ai/auth-core/machine-uuid");
6
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
7
  const keychain_1 = require("./keychain");
6
8
  /** An old profile may still share the default machine row and its device pin. */
7
9
  function legacyProfileSharesDefaultMachine() {
@@ -11,3 +13,30 @@ function legacyProfileSharesDefaultMachine() {
11
13
  !(0, machine_uuid_1.hostedRuntimeIdOverride)('OVERSKY_MACHINE_UUID') &&
12
14
  (0, machine_uuid_1.getMachineUuid)() === (0, machine_uuid_1.getHardwareMachineUuid)());
13
15
  }
16
+ /**
17
+ * Why a sign-in on this profile stopped, and what to do instead (OSK-16436).
18
+ *
19
+ * This said the profile "was created before profiles had separate machine
20
+ * identities". For someone whose `skrr login --profile work` ran minutes ago,
21
+ * under a CLI that then updated itself, that is false in time and unhelpful in
22
+ * substance: the profile is new to them and old to the program. What is true is
23
+ * a fact about the VERSION that set it up — the older one gave every profile the
24
+ * computer's machine identity, and this profile still holds a credential bound
25
+ * to it — and a consequence: signing in again re-pins the default profile's
26
+ * device key and signs its daemon out.
27
+ *
28
+ * The one command is the CLI's own (`skrr login --profile NAME`), which signs
29
+ * the background service in as well; `skrrd --profile NAME login` is the
30
+ * daemon-only spelling and is named second. The suggested name is concrete so
31
+ * the line can be pasted.
32
+ */
33
+ function describeLegacySharedProfile(profile) {
34
+ const next = `${profile}-2`;
35
+ return [
36
+ `Profile '${profile}' was set up by an older ${data_provider_1.BRAND.displayName} version that gave every profile on this ` +
37
+ 'computer the same machine identity, and it still has it. Signing in here would replace the default ' +
38
+ "profile's device-key pin and sign the default daemon out. Nothing was changed.",
39
+ `Use a new profile name instead: skrr login --profile ${next}`,
40
+ `(For the background service alone: skrrd --profile ${next} login)`,
41
+ ];
42
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Read a server refusal back in the words the person typed.
3
+ *
4
+ * The API validates the request BODY, so its messages name body paths:
5
+ * `execution.workflowSignal is required for mode "workflow_signal"`. A person who built that body
6
+ * from flags never typed `execution.workflowSignal`, and there is no flag of that name, so the
7
+ * message pointed at nothing they could change (OSK-15313). A command that builds its body from
8
+ * flags declares which flag writes which path, and `handleApiError` rewrites any path it finds in
9
+ * the server's message to that flag. The server keeps speaking its own contract; the CLI, which
10
+ * is the only party that knows how the body was built, does the translation.
11
+ *
12
+ * A command that sent a body the person wrote themselves (`--from-json`) declares nothing: then
13
+ * the path IS what they typed.
14
+ */
15
+ /**
16
+ * Replace every declared body path in `message` with its flag. Longest path first, so
17
+ * `execution.workflowSignal.eventKey` is not half-rewritten by `execution.workflowSignal`; a
18
+ * path is only replaced as a whole token (not when followed by more of a longer path).
19
+ */
20
+ export declare function translateRequestFieldPaths(message: string | undefined, fieldFlags: Readonly<Record<string, string>> | undefined): string | undefined;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ /**
3
+ * Read a server refusal back in the words the person typed.
4
+ *
5
+ * The API validates the request BODY, so its messages name body paths:
6
+ * `execution.workflowSignal is required for mode "workflow_signal"`. A person who built that body
7
+ * from flags never typed `execution.workflowSignal`, and there is no flag of that name, so the
8
+ * message pointed at nothing they could change (OSK-15313). A command that builds its body from
9
+ * flags declares which flag writes which path, and `handleApiError` rewrites any path it finds in
10
+ * the server's message to that flag. The server keeps speaking its own contract; the CLI, which
11
+ * is the only party that knows how the body was built, does the translation.
12
+ *
13
+ * A command that sent a body the person wrote themselves (`--from-json`) declares nothing: then
14
+ * the path IS what they typed.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.translateRequestFieldPaths = translateRequestFieldPaths;
18
+ function escapeRegExp(value) {
19
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
20
+ }
21
+ /**
22
+ * Replace every declared body path in `message` with its flag. Longest path first, so
23
+ * `execution.workflowSignal.eventKey` is not half-rewritten by `execution.workflowSignal`; a
24
+ * path is only replaced as a whole token (not when followed by more of a longer path).
25
+ */
26
+ function translateRequestFieldPaths(message, fieldFlags) {
27
+ if (!message || !fieldFlags)
28
+ return message;
29
+ const paths = Object.keys(fieldFlags).sort((a, b) => b.length - a.length);
30
+ if (paths.length === 0)
31
+ return message;
32
+ const pattern = new RegExp(`(?<![\\w.])(${paths.map(escapeRegExp).join('|')})(?![\\w.]*\\w)`, 'g');
33
+ return message.replace(pattern, (path) => fieldFlags[path] ?? path);
34
+ }
@@ -1,4 +1,4 @@
1
1
  import type { WorkspaceGrantReport, WorkspaceStewardshipReport } from './workspaces';
2
2
  export declare function printStewardshipCoverage(report: WorkspaceGrantReport, log: (line: string) => void): void;
3
- export declare function printWorkspaceGrants(report: WorkspaceGrantReport, log: (line: string) => void): void;
3
+ export declare function printWorkspaceGrants(report: WorkspaceGrantReport, log: (line: string) => void, members?: ReadonlyMap<string, string>): void;
4
4
  export declare function printWorkspaceStewardship(report: WorkspaceStewardshipReport, log: (line: string) => void): void;