@skrr-ai/cli 0.1.37 → 0.1.39

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 (177) hide show
  1. package/bin/dev-fallback.js +175 -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 +63 -2
  63. package/dist/commands/machines/dedicated/destroy.js +19 -5
  64. package/dist/commands/machines/dedicated/exec.js +3 -1
  65. package/dist/commands/machines/dedicated/list.js +9 -6
  66. package/dist/commands/machines/dedicated/price-book.d.ts +17 -0
  67. package/dist/commands/machines/dedicated/price-book.js +35 -0
  68. package/dist/commands/machines/hosted/list.js +12 -0
  69. package/dist/commands/machines/list.js +11 -0
  70. package/dist/commands/machines/show.d.ts +1 -0
  71. package/dist/commands/machines/show.js +40 -11
  72. package/dist/commands/profiles/list.d.ts +23 -0
  73. package/dist/commands/profiles/list.js +96 -0
  74. package/dist/commands/search/skills.d.ts +11 -1
  75. package/dist/commands/search/skills.js +26 -8
  76. package/dist/commands/skills/details.d.ts +2 -0
  77. package/dist/commands/skills/details.js +24 -1
  78. package/dist/commands/skills/import-as-actions.d.ts +32 -0
  79. package/dist/commands/skills/import-as-actions.js +175 -0
  80. package/dist/commands/skills/install.d.ts +2 -0
  81. package/dist/commands/skills/install.js +24 -1
  82. package/dist/commands/skills/invocable.js +6 -2
  83. package/dist/commands/skills/local.d.ts +40 -0
  84. package/dist/commands/skills/local.js +160 -0
  85. package/dist/commands/skills/search.d.ts +11 -1
  86. package/dist/commands/skills/search.js +29 -8
  87. package/dist/commands/spaces/actions/add.js +23 -5
  88. package/dist/commands/spaces/actions/available.js +3 -0
  89. package/dist/commands/spaces/actions/list.d.ts +5 -0
  90. package/dist/commands/spaces/actions/list.js +49 -3
  91. package/dist/commands/spaces/actions/run.d.ts +28 -0
  92. package/dist/commands/spaces/actions/run.js +146 -0
  93. package/dist/commands/spaces/actions/update.js +27 -5
  94. package/dist/commands/spaces/label-summaries.d.ts +27 -0
  95. package/dist/commands/spaces/label-summaries.js +64 -0
  96. package/dist/commands/spaces/task-rollups.d.ts +5 -0
  97. package/dist/commands/spaces/task-rollups.js +15 -4
  98. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +63 -3
  99. package/dist/commands/tasks/actions/attach.js +32 -7
  100. package/dist/commands/tasks/complete.js +13 -0
  101. package/dist/commands/tasks/create.js +1 -1
  102. package/dist/commands/tasks/events/append.js +29 -6
  103. package/dist/commands/tasks/expectations/assess.d.ts +1 -0
  104. package/dist/commands/tasks/expectations/assess.js +43 -5
  105. package/dist/commands/tasks/expectations.js +77 -16
  106. package/dist/commands/tasks/follow-up.js +2 -2
  107. package/dist/commands/tasks/lifecycle/definitions.js +21 -2
  108. package/dist/commands/tasks/lifecycle/detach.d.ts +7 -0
  109. package/dist/commands/tasks/lifecycle/detach.js +43 -3
  110. package/dist/commands/tasks/lifecycle/migrate.d.ts +2 -0
  111. package/dist/commands/tasks/lifecycle/migrate.js +42 -3
  112. package/dist/commands/tasks/lifecycle/reopen.d.ts +2 -0
  113. package/dist/commands/tasks/lifecycle/reopen.js +40 -3
  114. package/dist/commands/tasks/lifecycle/transition.js +33 -2
  115. package/dist/commands/tasks/list.js +16 -9
  116. package/dist/commands/tasks/move.js +11 -13
  117. package/dist/commands/tasks/review-queue.d.ts +15 -0
  118. package/dist/commands/tasks/review-queue.js +180 -31
  119. package/dist/commands/tasks/show.d.ts +11 -0
  120. package/dist/commands/tasks/show.js +105 -12
  121. package/dist/commands/tasks/trust.js +7 -2
  122. package/dist/commands/tasks/update.js +46 -10
  123. package/dist/commands/tasks/updates/add.js +1 -1
  124. package/dist/commands/token/list.d.ts +22 -0
  125. package/dist/commands/token/list.js +68 -0
  126. package/dist/commands/token/revoke.d.ts +23 -0
  127. package/dist/commands/token/revoke.js +53 -0
  128. package/dist/lib/agent-actions.d.ts +57 -2
  129. package/dist/lib/agent-actions.js +113 -21
  130. package/dist/lib/agent-pin-mute.d.ts +10 -5
  131. package/dist/lib/agent-pin-mute.js +17 -4
  132. package/dist/lib/agent-visual-refs.d.ts +27 -0
  133. package/dist/lib/agent-visual-refs.js +127 -2
  134. package/dist/lib/agentic-stream.d.ts +6 -0
  135. package/dist/lib/agentic-stream.js +34 -3
  136. package/dist/lib/assignee-resolver.d.ts +1 -1
  137. package/dist/lib/assignee-resolver.js +8 -0
  138. package/dist/lib/bulk-task-targets.d.ts +12 -0
  139. package/dist/lib/bulk-task-targets.js +34 -1
  140. package/dist/lib/cli-installers.d.ts +130 -0
  141. package/dist/lib/cli-installers.js +145 -0
  142. package/dist/lib/command-miss.js +55 -7
  143. package/dist/lib/dedicated-lease-command.d.ts +16 -1
  144. package/dist/lib/dedicated-lease-command.js +26 -3
  145. package/dist/lib/dedicated-machines.d.ts +135 -1
  146. package/dist/lib/dedicated-machines.js +307 -3
  147. package/dist/lib/failed-lookup.d.ts +3 -0
  148. package/dist/lib/failed-lookup.js +40 -0
  149. package/dist/lib/first-party-harness-agent.js +9 -2
  150. package/dist/lib/first-party-harness-doctor.d.ts +15 -0
  151. package/dist/lib/first-party-harness-doctor.js +126 -27
  152. package/dist/lib/followups.d.ts +14 -6
  153. package/dist/lib/followups.js +31 -6
  154. package/dist/lib/harnesses.d.ts +8 -0
  155. package/dist/lib/instruction-input.d.ts +19 -1
  156. package/dist/lib/instruction-input.js +52 -1
  157. package/dist/lib/label-scope.d.ts +15 -0
  158. package/dist/lib/label-scope.js +25 -5
  159. package/dist/lib/local-skills.d.ts +98 -0
  160. package/dist/lib/local-skills.js +195 -0
  161. package/dist/lib/machines.d.ts +10 -10
  162. package/dist/lib/machines.js +23 -1
  163. package/dist/lib/search-query.d.ts +19 -0
  164. package/dist/lib/search-query.js +35 -0
  165. package/dist/lib/space-custom-actions.d.ts +72 -0
  166. package/dist/lib/space-custom-actions.js +117 -0
  167. package/dist/lib/space-resolver.d.ts +1 -21
  168. package/dist/lib/space-resolver.js +19 -10
  169. package/dist/lib/task-closure.d.ts +3 -2
  170. package/dist/lib/task-closure.js +18 -20
  171. package/dist/lib/task-lifecycle-output.js +117 -9
  172. package/dist/lib/task-ref-resolver.d.ts +1 -17
  173. package/dist/lib/task-ref-resolver.js +18 -8
  174. package/dist/lib/tasks.js +9 -3
  175. package/dist/node_modules/@skrr-ai/data-provider/index.js +5076 -4947
  176. package/oclif.manifest.json +15505 -14460
  177. package/package.json +9 -3
