@skrr-ai/cli 0.1.37 → 0.1.38

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 (174) hide show
  1. package/bin/dev-fallback.js +167 -4
  2. package/dist/base-command.js +46 -2
  3. package/dist/commands/agents/actions/create.js +18 -7
  4. package/dist/commands/agents/actions/delete.d.ts +1 -0
  5. package/dist/commands/agents/actions/delete.js +7 -0
  6. package/dist/commands/agents/actions/update.js +5 -3
  7. package/dist/commands/agents/api-actions/add.js +5 -2
  8. package/dist/commands/agents/api-actions/index.d.ts +11 -0
  9. package/dist/commands/agents/api-actions/index.js +27 -1
  10. package/dist/commands/agents/avatar/default/index.d.ts +11 -0
  11. package/dist/commands/agents/avatar/default/index.js +17 -0
  12. package/dist/commands/agents/avatar/default/set.d.ts +2 -1
  13. package/dist/commands/agents/avatar/default/set.js +32 -7
  14. package/dist/commands/agents/avatar/upload.js +7 -1
  15. package/dist/commands/agents/categories/delete.js +8 -0
  16. package/dist/commands/agents/categories/index.d.ts +21 -5
  17. package/dist/commands/agents/categories/index.js +68 -15
  18. package/dist/commands/agents/chat.d.ts +21 -0
  19. package/dist/commands/agents/chat.js +94 -7
  20. package/dist/commands/agents/create.js +11 -4
  21. package/dist/commands/agents/list.d.ts +15 -0
  22. package/dist/commands/agents/list.js +31 -1
  23. package/dist/commands/agents/scene-background/upload.d.ts +30 -0
  24. package/dist/commands/agents/scene-background/upload.js +97 -0
  25. package/dist/commands/agents/show.js +13 -0
  26. package/dist/commands/agents/tools/calls.d.ts +23 -5
  27. package/dist/commands/agents/tools/calls.js +81 -18
  28. package/dist/commands/agents/update.js +15 -5
  29. package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
  30. package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
  31. package/dist/commands/convos/messages.d.ts +13 -0
  32. package/dist/commands/convos/messages.js +40 -16
  33. package/dist/commands/daemon/index.js +8 -1
  34. package/dist/commands/daemon/logs.d.ts +26 -0
  35. package/dist/commands/daemon/logs.js +56 -0
  36. package/dist/commands/daemon/uninstall.js +1 -1
  37. package/dist/commands/followups/act.js +3 -2
  38. package/dist/commands/followups/cancel.d.ts +6 -3
  39. package/dist/commands/followups/cancel.js +16 -6
  40. package/dist/commands/followups/observability.d.ts +23 -0
  41. package/dist/commands/followups/observability.js +114 -0
  42. package/dist/commands/followups/reschedule.js +1 -1
  43. package/dist/commands/followups/resolve.js +3 -3
  44. package/dist/commands/followups/show.js +2 -1
  45. package/dist/commands/followups/watch.js +9 -5
  46. package/dist/commands/harnesses/forget.js +28 -0
  47. package/dist/commands/harnesses/install.d.ts +41 -0
  48. package/dist/commands/harnesses/install.js +165 -0
  49. package/dist/commands/harnesses/installers.d.ts +45 -0
  50. package/dist/commands/harnesses/installers.js +155 -0
  51. package/dist/commands/harnesses/list.d.ts +82 -1
  52. package/dist/commands/harnesses/list.js +104 -12
  53. package/dist/commands/harnesses/models.js +10 -1
  54. package/dist/commands/harnesses/show.d.ts +15 -0
  55. package/dist/commands/harnesses/show.js +44 -3
  56. package/dist/commands/harnesses/update.js +1 -1
  57. package/dist/commands/harnesses/usage.d.ts +25 -0
  58. package/dist/commands/harnesses/usage.js +24 -2
  59. package/dist/commands/instructions/status.js +13 -1
  60. package/dist/commands/machines/dedicated/catalog.js +4 -15
  61. package/dist/commands/machines/dedicated/create.d.ts +8 -0
  62. package/dist/commands/machines/dedicated/create.js +58 -1
  63. package/dist/commands/machines/dedicated/destroy.js +18 -4
  64. package/dist/commands/machines/dedicated/list.js +9 -6
  65. package/dist/commands/machines/hosted/list.js +12 -0
  66. package/dist/commands/machines/list.js +11 -0
  67. package/dist/commands/machines/show.d.ts +1 -0
  68. package/dist/commands/machines/show.js +40 -11
  69. package/dist/commands/profiles/list.d.ts +23 -0
  70. package/dist/commands/profiles/list.js +96 -0
  71. package/dist/commands/search/skills.d.ts +11 -1
  72. package/dist/commands/search/skills.js +26 -8
  73. package/dist/commands/skills/details.d.ts +2 -0
  74. package/dist/commands/skills/details.js +24 -1
  75. package/dist/commands/skills/import-as-actions.d.ts +32 -0
  76. package/dist/commands/skills/import-as-actions.js +175 -0
  77. package/dist/commands/skills/install.d.ts +2 -0
  78. package/dist/commands/skills/install.js +24 -1
  79. package/dist/commands/skills/invocable.js +6 -2
  80. package/dist/commands/skills/local.d.ts +40 -0
  81. package/dist/commands/skills/local.js +160 -0
  82. package/dist/commands/skills/search.d.ts +11 -1
  83. package/dist/commands/skills/search.js +29 -8
  84. package/dist/commands/spaces/actions/add.js +23 -5
  85. package/dist/commands/spaces/actions/available.js +3 -0
  86. package/dist/commands/spaces/actions/list.d.ts +5 -0
  87. package/dist/commands/spaces/actions/list.js +49 -3
  88. package/dist/commands/spaces/actions/run.d.ts +28 -0
  89. package/dist/commands/spaces/actions/run.js +146 -0
  90. package/dist/commands/spaces/actions/update.js +27 -5
  91. package/dist/commands/spaces/label-summaries.d.ts +27 -0
  92. package/dist/commands/spaces/label-summaries.js +64 -0
  93. package/dist/commands/spaces/task-rollups.d.ts +5 -0
  94. package/dist/commands/spaces/task-rollups.js +15 -4
  95. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +63 -3
  96. package/dist/commands/tasks/actions/attach.js +32 -7
  97. package/dist/commands/tasks/complete.js +13 -0
  98. package/dist/commands/tasks/create.js +1 -1
  99. package/dist/commands/tasks/events/append.js +29 -6
  100. package/dist/commands/tasks/expectations/assess.d.ts +1 -0
  101. package/dist/commands/tasks/expectations/assess.js +43 -5
  102. package/dist/commands/tasks/expectations.js +77 -16
  103. package/dist/commands/tasks/follow-up.js +2 -2
  104. package/dist/commands/tasks/lifecycle/definitions.js +21 -2
  105. package/dist/commands/tasks/lifecycle/detach.d.ts +7 -0
  106. package/dist/commands/tasks/lifecycle/detach.js +43 -3
  107. package/dist/commands/tasks/lifecycle/migrate.d.ts +2 -0
  108. package/dist/commands/tasks/lifecycle/migrate.js +42 -3
  109. package/dist/commands/tasks/lifecycle/reopen.d.ts +2 -0
  110. package/dist/commands/tasks/lifecycle/reopen.js +40 -3
  111. package/dist/commands/tasks/lifecycle/transition.js +33 -2
  112. package/dist/commands/tasks/list.js +16 -9
  113. package/dist/commands/tasks/move.js +11 -13
  114. package/dist/commands/tasks/review-queue.d.ts +15 -0
  115. package/dist/commands/tasks/review-queue.js +180 -31
  116. package/dist/commands/tasks/show.d.ts +11 -0
  117. package/dist/commands/tasks/show.js +105 -12
  118. package/dist/commands/tasks/trust.js +7 -2
  119. package/dist/commands/tasks/update.js +46 -10
  120. package/dist/commands/tasks/updates/add.js +1 -1
  121. package/dist/commands/token/list.d.ts +22 -0
  122. package/dist/commands/token/list.js +68 -0
  123. package/dist/commands/token/revoke.d.ts +23 -0
  124. package/dist/commands/token/revoke.js +53 -0
  125. package/dist/lib/agent-actions.d.ts +57 -2
  126. package/dist/lib/agent-actions.js +113 -21
  127. package/dist/lib/agent-pin-mute.d.ts +10 -5
  128. package/dist/lib/agent-pin-mute.js +17 -4
  129. package/dist/lib/agent-visual-refs.d.ts +27 -0
  130. package/dist/lib/agent-visual-refs.js +127 -2
  131. package/dist/lib/agentic-stream.d.ts +6 -0
  132. package/dist/lib/agentic-stream.js +34 -3
  133. package/dist/lib/assignee-resolver.d.ts +1 -1
  134. package/dist/lib/assignee-resolver.js +8 -0
  135. package/dist/lib/bulk-task-targets.d.ts +12 -0
  136. package/dist/lib/bulk-task-targets.js +34 -1
  137. package/dist/lib/cli-installers.d.ts +130 -0
  138. package/dist/lib/cli-installers.js +145 -0
  139. package/dist/lib/command-miss.js +55 -7
  140. package/dist/lib/dedicated-lease-command.d.ts +13 -0
  141. package/dist/lib/dedicated-lease-command.js +22 -1
  142. package/dist/lib/dedicated-machines.d.ts +95 -1
  143. package/dist/lib/dedicated-machines.js +236 -2
  144. package/dist/lib/failed-lookup.d.ts +3 -0
  145. package/dist/lib/failed-lookup.js +40 -0
  146. package/dist/lib/first-party-harness-agent.js +9 -2
  147. package/dist/lib/first-party-harness-doctor.d.ts +15 -0
  148. package/dist/lib/first-party-harness-doctor.js +126 -27
  149. package/dist/lib/followups.d.ts +14 -6
  150. package/dist/lib/followups.js +31 -6
  151. package/dist/lib/harnesses.d.ts +8 -0
  152. package/dist/lib/instruction-input.d.ts +19 -1
  153. package/dist/lib/instruction-input.js +52 -1
  154. package/dist/lib/label-scope.d.ts +15 -0
  155. package/dist/lib/label-scope.js +25 -5
  156. package/dist/lib/local-skills.d.ts +98 -0
  157. package/dist/lib/local-skills.js +195 -0
  158. package/dist/lib/machines.d.ts +10 -10
  159. package/dist/lib/machines.js +23 -1
  160. package/dist/lib/search-query.d.ts +19 -0
  161. package/dist/lib/search-query.js +35 -0
  162. package/dist/lib/space-custom-actions.d.ts +72 -0
  163. package/dist/lib/space-custom-actions.js +117 -0
  164. package/dist/lib/space-resolver.d.ts +1 -21
  165. package/dist/lib/space-resolver.js +19 -10
  166. package/dist/lib/task-closure.d.ts +3 -2
  167. package/dist/lib/task-closure.js +18 -20
  168. package/dist/lib/task-lifecycle-output.js +117 -9
  169. package/dist/lib/task-ref-resolver.d.ts +1 -17
  170. package/dist/lib/task-ref-resolver.js +18 -8
  171. package/dist/lib/tasks.js +9 -3
  172. package/dist/node_modules/@skrr-ai/data-provider/index.js +5076 -4947
  173. package/oclif.manifest.json +26635 -25649
  174. package/package.json +9 -3
