@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
@@ -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,40 @@ 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
+ // WHY this profile is on the shared row, from the record that decided it: which
36
+ // file made it "already hold credentials", and when. Absent rather than guessed
37
+ // when the record cannot be read.
38
+ let why = '';
39
+ try {
40
+ why = `${(0, machine_uuid_1.describeMachineIdentity)().sentence} `;
41
+ }
42
+ catch {
43
+ why = '';
44
+ }
45
+ return [
46
+ `Profile '${profile}' was set up by an older ${data_provider_1.BRAND.displayName} version that gave every profile on this ` +
47
+ `computer the same machine identity, and it still has it. ${why}Signing in here would replace the default ` +
48
+ "profile's device-key pin and sign the default daemon out. Nothing was changed.",
49
+ `Use a new profile name instead: skrr login --profile ${next}`,
50
+ `(For the background service alone: skrrd --profile ${next} login)`,
51
+ ];
52
+ }
@@ -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;
@@ -4,6 +4,7 @@ exports.printStewardshipCoverage = printStewardshipCoverage;
4
4
  exports.printWorkspaceGrants = printWorkspaceGrants;
5
5
  exports.printWorkspaceStewardship = printWorkspaceStewardship;
6
6
  const format_1 = require("./format");
7
+ const grants_1 = require("./grants");
7
8
  function printStewardshipCoverage(report, log) {
8
9
  if (report.coverage.complete) {
9
10
  log('Coverage: complete within the bounded sources.');
@@ -41,23 +42,24 @@ function printStewardshipCoverage(report, log) {
41
42
  }
42
43
  }
43
44
  }