@@ -7,6 +7,7 @@ exports.describeMember = describeMember;
7
7
  exports.describeCandidates = describeCandidates;
8
8
  exports.resolveAssigneeReferences = resolveAssigneeReferences;
9
9
  exports.formatResolutionFailure = formatResolutionFailure;
10
+ const failed_lookup_1 = require("./failed-lookup");
10
11
  const data_provider_1 = require("@skrr-ai/data-provider");
11
12
  const OBJECT_ID_RE = /^[0-9a-fA-F]{24}$/;
12
13
  const UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
@@ -84,6 +85,11 @@ function resolveMember(query, members, kind) {
84
85
  if (!q) {
85
86
  return { ok: false, code: 'NOT_FOUND', query: raw, kind, candidates: pool };
86
87
  }
88
+ // A failed lookup's output is refused before the name tiers can match it.
89
+ // See `failed-lookup.ts`.
90
+ if ((0, failed_lookup_1.isFailedLookupValue)(raw)) {
91
+ return { ok: false, code: 'NOT_A_REFERENCE', query: raw, kind, candidates: [] };
92
+ }
87
93
  const tiers = [
88
94
  // 1. Exact id. Case-sensitive on purpose — ids are opaque.
89
95
  (m) => m.id === raw,
@@ -186,6 +192,8 @@ function formatResolutionFailure(failure, bin = 'skrr') {
186
192
  const noun = failure.kind === 'user' ? 'person' : 'agent';
187
193
  const plural = failure.kind === 'user' ? 'people' : 'agents';
188
194
  switch (failure.code) {
195
+ case 'NOT_A_REFERENCE':
196
+ return (0, failed_lookup_1.describeFailedLookupValue)(failure.query, noun);
189
197
  case 'AMBIGUOUS':
190
198
  return (`"${failure.query}" matches ${failure.candidates.length} ${plural} in this space:\n` +
191
199
  `${describeCandidates(failure.candidates)}\n` +
@@ -29,6 +29,18 @@ export type BulkApplyResult = {
29
29
  * semantics match `skrr tasks bulk-update`). `apply` returns whether the task
30
30
  * actually changed (already-satisfied tasks are reported as no-ops).
31
31
  */
32
+ /**
33
+ * The server's reason for a failed per-task write, not the transport's.
34
+ *
35
+ * A bulk run used to record `err.message`, which for a dataService call is the
36
+ * transport line — `HTTP 400 Bad Request — PATCH https://…/api/tasks/<id>` — so
37
+ * `tasks actions attach --all-in-space` printed that once per task and named
38
+ * neither the ref nor the rule that refused it (OSK-9652). The same call made
39
+ * for a single task goes through `handleApiError`, which reads the body. This
40
+ * reads it the same way: `message`, then `error`, plus the first validation
41
+ * detail when the server sent one.
42
+ */
43
+ export declare function bulkFailureMessage(err: unknown): string;
32
44
  export declare function applyToTaskTargets(targets: Array<{
33
45
  id: string;
34
46
  identifier?: string;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.listSpaceTaskTargets = listSpaceTaskTargets;
4
+ exports.bulkFailureMessage = bulkFailureMessage;
4
5
  exports.applyToTaskTargets = applyToTaskTargets;
5
6
  exports.formatBulkOutcome = formatBulkOutcome;
6
7
  exports.parseStatusFilter = parseStatusFilter;
@@ -34,6 +35,38 @@ async function listSpaceTaskTargets(options) {
34
35
  * semantics match `skrr tasks bulk-update`). `apply` returns whether the task
35
36
  * actually changed (already-satisfied tasks are reported as no-ops).
36
37
  */
38
+ /**
39
+ * The server's reason for a failed per-task write, not the transport's.
40
+ *
41
+ * A bulk run used to record `err.message`, which for a dataService call is the
42
+ * transport line — `HTTP 400 Bad Request — PATCH https://…/api/tasks/<id>` — so
43
+ * `tasks actions attach --all-in-space` printed that once per task and named
44
+ * neither the ref nor the rule that refused it (OSK-9652). The same call made
45
+ * for a single task goes through `handleApiError`, which reads the body. This
46
+ * reads it the same way: `message`, then `error`, plus the first validation
47
+ * detail when the server sent one.
48
+ */
49
+ function bulkFailureMessage(err) {
50
+ const e = err;
51
+ if (e && typeof e.body === 'string' && e.body.trim()) {
52
+ try {
53
+ const body = JSON.parse(e.body);
54
+ const reason = [body.message, body.error].find((value) => typeof value === 'string' && value.trim().length > 0);
55
+ if (reason) {
56
+ const detail = Array.isArray(body.details) ? body.details[0] : undefined;
57
+ const detailText = detail && typeof detail.message === 'string'
58
+ ? ` — ${Array.isArray(detail.path) && detail.path.length ? `${detail.path.join('.')}: ` : ''}${detail.message}`
59
+ : '';
60
+ const status = typeof e.status === 'number' ? ` (HTTP ${e.status})` : '';
61
+ return `${reason}${detailText}${status}`;
62
+ }
63
+ }
64
+ catch {
65
+ /* not JSON: fall back to the transport message below */
66
+ }
67
+ }
68
+ return err instanceof Error ? err.message : String(err);
69
+ }
37
70
  async function applyToTaskTargets(targets, apply) {
38
71
  const result = { succeeded: [], failed: [] };
39
72
  for (const target of targets) {
@@ -46,7 +79,7 @@ async function applyToTaskTargets(targets, apply) {
46
79
  result.failed.push({
47
80
  id: target.id,
48
81
  label,
49
- error: err instanceof Error ? err.message : String(err),
82
+ error: bulkFailureMessage(err),
50
83
  });
51
84
  }
52
85
  }
@@ -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 {
@@ -61,7 +61,22 @@ export declare abstract class DedicatedLeaseCommand extends BaseCommand {
61
61
  timeout?: string;
62
62
  interval?: number;
63
63
  workspace?: string;
64
- }, goal: DedicatedWaitGoal | null, verb: string): Promise<void>;
64
+ }, goal: DedicatedWaitGoal | null, verb: string,
65
+ /** Fields a `--json` caller needs that the server response does not carry. */
66
+ jsonExtra?: Record<string, unknown>): Promise<void>;
65
67
  }
66
68
  /** The same transient verdict BaseCommand gives an ordinary API call. */
67
69
  export declare function dedicatedRuntimeApiErrorRetryable(err: unknown): boolean;
70
+ /**
71
+ * Accept the id our own output printed.
72
+ *
73
+ * `skrr machines list --all` renders a Dedicated Runtime's MACHINE identity,
74
+ * which the server builds as `lease:${leaseId}` (`machineIdentityForHarness`).
75
+ * The lease routes want the bare `rtl_…`. Pasting the one you were just shown
76
+ * therefore 404'd, and the refusal blamed workspace scope — sending you to try
77
+ * `--workspace` when the fix was deleting six characters.
78
+ *
79
+ * Only the prefixes this product itself prints are stripped, so a genuinely
80
+ * unknown id still fails as an unknown id rather than being silently reshaped.
81
+ */
82
+ 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 });
@@ -144,7 +145,9 @@ class DedicatedLeaseCommand extends base_command_1.BaseCommand {
144
145
  * Print the lease an action returned, or wait for its operation to finish
145
146
  * first when `--wait` was passed.
146
147
  */
147
- async reportLeaseAction(response, flags, goal, verb) {
148
+ async reportLeaseAction(response, flags, goal, verb,
149
+ /** Fields a `--json` caller needs that the server response does not carry. */
150
+ jsonExtra = {}) {
148
151
  let lease = response?.lease;
149
152
  const id = (0, dedicated_machines_1.dedicatedLeaseId)(lease);
150
153
  if (flags.wait && goal && id) {
@@ -200,7 +203,7 @@ class DedicatedLeaseCommand extends base_command_1.BaseCommand {
200
203
  }
201
204
  }
202
205
  if (flags.json) {
203
- this.log(JSON.stringify({ ...(response || {}), lease }, null, 2));
206
+ this.log(JSON.stringify({ ...(response || {}), lease, ...jsonExtra }, null, 2));
204
207
  return;
205
208
  }
206
209
  this.log(`Dedicated Runtime ${id || 'unknown'} ${(0, dedicated_machines_1.dedicatedLeaseState)(lease)}.`);
@@ -226,3 +229,23 @@ function dedicatedRuntimeApiErrorRetryable(err) {
226
229
  }
227
230
  return (0, base_command_1.isRetryableFailure)(err.status, body);
228
231
  }
232
+ /**
233
+ * Accept the id our own output printed.
234
+ *
235
+ * `skrr machines list --all` renders a Dedicated Runtime's MACHINE identity,
236
+ * which the server builds as `lease:${leaseId}` (`machineIdentityForHarness`).
237
+ * The lease routes want the bare `rtl_…`. Pasting the one you were just shown
238
+ * therefore 404'd, and the refusal blamed workspace scope — sending you to try
239
+ * `--workspace` when the fix was deleting six characters.
240
+ *
241
+ * Only the prefixes this product itself prints are stripped, so a genuinely
242
+ * unknown id still fails as an unknown id rather than being silently reshaped.
243
+ */
244
+ function stripMachineIdentityPrefix(ref) {
245
+ const trimmed = ref.trim();
246
+ for (const prefix of ['lease:', 'dedicated-runtime:']) {
247
+ if (trimmed.startsWith(prefix))
248
+ return trimmed.slice(prefix.length);
249
+ }
250
+ return trimmed;
251
+ }