@@ -0,0 +1,130 @@
1
+ /** Matches `ManagedCliAuthenticationState` in `daemon/src/cli-installers.ts`. */
2
+ export interface ManagedCliAuthentication {
3
+ status: 'ready' | 'interactive-required' | 'headless-key-required';
4
+ source: 'environment' | 'browser' | 'none';
5
+ message: string;
6
+ }
7
+ /**
8
+ * Matches `ManagedCliProviderState` in `daemon/src/cli-installers.ts`.
9
+ *
10
+ * Every field optional on purpose: this crosses a version boundary. The daemon
11
+ * on the other end may be older than this CLI (they ship separately, and this
12
+ * machine routinely runs a daemon bundled inside the desktop app), so a missing
13
+ * field has to read as "this daemon does not say", never as a falsy answer.
14
+ */
15
+ export interface ManagedCliProviderState {
16
+ provider?: string;
17
+ displayName?: string;
18
+ binaryName?: string;
19
+ installed?: boolean;
20
+ available?: boolean;
21
+ version?: string;
22
+ minVersion?: string;
23
+ meetsMinimum?: boolean;
24
+ resolvedPath?: string | null;
25
+ source?: string;
26
+ installable?: boolean;
27
+ installDisabledReason?: string | null;
28
+ platformSupported?: boolean;
29
+ authentication?: ManagedCliAuthentication | null;
30
+ job?: {
31
+ id?: string;
32
+ status?: string;
33
+ } | null;
34
+ }
35
+ export interface CliInstallersResponse {
36
+ daemonId?: string;
37
+ enabled?: boolean;
38
+ platform?: string;
39
+ /** Absent from a daemon older than the field — render as "not reported". */
40
+ daemonVersion?: string;
41
+ providers?: ManagedCliProviderState[];
42
+ }
43
+ /**
44
+ * Providers the platform knows about that this daemon did not offer.
45
+ *
46
+ * The inventory is whatever the CONNECTED daemon supports, and an older daemon
47
+ * legitimately supports fewer: `devin` arrived in daemon 0.8.43, so a machine
48
+ * running 0.8.35 answers with six providers instead of seven. Observed here —
49
+ * the desktop app restarted the daemon from its bundled 0.8.35 copy and Devin
50
+ * disappeared from the install list between two page loads, with nothing on
51
+ * either screen to explain it. A provider that silently stops existing reads as
52
+ * dropped support.
53
+ *
54
+ * This works WITHOUT a daemon update, which is the point: `daemonVersion` only
55
+ * helps once apps ship a daemon that reports it, whereas the shared vocabulary
56
+ * is already on this side of the wire. Naming the gap is what turns "Devin is
57
+ * gone" into "this daemon is older than Devin".
58
+ */
59
+ export declare function providersNotOffered(offered: readonly ManagedCliProviderState[] | undefined, known: readonly string[]): string[];
60
+ export declare function listCliInstallers(daemonId: string, credential?: unknown): Promise<CliInstallersResponse>;
61
+ /**
62
+ * The one-word readiness verdict for a provider, and whether it is good news.
63
+ *
64
+ * Four states, and the fourth is the one that matters most to get right:
65
+ * `unknown`. A provider whose recipe declares no credential resolver reports
66
+ * `authentication: null`, and that is NOT `ready` — this daemon simply has no
67
+ * way to read that vendor's credential state. Collapsing the two is the exact
68
+ * failure `d878b79581` was written to stop, so the word is different and the
69
+ * detail says why.
70
+ */
71
+ export declare function readinessOf(provider: ManagedCliProviderState): {
72
+ label: string;
73
+ detail: string;
74
+ ok: boolean;
75
+ };
76
+ /** A job the daemon runs to install one managed provider. */
77
+ export interface ManagedCliInstallJob {
78
+ id: string;
79
+ status: 'queued' | 'running' | 'succeeded' | 'failed';
80
+ error?: string;
81
+ log?: string[];
82
+ }
83
+ export interface StartInstallResponse {
84
+ started?: boolean;
85
+ provider?: ManagedCliProviderState;
86
+ job?: {
87
+ id?: string;
88
+ status?: string;
89
+ };
90
+ }
91
+ export declare function startManagedCliInstall(daemonId: string, provider: string, credential?: unknown): Promise<StartInstallResponse>;
92
+ export declare function readManagedCliInstallJob(daemonId: string, jobId: string, credential?: unknown): Promise<{
93
+ job?: ManagedCliInstallJob;
94
+ }>;
95
+ /** A 100 MB download over a hotel connection is the case this has to survive. */
96
+ export declare const INSTALL_MAX_WAIT_MS: number;
97
+ export declare const INSTALL_POLL_INTERVAL_MS = 2000;
98
+ export type FollowJobOutcome = {
99
+ kind: 'settled';
100
+ job: ManagedCliInstallJob;
101
+ }
102
+ /** The daemon is still working. NOT a failure — see below. */
103
+ | {
104
+ kind: 'timeout';
105
+ jobId: string;
106
+ } | {
107
+ kind: 'missing';
108
+ jobId: string;
109
+ };
110
+ /**
111
+ * Poll an install job to completion, printing each new log line once.
112
+ *
113
+ * The daemon writes the only account of a download the user is waiting on
114
+ * ("Fetching the signed release manifest", "Verified and installed …"), so the
115
+ * loop relays it; printing only the unseen tail keeps a poll from repeating the
116
+ * whole log each time.
117
+ *
118
+ * A timeout is returned, never thrown as a failure. Saying "install failed"
119
+ * because this process ran out of patience is a lie that sends someone to debug
120
+ * a download that is going fine — so the caller decides the wording, and both
121
+ * callers say the daemon is still working.
122
+ */
123
+ export declare function followManagedCliInstallJob(input: {
124
+ daemonId: string;
125
+ jobId: string;
126
+ credential?: unknown;
127
+ onLogLine?: (line: string) => void;
128
+ maxWaitMs?: number;
129
+ pollIntervalMs?: number;
130
+ }): Promise<FollowJobOutcome>;
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.INSTALL_POLL_INTERVAL_MS = exports.INSTALL_MAX_WAIT_MS = void 0;
4
+ exports.providersNotOffered = providersNotOffered;
5
+ exports.listCliInstallers = listCliInstallers;
6
+ exports.readinessOf = readinessOf;
7
+ exports.startManagedCliInstall = startManagedCliInstall;
8
+ exports.readManagedCliInstallJob = readManagedCliInstallJob;
9
+ exports.followManagedCliInstallJob = followManagedCliInstallJob;
10
+ /**
11
+ * The daemon's managed-CLI installer inventory, as the CLI reads it.
12
+ *
13
+ * ## Why this file exists
14
+ *
15
+ * `GET /api/daemons/:id/cli-installers` is where `d878b79581` put the answer to
16
+ * "is this harness actually usable" — `installed`, and for providers whose
17
+ * credential state the daemon can read, one of `ready` /
18
+ * `interactive-required` / `headless-key-required`. The whole point of that
19
+ * change was to stop surfaces answering "is the binary there" instead.
20
+ *
21
+ * The CLI called the route from exactly one place (`commands/code/install.ts`),
22
+ * for one provider, to read one field, and showed the user none of it. So from
23
+ * the CLI there was no way to learn that a harness listed as `online` had no
24
+ * binary at all — which is the state this account's `skrr-code` row was in.
25
+ *
26
+ * The shape lives here rather than beside a command because two commands read
27
+ * it now. Root `CLAUDE.md`: a second declaration is how a surface silently
28
+ * drifts from the server that produced it.
29
+ */
30
+ const data_provider_1 = require("@skrr-ai/data-provider");
31
+ const api_fetch_1 = require("./api-fetch");
32
+ /**
33
+ * Providers the platform knows about that this daemon did not offer.
34
+ *
35
+ * The inventory is whatever the CONNECTED daemon supports, and an older daemon
36
+ * legitimately supports fewer: `devin` arrived in daemon 0.8.43, so a machine
37
+ * running 0.8.35 answers with six providers instead of seven. Observed here —
38
+ * the desktop app restarted the daemon from its bundled 0.8.35 copy and Devin
39
+ * disappeared from the install list between two page loads, with nothing on
40
+ * either screen to explain it. A provider that silently stops existing reads as
41
+ * dropped support.
42
+ *
43
+ * This works WITHOUT a daemon update, which is the point: `daemonVersion` only
44
+ * helps once apps ship a daemon that reports it, whereas the shared vocabulary
45
+ * is already on this side of the wire. Naming the gap is what turns "Devin is
46
+ * gone" into "this daemon is older than Devin".
47
+ */
48
+ function providersNotOffered(offered, known) {
49
+ // Canonicalize BOTH sides. An older daemon answers under the spelling it was
50
+ // built with — the 0.8.35 build reports the first-party harness as
51
+ // `sky-code`, so a raw comparison against `skrr-code` reported it missing on
52
+ // a machine where it is installed and working. Caught by running this against
53
+ // the very daemon the finding was about: the first version of this helper
54
+ // said "Not offered: devin, skrr-code" when only `devin` was true.
55
+ const canonical = (value) => typeof value === 'string' ? String((0, data_provider_1.canonicalHarnessProvider)(value) ?? value) : '';
56
+ const seen = new Set((offered ?? []).map((provider) => canonical(provider.provider)).filter(Boolean));
57
+ return known.filter((provider) => !seen.has(canonical(provider)));
58
+ }
59
+ async function listCliInstallers(daemonId, credential) {
60
+ return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers`, { credential: credential });
61
+ }
62
+ /**
63
+ * The one-word readiness verdict for a provider, and whether it is good news.
64
+ *
65
+ * Four states, and the fourth is the one that matters most to get right:
66
+ * `unknown`. A provider whose recipe declares no credential resolver reports
67
+ * `authentication: null`, and that is NOT `ready` — this daemon simply has no
68
+ * way to read that vendor's credential state. Collapsing the two is the exact
69
+ * failure `d878b79581` was written to stop, so the word is different and the
70
+ * detail says why.
71
+ */
72
+ function readinessOf(provider) {
73
+ if (provider.platformSupported === false) {
74
+ return { label: 'unsupported', detail: 'Not available on this platform.', ok: false };
75
+ }
76
+ if (!provider.installed) {
77
+ // `available` without `meetsMinimum` is a REAL third case and reads very
78
+ // differently from "not installed": the binary is right there and too old.
79
+ if (provider.available && provider.meetsMinimum === false) {
80
+ return {
81
+ label: 'too-old',
82
+ detail: provider.minVersion
83
+ ? `Found ${provider.version || 'an older build'}; needs ${provider.minVersion} or newer.`
84
+ : 'The installed build is older than the minimum this harness needs.',
85
+ ok: false,
86
+ };
87
+ }
88
+ return {
89
+ label: 'not-installed',
90
+ detail: provider.installDisabledReason || 'No binary for this provider on this machine.',
91
+ ok: false,
92
+ };
93
+ }
94
+ const auth = provider.authentication;
95
+ if (!auth) {
96
+ return {
97
+ label: 'unknown',
98
+ detail: 'Installed. This daemon cannot read this provider’s credential state.',
99
+ ok: false,
100
+ };
101
+ }
102
+ return { label: auth.status, detail: auth.message, ok: auth.status === 'ready' };
103
+ }
104
+ async function startManagedCliInstall(daemonId, provider, credential) {
105
+ return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/${encodeURIComponent(provider)}/install`, { method: 'POST', body: {}, credential: credential });
106
+ }
107
+ async function readManagedCliInstallJob(daemonId, jobId, credential) {
108
+ return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/jobs/${encodeURIComponent(jobId)}`, { credential: credential });
109
+ }
110
+ /** A 100 MB download over a hotel connection is the case this has to survive. */
111
+ exports.INSTALL_MAX_WAIT_MS = 10 * 60_000;
112
+ exports.INSTALL_POLL_INTERVAL_MS = 2_000;
113
+ /**
114
+ * Poll an install job to completion, printing each new log line once.
115
+ *
116
+ * The daemon writes the only account of a download the user is waiting on
117
+ * ("Fetching the signed release manifest", "Verified and installed …"), so the
118
+ * loop relays it; printing only the unseen tail keeps a poll from repeating the
119
+ * whole log each time.
120
+ *
121
+ * A timeout is returned, never thrown as a failure. Saying "install failed"
122
+ * because this process ran out of patience is a lie that sends someone to debug
123
+ * a download that is going fine — so the caller decides the wording, and both
124
+ * callers say the daemon is still working.
125
+ */
126
+ async function followManagedCliInstallJob(input) {
127
+ const deadline = Date.now() + (input.maxWaitMs ?? exports.INSTALL_MAX_WAIT_MS);
128
+ let printed = 0;
129
+ for (;;) {
130
+ const res = await readManagedCliInstallJob(input.daemonId, input.jobId, input.credential);
131
+ const job = res.job;
132
+ if (!job)
133
+ return { kind: 'missing', jobId: input.jobId };
134
+ if (input.onLogLine) {
135
+ for (const line of (job.log ?? []).slice(printed))
136
+ input.onLogLine(line);
137
+ printed = (job.log ?? []).length;
138
+ }
139
+ if (job.status === 'succeeded' || job.status === 'failed')
140
+ return { kind: 'settled', job };
141
+ if (Date.now() > deadline)
142
+ return { kind: 'timeout', jobId: input.jobId };
143
+ await new Promise((resolve) => setTimeout(resolve, input.pollIntervalMs ?? exports.INSTALL_POLL_INTERVAL_MS));
144
+ }
145
+ }
@@ -104,17 +104,65 @@ function describeCommandMiss(config, id) {
104
104
  // command id, so a whole-string comparison finds nothing and the reader is
105
105
  // told only that they are wrong — when the useful answer is "that topic
106
106
  // exists and here is what it does have".
107
+ const typedId = parts.join(':');
108
+ const spokenId = `${config.bin} ${parts.join(' ')}`;
109
+ // The id IS registered and the lookup still could not produce it. That is not
110
+ // a typo, so the "is not a command" wording below would be false — and worse,
111
+ // the suggestion search then finds the id itself and hands the reader their
112
+ // own input back:
113
+ //
114
+ // $ skrr agents list --help
115
+ // Error: `skrr agents list` is not a command.
116
+ // Did you mean: `skrr agents list`?
117
+ //
118
+ // Observed against a real build, for ten commands in a row, and it is actively
119
+ // misleading rather than merely unhelpful: a dogfood run read it as proof that
120
+ // a shipped release had not shipped its commands. The two facts come from two
121
+ // sources — `findCommand` loads, `commandIDs` lists — and when they disagree
122
+ // the honest thing to report is the disagreement, which has a known cause and
123
+ // a next step.
124
+ if (config.commandIDs.includes(typedId) || config.topicNames.includes(typedId)) {
125
+ return (`\`${spokenId}\` is registered but could not be loaded from this build.\n` +
126
+ `Its compiled output is missing or stale — run \`npm --prefix cli run build\` in a ` +
127
+ `development checkout, or reinstall the CLI.`);
128
+ }
107
129
  const siblings = config.commandIDs.filter((c) => c.startsWith(`${parts[0]}:`));