44
- /** Who holds the Grant, in the same `kind id` words `skrr grants list` uses. */
45
- function principalText(principal) {
46
- const value = principal;
47
- if (!value?.kind)
48
- return '—';
49
- const id = value.agentId ?? value.commitmentId;
50
- return id ? `${value.kind} ${id}` : value.kind;
51
- }
52
- /** What the Grant allows: its capability names, or an honest dash when it lists none. */
53
- function accessText(capabilities) {
45
+ /** What the Grant's capabilities name, or an honest dash when it lists none. */
46
+ function capabilitiesText(capabilities) {
54
47
  return Array.isArray(capabilities) && capabilities.length ? capabilities.join(', ') : '—';
55
48
  }
56
- function printWorkspaceGrants(report, log) {
49
+ /**
50
+ * The owner as a person reads it: `Name (id)` when the id belongs to a current
51
+ * member, the bare id otherwise — an owner who has left is not a member, and
52
+ * the id is still the thing to act on.
53
+ */
54
+ function ownerText(ownerUserId, members) {
55
+ const name = members?.get(ownerUserId);
56
+ return name ? `${name} (${ownerUserId})` : ownerUserId;
57
+ }
58
+ function printWorkspaceGrants(report, log, members) {
57
59
  printStewardshipCoverage(report, log);
58
- printGrantRows(report, log);
60
+ printGrantRows(report, log, members);
59
61
  }
60
- function printGrantRows(report, log) {
62
+ function printGrantRows(report, log, members) {
61
63
  if (report.grants === null) {
62
64
  log('Grants: unavailable.');
63
65
  return;
@@ -69,30 +71,24 @@ function printGrantRows(report, log) {
69
71
  : 'No workspace Grants observed.');
70
72
  return;
71
73
  }
74
+ // The columns `skrr grants list` shows come from the same function, so a
75
+ // header means the same thing in both tables (OSK-15357); the workspace view
76
+ // adds who owns the Grant, its capabilities, its expiry and what uses it.
72
77
  (0, format_1.renderTable)(report.grants.map((row) => ({
73
- id: row.id,
74
- label: row.label,
75
- provider: row.provider || '—',
76
- principal: principalText(row.principal),
77
- access: accessText(row.capabilities),
78
- owner: row.ownerUserId,
79
- active: row.active ? 'yes' : 'no',
78
+ ...(0, grants_1.grantTableCells)(row),
79
+ owner: ownerText(row.ownerUserId, members),
80
+ capabilities: capabilitiesText(row.capabilities),
80
81
  // No expiry is a fact about the Grant; a blank cell read as "not readable" (OSK-15357).
81
82
  expires: row.expiresAt ?? 'never',
82
- envKeys: row.credential?.envKeys.length ? row.credential.envKeys.join(', ') : '—',
83
83
  referencedBy: row.referencedBy === null
84
84
  ? 'unavailable'
85
- : `${row.referencedBy.join(', ')}${row.referencesTruncated ? ' (truncated)' : ''}`,
85
+ : `${row.referencedBy.join(', ') || '—'}${row.referencesTruncated ? ' (truncated)' : ''}`,
86
86
  })), [
87
- { key: 'id', header: 'ID' },
88
- { key: 'label', header: 'LABEL' },
89
- { key: 'provider', header: 'PROVIDER' },
90
- { key: 'principal', header: 'HELD BY' },
91
- { key: 'access', header: 'ACCESS' },
87
+ { key: 'id', header: 'ID', verbatim: true },
88
+ ...grants_1.GRANT_TABLE_COLUMNS,
92
89
  { key: 'owner', header: 'OWNER' },
93
- { key: 'active', header: 'ACTIVE' },
90
+ { key: 'capabilities', header: 'CAPABILITIES' },
94
91
  { key: 'expires', header: 'EXPIRES' },
95
- { key: 'envKeys', header: 'ENV NAMES' },
96
92
  { key: 'referencedBy', header: 'REFERENCED BY' },
97
93
  ], log);
98
94
  }
@@ -101,7 +97,7 @@ function printWorkspaceStewardship(report, log) {
101
97
  log(`Membership: ${report.membership.status}${report.membership.role ? ` (${report.membership.role})` : ''}${
102
98
  // "Absent" is all the report knows: a removed member and an id that never existed read alike (OSK-15356).
103
99
  report.membership.status === 'absent'
104
- ? ' — not a current member; this report cannot tell a removed member from an id that never existed'
100
+ ? ' — not a current member; this report cannot tell a removed member from an id that never existed (`skrr workspaces members <workspace-id>` lists current members)'
105
101
  : ''}`);
106
102
  const isCut = (source) => report.coverage.sources.some((entry) => entry.name === source && entry.truncated);
107
103
  const count = (rows, source) => rows === null
@@ -26,6 +26,12 @@ export declare function batchIdempotencyKey(kind: 'create' | 'patch', items: unk
26
26
  export type BatchCreateNode = {
27
27
  key: string;
28
28
  parentKey?: string;
29
+ /**
30
+ * Keys of other nodes that block this one. Each blocker must come BEFORE this
31
+ * node in the list; the batch resolves them to real ids on the server (same
32
+ * chunk) or from `mapping` (an earlier chunk, or a task that already existed).
33
+ */
34
+ blockerKeys?: string[];
29
35
  payload: Record<string, unknown>;
30
36
  };
31
37
  export type BatchCreatedRow = {
@@ -56,13 +62,64 @@ export type BatchCreateOutcome = {
56
62
  supported: true;
57
63
  rows: BatchCreatedRow[];
58
64
  failures: BatchCreateFailure[];
65
+ /** Requests made to the batch route, retries included. */
66
+ rounds: number;
67
+ };
68
+ /** What a long create reports between requests (the command decides whether anyone sees it). */
69
+ export type BatchProgress = {
70
+ /** Nodes confirmed to exist so far, created now or found already there. */
71
+ confirmed: number;
72
+ total: number;
73
+ round: number;
74
+ elapsedMs: number;
75
+ /** Set while waiting before the next attempt, with why. */
76
+ waiting?: {
77
+ ms: number;
78
+ reason: string;
79
+ };
80
+ };
81
+ export type BatchCreateOptions = {
82
+ onProgress?: (progress: BatchProgress) => void;
83
+ };
84
+ /**
85
+ * How a create that is not finishing is retried. A seam rather than constants so
86
+ * a test can run the whole policy without waiting through it.
87
+ *
88
+ * Each round that confirms nothing waits the next interval and counts toward the
89
+ * limit; a round that confirms anything resets it. The total wait is bounded
90
+ * (about 15 s), on purpose: this is a courtesy to a server that is briefly slow
91
+ * or rate-limited, not a promise to outlast an outage — past it the command says
92
+ * what is unknown and how to continue.
93
+ */
94
+ export declare const batchRetryPolicy: {
95
+ backoffMs: number[];
96
+ sleep: (ms: number) => Promise<void>;
59
97
  };
60
98
  /**
61
- * Create `nodes` (parents before children) in chunks. `mapping` holds key -> id
62
- * for nodes that already exist and is extended in place. Stops after the first
63
- * chunk that reports a failure, as the serial path stopped at its first failure.
99
+ * Create `nodes` — parents and blockers BEFORE what depends on them — until every
100
+ * one exists, or say precisely which do not and why.
101
+ *
102
+ * One request is a bounded unit of work, not the whole job: the server answers
103
+ * within a response budget, creating what it can and reporting the rest as not
104
+ * started (`BATCH_DEADLINE_EXCEEDED`) or still in flight (`pending`). That is a
105
+ * normal, successful answer, so this loop is what turns it into a finished tree:
106
+ * submit, record what is confirmed, submit what is left. It used to stop at the
107
+ * first row the server had not confirmed and hand the person a `--resume` to
108
+ * type — once per row or two, against a server that confirms a row or two per
109
+ * request (OSK-S-440 #1, #3, #4).
110
+ *
111
+ * The one rule that makes resubmitting safe is the one the server enforces: a
112
+ * node with a stable `externalKey` can be created once. A submitted write whose
113
+ * outcome is unknown is therefore READ first (`reconcileSubmittedNodes`), and only
114
+ * if the key is still absent is the node sent again — where, if the first write
115
+ * has landed in the meantime, the unique index turns it into `exists`. A node with
116
+ * no stable key is never resubmitted after an uncertain outcome, because nothing
117
+ * could tell the second write from a duplicate; that case stops and says so, as
118
+ * before.
119
+ *
120
+ * `mapping` holds key -> id for nodes that already exist and is extended in place.
64
121
  */
65
- export declare function createNodesViaBatch(nodes: BatchCreateNode[], mapping: Record<string, string>): Promise<BatchCreateOutcome>;
122
+ export declare function createNodesViaBatch(nodes: BatchCreateNode[], mapping: Record<string, string>, options?: BatchCreateOptions): Promise<BatchCreateOutcome>;
66
123
  export type BatchPatchOutcome = {
67
124
  supported: false;
68
125
  } | {