108
- const suggestions = siblings.length
130
+ const suggestions = (siblings.length
109
131
  ? nearestCommands(parts.slice(0, 2).join(':'), siblings)
110
- : nearestCommands(parts.join(':'), [...config.commandIDs, ...config.topicNames]);
111
- return (`\`${config.bin} ${parts.join(' ')}\` is not a command.` +
112
- (suggestions.length
113
- ? `\nDid you mean: ${suggestions
132
+ : nearestCommands(typedId, [...config.commandIDs, ...config.topicNames]))
133
+ // A suggestion identical to what was typed is an echo, not a correction.
134
+ // It can outrank real alternatives too, since nothing is closer to the id
135
+ // than the id, so it costs a suggestion slot as well as the reader's trust.
136
+ .filter((candidate) => candidate !== typedId);
137
+ if (suggestions.length) {
138
+ return (`\`${spokenId}\` is not a command.` +
139
+ `\nDid you mean: ${suggestions
114
140
  .map((s) => `\`${config.bin} ${s.split(':').join(' ')}\``)
115
- .join(', ')}?`
116
- : `\nRun \`${config.bin} --help\` to see the topics.`));
141
+ .join(', ')}?`);
142
+ }
143
+ // The topic the reader typed is real, and nothing in it is spelled like what
144
+ // they asked for — a different word for it, not a typo. "See the topics"
145
+ // sent them back to the top level to rediscover the topic they had already
146
+ // found; what they need is what THIS topic has (OSK-9723: `skrr dm send`,
147
+ // where `dm` has no `send` at all).
148
+ if (siblings.length) {
149
+ const topic = parts[0];
150
+ const shown = siblings
151
+ .map((id) => id
152
+ .slice(topic.length + 1)
153
+ .split(':')
154
+ .join(' '))
155
+ .sort()
156
+ .slice(0, MAX_TOPIC_LISTING);
157
+ const more = siblings.length > shown.length ? ', …' : '';
158
+ return (`\`${spokenId}\` is not a command.\n` +
159
+ `\`${config.bin} ${topic}\` has: ${shown.map((c) => `\`${c}\``).join(', ')}${more}.\n` +
160
+ `See \`${config.bin} ${topic} --help\`.`);
161
+ }
162
+ return `\`${spokenId}\` is not a command.\nRun \`${config.bin} --help\` to see the topics.`;
117
163
  }
164
+ /** Enough to scan at a glance; the topic's own `--help` has the rest. */
165
+ const MAX_TOPIC_LISTING = 10;
118
166
  /** Adapt an oclif `Config` to the lookup shape above. */
119
167
  function lookupFromConfig(config) {
120
168
  return {
@@ -65,3 +65,16 @@ export declare abstract class DedicatedLeaseCommand extends BaseCommand {
65
65
  }
66
66
  /** The same transient verdict BaseCommand gives an ordinary API call. */
67
67
  export declare function dedicatedRuntimeApiErrorRetryable(err: unknown): boolean;
68
+ /**
69
+ * Accept the id our own output printed.
70
+ *
71
+ * `skrr machines list --all` renders a Dedicated Runtime's MACHINE identity,
72
+ * which the server builds as `lease:${leaseId}` (`machineIdentityForHarness`).
73
+ * The lease routes want the bare `rtl_…`. Pasting the one you were just shown
74
+ * therefore 404'd, and the refusal blamed workspace scope — sending you to try
75
+ * `--workspace` when the fix was deleting six characters.
76
+ *
77
+ * Only the prefixes this product itself prints are stripped, so a genuinely
78
+ * unknown id still fails as an unknown id rather than being silently reshaped.
79
+ */
80
+ export declare function stripMachineIdentityPrefix(ref: string): string;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DedicatedLeaseCommand = exports.DEDICATED_LEASE_ARG_DESCRIPTION = exports.DEDICATED_SELF_LEASE = exports.dedicatedWaitFlags = void 0;
4
4
  exports.dedicatedRuntimeApiErrorRetryable = dedicatedRuntimeApiErrorRetryable;
5
+ exports.stripMachineIdentityPrefix = stripMachineIdentityPrefix;
5
6
  const core_1 = require("@oclif/core");
6
7
  const base_command_1 = require("../base-command");
7
8
  const api_fetch_1 = require("./api-fetch");
@@ -51,7 +52,7 @@ class DedicatedLeaseCommand extends base_command_1.BaseCommand {
51
52
  */
52
53
  async resolveLeaseArg(lease) {
53
54
  if (lease !== exports.DEDICATED_SELF_LEASE)
54
- return lease;
55
+ return stripMachineIdentityPrefix(lease);
55
56
  const descriptor = (0, daemonBroker_1.readBootstrap)(daemonBroker_1.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR);
56
57
  if (!descriptor?.daemonId) {
57
58
  this.error('`self` means the Dedicated Runtime this command runs on, and this is not one (or its daemon is not running). Pass a lease id.', { exit: 2 });
@@ -226,3 +227,23 @@ function dedicatedRuntimeApiErrorRetryable(err) {
226
227
  }
227
228
  return (0, base_command_1.isRetryableFailure)(err.status, body);
228
229
  }
230
+ /**
231
+ * Accept the id our own output printed.
232
+ *
233
+ * `skrr machines list --all` renders a Dedicated Runtime's MACHINE identity,
234
+ * which the server builds as `lease:${leaseId}` (`machineIdentityForHarness`).
235
+ * The lease routes want the bare `rtl_…`. Pasting the one you were just shown
236
+ * therefore 404'd, and the refusal blamed workspace scope — sending you to try
237
+ * `--workspace` when the fix was deleting six characters.
238
+ *
239
+ * Only the prefixes this product itself prints are stripped, so a genuinely
240
+ * unknown id still fails as an unknown id rather than being silently reshaped.
241
+ */
242
+ function stripMachineIdentityPrefix(ref) {
243
+ const trimmed = ref.trim();
244
+ for (const prefix of ['lease:', 'dedicated-runtime:']) {
245
+ if (trimmed.startsWith(prefix))
246
+ return trimmed.slice(prefix.length);
247
+ }
248
+ return trimmed;
249
+ }
@@ -389,10 +389,57 @@ export type DedicatedCopyPlan = {
389
389
  export declare function parseDedicatedCopyEndpoints(source: string, destination: string): DedicatedCopyPlan;
390
390
  export declare function dedicatedLeaseId(lease: DedicatedLease | HarnessLease | undefined | null): string;
391
391
  export declare function dedicatedLeaseState(lease: DedicatedLease | HarnessLease | undefined | null): string;
392
+ /**
393
+ * What `destroy` should say when it keeps the workspace — or null when it does
394
+ * not.
395
+ *
396
+ * A destroy under retention class standard/extended archives the lease and keeps
397
+ * its workspace volume, which keeps billing storage until it is purged. Nothing
398
+ * said so at the moment it mattered: `destroy` printed the new state and exited,
399
+ * `list` showed `archived` in the same column as `terminated`, and the one way to
400
+ * find out was the bill (OSK-9593).
401
+ */
402
+ export declare function dedicatedDestroyRetentionNote(priorLease: DedicatedLease | undefined | null, opts: {
403
+ purge: boolean;
404
+ retain: boolean;
405
+ bin: string;
406
+ }): string | null;
407
+ /**
408
+ * Month-to-date Dedicated Runtime spend for the scope a `list` shows.
409
+ *
410
+ * `billing.currentMonthSpendCents` is the SCOPE's spend for the product — the
411
+ * server aggregates by owner scope, not by lease — so every row carried the same
412
+ * figure, and a column headed MONTH/CAP on per-machine rows read as that
413
+ * machine's cost when it was every machine's (OSK-9576). It is said once instead.
414
+ */
415
+ export declare function dedicatedScopeMonthSpendCents(leases: Array<DedicatedLease | undefined | null>): number | null;
416
+ /**
417
+ * A lease's own cap, marked when the scope's spend has reached it. The mark is
418
+ * left off a terminal lease: its cap can no longer stop anything, and
419
+ * "$11.06/$10.00 OVER" on a machine terminated days ago read as that machine
420
+ * having overrun.
421
+ */
422
+ export declare function dedicatedLeaseCapCell(lease: DedicatedLease | undefined | null): string;
423
+ /**
424
+ * A lease's STATUS cell. `archived` and `terminated` are both "gone" to read, and
425
+ * only one of them is still charging — so a lease billing retained storage says
426
+ * so (OSK-9593).
427
+ */
428
+ export declare function dedicatedLeaseStatusCell(lease: DedicatedLease | undefined | null): string;
392
429
  export declare function dedicatedLeaseDisplayName(lease: DedicatedLease | HarnessLease | undefined | null): string;
393
430
  /** `$12.34` — cents are what the API speaks; dollars are what a person reads. */
394
431
  export declare function formatUsdCents(cents: number | null | undefined): string;
395
- /** `this month $1.23 of $500.00 cap`, or why that sentence cannot be written. */
432
+ /**
433
+ * `$1.23 this month for machines of this type · $500.00 cap for this machine`,
434
+ * or why that sentence cannot be written.
435
+ *
436
+ * The two figures are different scopes and the sentence has to say so. The spend
437
+ * is a shared pot the server computes for (owner, team, machineProduct), so every
438
+ * lease of the same product reports the same number — a machine created minutes
439
+ * ago with no active seconds still reports the month's total. The cap is this
440
+ * lease's own. Printed as "$1.23 this month of $500.00 cap" the only available
441
+ * reading was that this machine had spent it.
442
+ */
396
443
  export declare function dedicatedSpendSummary(lease: DedicatedLease): string;
397
444
  /**
398
445
  * The human rendering of `machines dedicated show`, as lines.
@@ -461,12 +508,15 @@ interface CatalogSize {
461
508
  vcpus?: number;
462
509
  memoryGiB?: number;
463
510
  accelerator?: string;
511
+ rateCentsPerHour?: number | null;
464
512
  }
465
513
  interface CatalogPriceLine {
466
514
  label?: string;
467
515
  display?: string;
468
516
  amountCents?: number | null;
469
517
  cadence?: string;
518
+ /** `charge` is billed, `hold` is reserved at start, `limit` is a cap. Absent means `charge`. */
519
+ kind?: 'charge' | 'hold' | 'limit';
470
520
  }
471
521
  export interface DedicatedCatalog {
472
522
  availability: string;
@@ -480,9 +530,15 @@ export interface DedicatedCatalog {
480
530
  };
481
531
  retentionClasses: string[];
482
532
  backupProfiles: string[];
533
+ /** Server-declared: what create applies when a flag is omitted. */
534
+ defaultSizePreset?: string;
535
+ defaultRetentionClass?: string;
536
+ defaultBackupProfile?: string;
483
537
  defaultMonthlySpendingLimitCents?: number | null;
484
538
  maxUserMonthlySpendingLimitCents?: number | null;
485
539
  priceLines: CatalogPriceLine[];
540
+ /** The price book the quoted prices come from — what a later pricing question cites. */
541
+ priceBookVersion?: string;
486
542
  headline?: string;
487
543
  }
488
544
  /**
@@ -493,6 +549,44 @@ export interface DedicatedCatalog {
493
549
  * gated by server flags — so this is where a caller learns which ones exist.
494
550
  */
495
551
  export declare function dedicatedCatalogFromProducts(payload: unknown): DedicatedCatalog | null;
552
+ /**
553
+ * The catalog's size rows: spec, the size's own hourly price, and which one
554
+ * `create` uses by default. The rows used to carry spec alone, sorted by name,
555
+ * so the $1.40/hr tier came first with nothing saying what it cost or that
556
+ * $0.65/hr standard was the default; a reader had to join two blocks by name.
557
+ */
558
+ export declare function dedicatedCatalogSizeLines(catalog: DedicatedCatalog): string[];
559
+ export interface DedicatedCreateChoices {
560
+ name: string;
561
+ sizePreset: string;
562
+ storageGb?: number;
563
+ retentionClass?: string;
564
+ spendingLimitCents?: number;
565
+ }
566
+ /**
567
+ * What a create is about to buy, in the words and numbers the catalog uses.
568
+ *
569
+ * `create` provisioned a billable machine the moment it ran — no confirmation,
570
+ * no dry run, and every omitted flag silently took a paid default — while
571
+ * `destroy` asked first. A loop probing size validation bought two real
572
+ * machines, one of them $1.40/hr (OSK-9592). This is what `create` now shows
573
+ * before it asks, and what `--dry-run` prints instead of creating.
574
+ *
575
+ * Every default comes from the catalog, never from this file: a CLI that
576
+ * restates a default it does not own is how `--storage-gb` once billed twice the
577
+ * product's disk. A value the catalog does not declare is shown as "server
578
+ * default" rather than guessed.
579
+ */
580
+ export declare function dedicatedCreateSummary(catalog: DedicatedCatalog | null, choices: DedicatedCreateChoices): string[];
581
+ /**
582
+ * The catalog's money lines, grouped by what each amount IS.
583
+ *
584
+ * A start hold and a monthly cap used to print under "Prices" beside the hourly
585
+ * rates, so starting a machine read as a 33c charge and "$500 default monthly
586
+ * cap" read as a bill (OSK-9536). A line with no `kind` comes from a server that
587
+ * predates it and is shown the way every line used to be: as a charge.
588
+ */
589
+ export declare function dedicatedCatalogPriceLineOutput(priceLines: CatalogPriceLine[], priceBookVersion?: string): string[];
496
590
  /**
497
591
  * The product refusal code the server sent (`DEDICATED_RUNTIME_NOT_ASSIGNABLE`,
498
592
  * …), for `this.error(message, { code })`. Without it a `--json` caller got the