@skrr-ai/cli 0.1.36 → 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 (266) hide show
  1. package/README.md +17 -4
  2. package/bin/dev-fallback.js +167 -4
  3. package/dist/base-command.js +46 -2
  4. package/dist/commands/agents/actions/create.d.ts +5 -0
  5. package/dist/commands/agents/actions/create.js +42 -5
  6. package/dist/commands/agents/actions/delete.d.ts +1 -0
  7. package/dist/commands/agents/actions/delete.js +7 -0
  8. package/dist/commands/agents/actions/update.d.ts +5 -0
  9. package/dist/commands/agents/actions/update.js +27 -1
  10. package/dist/commands/agents/api-actions/add.d.ts +41 -0
  11. package/dist/commands/agents/api-actions/add.js +205 -0
  12. package/dist/commands/agents/api-actions/index.d.ts +39 -0
  13. package/dist/commands/agents/api-actions/index.js +103 -0
  14. package/dist/commands/agents/api-actions/remove.d.ts +14 -0
  15. package/dist/commands/agents/api-actions/remove.js +36 -0
  16. package/dist/commands/agents/avatar/default/apply.d.ts +25 -0
  17. package/dist/commands/agents/avatar/default/apply.js +56 -0
  18. package/dist/commands/agents/avatar/default/index.d.ts +29 -0
  19. package/dist/commands/agents/avatar/default/index.js +61 -0
  20. package/dist/commands/agents/avatar/default/set.d.ts +24 -0
  21. package/dist/commands/agents/avatar/default/set.js +120 -0
  22. package/dist/commands/agents/avatar/library/delete.d.ts +20 -0
  23. package/dist/commands/agents/avatar/library/delete.js +47 -0
  24. package/dist/commands/agents/avatar/library/index.d.ts +16 -0
  25. package/dist/commands/agents/avatar/library/index.js +56 -0
  26. package/dist/commands/agents/avatar/presets.d.ts +19 -0
  27. package/dist/commands/agents/avatar/presets.js +56 -0
  28. package/dist/commands/agents/avatar/upload.js +7 -1
  29. package/dist/commands/agents/categories/create.d.ts +14 -0
  30. package/dist/commands/agents/categories/create.js +46 -0
  31. package/dist/commands/agents/categories/delete.d.ts +13 -0
  32. package/dist/commands/agents/categories/delete.js +41 -0
  33. package/dist/commands/agents/categories/index.d.ts +36 -0
  34. package/dist/commands/agents/categories/index.js +135 -0
  35. package/dist/commands/agents/categories/update.d.ts +22 -0
  36. package/dist/commands/agents/categories/update.js +59 -0
  37. package/dist/commands/agents/chat.d.ts +21 -0
  38. package/dist/commands/agents/chat.js +94 -7
  39. package/dist/commands/agents/create.d.ts +10 -0
  40. package/dist/commands/agents/create.js +64 -3
  41. package/dist/commands/agents/ensure-default.d.ts +27 -0
  42. package/dist/commands/agents/ensure-default.js +65 -0
  43. package/dist/commands/agents/list.d.ts +15 -0
  44. package/dist/commands/agents/list.js +31 -1
  45. package/dist/commands/agents/mute.d.ts +16 -0
  46. package/dist/commands/agents/mute.js +38 -0
  47. package/dist/commands/agents/pin.d.ts +16 -0
  48. package/dist/commands/agents/pin.js +38 -0
  49. package/dist/commands/agents/scene-background/upload.d.ts +30 -0
  50. package/dist/commands/agents/scene-background/upload.js +97 -0
  51. package/dist/commands/agents/show.js +13 -0
  52. package/dist/commands/agents/tools/auth.d.ts +19 -0
  53. package/dist/commands/agents/tools/auth.js +41 -0
  54. package/dist/commands/agents/tools/calls.d.ts +36 -0
  55. package/dist/commands/agents/tools/calls.js +117 -0
  56. package/dist/commands/agents/tools/index.d.ts +20 -0
  57. package/dist/commands/agents/tools/index.js +77 -0
  58. package/dist/commands/agents/unmute.d.ts +16 -0
  59. package/dist/commands/agents/unmute.js +38 -0
  60. package/dist/commands/agents/unpin.d.ts +16 -0
  61. package/dist/commands/agents/unpin.js +38 -0
  62. package/dist/commands/agents/update.d.ts +12 -0
  63. package/dist/commands/agents/update.js +74 -3
  64. package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
  65. package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
  66. package/dist/commands/convos/messages.d.ts +13 -0
  67. package/dist/commands/convos/messages.js +40 -16
  68. package/dist/commands/daemon/index.js +8 -1
  69. package/dist/commands/daemon/logs.d.ts +26 -0
  70. package/dist/commands/daemon/logs.js +56 -0
  71. package/dist/commands/daemon/uninstall.js +1 -1
  72. package/dist/commands/followups/act.d.ts +22 -0
  73. package/dist/commands/followups/act.js +103 -0
  74. package/dist/commands/followups/cancel.d.ts +20 -0
  75. package/dist/commands/followups/cancel.js +50 -0
  76. package/dist/commands/followups/checkin.d.ts +27 -0
  77. package/dist/commands/followups/checkin.js +107 -0
  78. package/dist/commands/followups/list.d.ts +24 -0
  79. package/dist/commands/followups/list.js +90 -0
  80. package/dist/commands/followups/observability.d.ts +23 -0
  81. package/dist/commands/followups/observability.js +114 -0
  82. package/dist/commands/followups/remind.d.ts +26 -0
  83. package/dist/commands/followups/remind.js +106 -0
  84. package/dist/commands/followups/reschedule.d.ts +15 -0
  85. package/dist/commands/followups/reschedule.js +48 -0
  86. package/dist/commands/followups/resolve.d.ts +23 -0
  87. package/dist/commands/followups/resolve.js +54 -0
  88. package/dist/commands/followups/show.d.ts +12 -0
  89. package/dist/commands/followups/show.js +46 -0
  90. package/dist/commands/followups/watch.d.ts +51 -0
  91. package/dist/commands/followups/watch.js +211 -0
  92. package/dist/commands/goals/labels/add.js +19 -2
  93. package/dist/commands/goals/labels/remove.js +22 -2
  94. package/dist/commands/harnesses/forget.js +28 -0
  95. package/dist/commands/harnesses/install.d.ts +41 -0
  96. package/dist/commands/harnesses/install.js +165 -0
  97. package/dist/commands/harnesses/installers.d.ts +45 -0
  98. package/dist/commands/harnesses/installers.js +155 -0
  99. package/dist/commands/harnesses/list.d.ts +82 -1
  100. package/dist/commands/harnesses/list.js +104 -12
  101. package/dist/commands/harnesses/models.js +10 -1
  102. package/dist/commands/harnesses/show.d.ts +15 -0
  103. package/dist/commands/harnesses/show.js +44 -3
  104. package/dist/commands/harnesses/update.js +1 -1
  105. package/dist/commands/harnesses/usage.d.ts +25 -0
  106. package/dist/commands/harnesses/usage.js +24 -2
  107. package/dist/commands/instructions/status.js +13 -1
  108. package/dist/commands/labels/list.d.ts +24 -1
  109. package/dist/commands/labels/list.js +62 -19
  110. package/dist/commands/machines/dedicated/catalog.js +4 -15
  111. package/dist/commands/machines/dedicated/create.d.ts +8 -0
  112. package/dist/commands/machines/dedicated/create.js +58 -1
  113. package/dist/commands/machines/dedicated/destroy.js +18 -4
  114. package/dist/commands/machines/dedicated/list.js +9 -6
  115. package/dist/commands/machines/hosted/list.js +12 -0
  116. package/dist/commands/machines/list.js +11 -0
  117. package/dist/commands/machines/show.d.ts +1 -0
  118. package/dist/commands/machines/show.js +40 -11
  119. package/dist/commands/memory/governance/index.d.ts +21 -0
  120. package/dist/commands/memory/governance/index.js +64 -0
  121. package/dist/commands/memory/governance/set.d.ts +24 -0
  122. package/dist/commands/memory/governance/set.js +95 -0
  123. package/dist/commands/memory/provenance.d.ts +18 -0
  124. package/dist/commands/memory/provenance.js +66 -0
  125. package/dist/commands/profiles/list.d.ts +23 -0
  126. package/dist/commands/profiles/list.js +96 -0
  127. package/dist/commands/search/skills.d.ts +11 -1
  128. package/dist/commands/search/skills.js +26 -8
  129. package/dist/commands/skills/details.d.ts +2 -0
  130. package/dist/commands/skills/details.js +24 -1
  131. package/dist/commands/skills/import-as-actions.d.ts +32 -0
  132. package/dist/commands/skills/import-as-actions.js +175 -0
  133. package/dist/commands/skills/install.d.ts +2 -0
  134. package/dist/commands/skills/install.js +24 -1
  135. package/dist/commands/skills/invocable.js +6 -2
  136. package/dist/commands/skills/local.d.ts +40 -0
  137. package/dist/commands/skills/local.js +160 -0
  138. package/dist/commands/skills/search.d.ts +11 -1
  139. package/dist/commands/skills/search.js +29 -8
  140. package/dist/commands/spaces/actions/add.js +23 -5
  141. package/dist/commands/spaces/actions/available.js +3 -0
  142. package/dist/commands/spaces/actions/list.d.ts +5 -0
  143. package/dist/commands/spaces/actions/list.js +49 -3
  144. package/dist/commands/spaces/actions/run.d.ts +28 -0
  145. package/dist/commands/spaces/actions/run.js +146 -0
  146. package/dist/commands/spaces/actions/update.js +27 -5
  147. package/dist/commands/spaces/create.d.ts +3 -0
  148. package/dist/commands/spaces/create.js +32 -2
  149. package/dist/commands/spaces/label-summaries.d.ts +27 -0
  150. package/dist/commands/spaces/label-summaries.js +64 -0
  151. package/dist/commands/spaces/labels/add.js +19 -2
  152. package/dist/commands/spaces/labels/remove.js +22 -2
  153. package/dist/commands/spaces/show.js +2 -0
  154. package/dist/commands/spaces/task-rollups.d.ts +5 -0
  155. package/dist/commands/spaces/task-rollups.js +15 -4
  156. package/dist/commands/spaces/update.d.ts +5 -0
  157. package/dist/commands/spaces/update.js +41 -4
  158. package/dist/commands/spaces/workflows/lifecycle-diagnostics.d.ts +11 -0
  159. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +105 -0
  160. package/dist/commands/tasks/actions/attach.js +32 -7
  161. package/dist/commands/tasks/complete.d.ts +1 -0
  162. package/dist/commands/tasks/complete.js +21 -1
  163. package/dist/commands/tasks/create.d.ts +3 -0
  164. package/dist/commands/tasks/create.js +34 -2
  165. package/dist/commands/tasks/events/append.js +29 -6
  166. package/dist/commands/tasks/expectations/assess.d.ts +12 -0
  167. package/dist/commands/tasks/expectations/assess.js +123 -14
  168. package/dist/commands/tasks/expectations.js +95 -17
  169. package/dist/commands/tasks/follow-up.js +2 -2
  170. package/dist/commands/tasks/labels/attach.js +20 -3
  171. package/dist/commands/tasks/labels/detach.js +21 -3
  172. package/dist/commands/tasks/lifecycle/attach.d.ts +14 -0
  173. package/dist/commands/tasks/lifecycle/attach.js +47 -0
  174. package/dist/commands/tasks/lifecycle/definitions.d.ts +11 -0
  175. package/dist/commands/tasks/lifecycle/definitions.js +59 -0
  176. package/dist/commands/tasks/lifecycle/detach.d.ts +19 -0
  177. package/dist/commands/tasks/lifecycle/detach.js +72 -0
  178. package/dist/commands/tasks/lifecycle/history.d.ts +13 -0
  179. package/dist/commands/tasks/lifecycle/history.js +38 -0
  180. package/dist/commands/tasks/lifecycle/migrate.d.ts +14 -0
  181. package/dist/commands/tasks/lifecycle/migrate.js +68 -0
  182. package/dist/commands/tasks/lifecycle/reconcile.d.ts +11 -0
  183. package/dist/commands/tasks/lifecycle/reconcile.js +27 -0
  184. package/dist/commands/tasks/lifecycle/reopen.d.ts +15 -0
  185. package/dist/commands/tasks/lifecycle/reopen.js +71 -0
  186. package/dist/commands/tasks/lifecycle/show.d.ts +11 -0
  187. package/dist/commands/tasks/lifecycle/show.js +27 -0
  188. package/dist/commands/tasks/lifecycle/start.d.ts +12 -0
  189. package/dist/commands/tasks/lifecycle/start.js +30 -0
  190. package/dist/commands/tasks/lifecycle/transition.d.ts +15 -0
  191. package/dist/commands/tasks/lifecycle/transition.js +109 -0
  192. package/dist/commands/tasks/list.js +16 -9
  193. package/dist/commands/tasks/move.d.ts +1 -0
  194. package/dist/commands/tasks/move.js +29 -0
  195. package/dist/commands/tasks/result/submit.js +11 -1
  196. package/dist/commands/tasks/review-queue.d.ts +44 -0
  197. package/dist/commands/tasks/review-queue.js +314 -0
  198. package/dist/commands/tasks/show.d.ts +11 -0
  199. package/dist/commands/tasks/show.js +139 -4
  200. package/dist/commands/tasks/trust.js +7 -2
  201. package/dist/commands/tasks/update.d.ts +1 -0
  202. package/dist/commands/tasks/update.js +83 -0
  203. package/dist/commands/tasks/updates/add.js +1 -1
  204. package/dist/commands/token/list.d.ts +22 -0
  205. package/dist/commands/token/list.js +68 -0
  206. package/dist/commands/token/revoke.d.ts +23 -0
  207. package/dist/commands/token/revoke.js +53 -0
  208. package/dist/lib/agent-actions.d.ts +92 -11
  209. package/dist/lib/agent-actions.js +171 -25
  210. package/dist/lib/agent-config.d.ts +11 -0
  211. package/dist/lib/agent-config.js +85 -3
  212. package/dist/lib/agent-pin-mute.d.ts +29 -0
  213. package/dist/lib/agent-pin-mute.js +30 -0
  214. package/dist/lib/agent-visual-refs.d.ts +33 -0
  215. package/dist/lib/agent-visual-refs.js +267 -0
  216. package/dist/lib/agentic-stream.d.ts +6 -0
  217. package/dist/lib/agentic-stream.js +34 -3
  218. package/dist/lib/assignee-resolver.d.ts +1 -1
  219. package/dist/lib/assignee-resolver.js +8 -0
  220. package/dist/lib/bulk-task-targets.d.ts +12 -0
  221. package/dist/lib/bulk-task-targets.js +34 -1
  222. package/dist/lib/cli-installers.d.ts +130 -0
  223. package/dist/lib/cli-installers.js +145 -0
  224. package/dist/lib/command-miss.js +55 -7
  225. package/dist/lib/dedicated-lease-command.d.ts +13 -0
  226. package/dist/lib/dedicated-lease-command.js +22 -1
  227. package/dist/lib/dedicated-machines.d.ts +95 -1
  228. package/dist/lib/dedicated-machines.js +236 -2
  229. package/dist/lib/failed-lookup.d.ts +3 -0
  230. package/dist/lib/failed-lookup.js +40 -0
  231. package/dist/lib/first-party-harness-agent.js +16 -2
  232. package/dist/lib/first-party-harness-doctor.d.ts +15 -0
  233. package/dist/lib/first-party-harness-doctor.js +126 -27
  234. package/dist/lib/followups.d.ts +115 -0
  235. package/dist/lib/followups.js +127 -0
  236. package/dist/lib/harnesses.d.ts +8 -0
  237. package/dist/lib/instruction-input.d.ts +19 -1
  238. package/dist/lib/instruction-input.js +52 -1
  239. package/dist/lib/label-ref.d.ts +95 -0
  240. package/dist/lib/label-ref.js +163 -0
  241. package/dist/lib/label-scope.d.ts +17 -2
  242. package/dist/lib/label-scope.js +27 -7
  243. package/dist/lib/local-skills.d.ts +98 -0
  244. package/dist/lib/local-skills.js +195 -0
  245. package/dist/lib/machines.d.ts +10 -10
  246. package/dist/lib/machines.js +23 -1
  247. package/dist/lib/search-query.d.ts +19 -0
  248. package/dist/lib/search-query.js +35 -0
  249. package/dist/lib/space-custom-actions.d.ts +72 -0
  250. package/dist/lib/space-custom-actions.js +117 -0
  251. package/dist/lib/space-resolver.d.ts +1 -21
  252. package/dist/lib/space-resolver.js +19 -10
  253. package/dist/lib/task-closure.d.ts +33 -0
  254. package/dist/lib/task-closure.js +104 -0
  255. package/dist/lib/task-lifecycle-output.d.ts +14 -0
  256. package/dist/lib/task-lifecycle-output.js +209 -0
  257. package/dist/lib/task-ref-resolver.d.ts +1 -17
  258. package/dist/lib/task-ref-resolver.js +18 -8
  259. package/dist/lib/tasks.d.ts +3 -0
  260. package/dist/lib/tasks.js +40 -3
  261. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.js +4 -0
  262. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.js +4 -0
  263. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  264. package/dist/node_modules/@skrr-ai/data-provider/index.js +5559 -4897
  265. package/oclif.manifest.json +33890 -28690
  266. package/package.json +24 -6
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
+ const base_command_1 = require("../../base-command");
6
+ const cli_installers_1 = require("../../lib/cli-installers");
7
+ const daemon_ref_1 = require("../../lib/daemon-ref");
8
+ const daemon_target_1 = require("../../lib/daemon-target");
9
+ /**
10
+ * Install one managed coding-agent CLI on a machine.
11
+ *
12
+ * ## Why this did not exist
13
+ *
14
+ * The daemon has installed any of seven providers since `d878b79581`, and
15
+ * `POST /api/daemons/:id/cli-installers/:provider/install` is the request the
16
+ * web app's Install button sends. From the CLI only ONE of them was reachable:
17
+ * `skrr code install`, whose provider is a constant. Everything else had to be
18
+ * installed by hand, or from the web app.
19
+ *
20
+ * `skrr harnesses install-token` made that worse rather than neutral. It is the
21
+ * only entry in `harnesses --help` with "install" in its name, so a user
22
+ * scanning for "how do I install a harness" lands on it — and it mints a daemon
23
+ * enrolment token for another machine, which is a different thing entirely.
24
+ *
25
+ * ## The provider name is validated here, against the shared vocabulary
26
+ *
27
+ * Unlike `harnesses list --provider`, where the set is open because a daemon
28
+ * registers a row per capability it detects, the INSTALLABLE set is closed and
29
+ * owned: `MANAGED_CLI_PROVIDER_IDS` in `@skrr-ai/data-provider`, which the
30
+ * daemon's own list is pinned against by `managedCliProviders.spec.ts`. So a
31
+ * typo can be rejected with the real options, rather than sent to the daemon to
32
+ * come back as an opaque refusal.
33
+ */
34
+ class HarnessesInstall extends base_command_1.BaseCommand {
35
+ static description = 'Install a managed coding-agent CLI (claude, codex, devin, …) on one of your machines';
36
+ static examples = [
37
+ '<%= config.bin %> harnesses install devin',
38
+ '<%= config.bin %> harnesses install codex --daemon daemon_07c086e3c249_cli_default',
39
+ ];
40
+ static args = {
41
+ provider: core_1.Args.string({
42
+ ignoreStdin: true,
43
+ required: true,
44
+ description: `Which CLI to install (${data_provider_1.MANAGED_CLI_PROVIDER_IDS.join(', ')})`,
45
+ }),
46
+ };
47
+ static flags = {
48
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
49
+ daemon: core_1.Flags.string({
50
+ description: 'Which machine to install on. Defaults to your only active daemon; required when several are active.',
51
+ }),
52
+ };
53
+ async run() {
54
+ this.requireAuth();
55
+ const { args, flags } = await this.parse(HarnessesInstall);
56
+ // Case-insensitive, and every accepted spelling of the first-party harness
57
+ // resolves to its canonical id — the same normalization the route applies.
58
+ const provider = (0, data_provider_1.normalizeManagedCliProvider)(args.provider);
59
+ if (!provider) {
60
+ this.error(`"${args.provider}" is not a managed CLI provider. Installable: ${data_provider_1.MANAGED_CLI_PROVIDER_IDS.join(', ')}.`, { exit: 2 });
61
+ return;
62
+ }
63
+ const target = await (0, daemon_target_1.resolveDaemonTarget)({
64
+ explicit: flags.daemon,
65
+ local: (0, daemon_target_1.localMachine)(),
66
+ listActiveDaemons: () => (0, daemon_target_1.listActiveDaemons)(this.resolvedCredential),
67
+ });
68
+ const daemonId = this.daemonIdFor(target);
69
+ let start;
70
+ try {
71
+ start = await (0, cli_installers_1.startManagedCliInstall)(daemonId, provider, this.resolvedCredential);
72
+ }
73
+ catch (err) {
74
+ this.handleApiError(err);
75
+ return;
76
+ }
77
+ const jobId = start.job?.id;
78
+ if (!start.started || !jobId) {
79
+ // The daemon declined and already knows why. Read its own reason off the
80
+ // provider state rather than inventing an explanation for the silence —
81
+ // "Already installed." and "Not available on this platform." want very
82
+ // different responses, and a generic "install failed" hides both.
83
+ const reason = start.provider?.installDisabledReason?.trim() ||
84
+ (await this.installerNote(daemonId, provider));
85
+ this.log(reason ? `${provider}: ${reason}` : `${provider}: the daemon started no install job.`);
86
+ return;
87
+ }
88
+ if (!flags.json)
89
+ this.logToStderr(`Installing ${provider} on ${(0, daemon_ref_1.formatDaemonRef)(daemonId)}…`);
90
+ const outcome = await (0, cli_installers_1.followManagedCliInstallJob)({
91
+ daemonId,
92
+ jobId,
93
+ credential: this.resolvedCredential,
94
+ onLogLine: flags.json ? undefined : (line) => this.logToStderr(` ${line}`),
95
+ });
96
+ if (outcome.kind === 'missing') {
97
+ this.error(`The daemon no longer has install job ${jobId}.`, { exit: 1 });
98
+ return;
99
+ }
100
+ if (outcome.kind === 'timeout') {
101
+ // Not a failure: the daemon is still working, and calling this failed
102
+ // would send someone to debug a download that is going fine.
103
+ this.error(`Still installing after ${Math.round(cli_installers_1.INSTALL_MAX_WAIT_MS / 60000)} minutes — the daemon is still working on job ${jobId}. Check \`${this.config.bin} harnesses installers\` in a while.`, { exit: 1 });
104
+ return;
105
+ }
106
+ const job = outcome.job;
107
+ if (flags.json) {
108
+ this.log(JSON.stringify({ daemonId, provider, job }, null, 2));
109
+ if (job.status !== 'succeeded')
110
+ process.exitCode = 1;
111
+ return;
112
+ }
113
+ if (job.status === 'failed') {
114
+ this.error(`Installing ${provider} failed: ${job.error || 'the daemon gave no reason.'}`, {
115
+ exit: 1,
116
+ });
117
+ return;
118
+ }
119
+ // Report the state the install actually produced, not "done". Installing a
120
+ // binary is not the same as being able to run it: Devin lands
121
+ // `interactive-required`, and a user told only "installed" would find that
122
+ // out on their first turn instead.
123
+ const state = await this.installerNote(daemonId, provider, true);
124
+ this.log(`${provider} installed.${state ? ` ${state}` : ''}`);
125
+ }
126
+ /** What the daemon says about this provider right now, best effort. */
127
+ async installerNote(daemonId, provider, readiness = false) {
128
+ try {
129
+ const res = await (0, cli_installers_1.listCliInstallers)(daemonId, this.resolvedCredential);
130
+ const row = (res.providers ?? []).find((p) => (0, data_provider_1.normalizeManagedCliProvider)(p.provider) === provider);
131
+ if (!row)
132
+ return null;
133
+ if (readiness) {
134
+ const verdict = (0, cli_installers_1.readinessOf)(row);
135
+ return `${verdict.label}: ${verdict.detail}`;
136
+ }
137
+ const reason = row.installDisabledReason?.trim();
138
+ if (!reason)
139
+ return null;
140
+ return row.version ? `${reason} (${row.version})` : reason;
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ }
146
+ daemonIdFor(target) {
147
+ if (target.kind === 'daemon')
148
+ return target.daemonId;
149
+ if (target.kind === 'this-machine') {
150
+ if (target.daemonId)
151
+ return target.daemonId;
152
+ this.error('This machine is a Dedicated Runtime whose daemon has not published an id this process can read. Name a machine with --daemon.', { exit: 1 });
153
+ }
154
+ if (target.kind === 'none') {
155
+ this.error(`No active daemon is registered for this account, so nothing can perform the install. Start the local runtime with \`${this.config.bin} daemon start\`.`, { exit: 1 });
156
+ }
157
+ const choices = target.daemons
158
+ .slice(0, 10)
159
+ .map((d) => ` ${(0, daemon_ref_1.formatDaemonRef)(d.id)} ${d.name || '—'}`)
160
+ .join('\n');
161
+ this.error(`${target.daemons.length} active daemons — say which machine to install on:\n${choices}\n` +
162
+ `Pass one with --daemon, or see them all with \`${this.config.bin} list-daemons\`.`, { exit: 1 });
163
+ }
164
+ }
165
+ exports.default = HarnessesInstall;
@@ -0,0 +1,45 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * What each managed provider's state is on one machine.
4
+ *
5
+ * ## Why this command exists
6
+ *
7
+ * `d878b79581` replaced "is the binary there" with four states — installed,
8
+ * ready, interactive-required, headless-key-required — and routed them from
9
+ * `cli-installers` through health to the web harness create modal. It reached
10
+ * exactly one surface. From the CLI there was no way to ask.
11
+ *
12
+ * That gap is not cosmetic, because `skrr harnesses list` answers a DIFFERENT
13
+ * question and looks like it answers this one. Its `status` is the daemon's
14
+ * socket, so on this account `skrr-code` read `online` with no binary installed
15
+ * at all, and `harnesses ping` answered it in 78ms — the daemon was there, the
16
+ * harness was not. A user could not discover that from inside the product; it
17
+ * took `command -v` in a shell.
18
+ *
19
+ * So this prints what the DAEMON says about each provider: whether the binary
20
+ * is present, which one and where, and whether it is signed in. The four states
21
+ * keep their server-side names here rather than being re-prosed, so that a
22
+ * message quoted from the CLI matches the one in the modal.
23
+ *
24
+ * `unknown` is a first-class answer and is not `ready`. A provider whose recipe
25
+ * declares no credential resolver genuinely cannot be assessed from here, and
26
+ * saying so is the point — the failure this whole vocabulary exists to prevent
27
+ * is a surface that reports ready for a harness that will refuse on first use.
28
+ */
29
+ export default class HarnessesInstallers extends BaseCommand {
30
+ static description: string;
31
+ static examples: string[];
32
+ static flags: {
33
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
34
+ daemon: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
35
+ };
36
+ run(): Promise<void>;
37
+ /**
38
+ * Refuse rather than guess which machine to report on.
39
+ *
40
+ * Reading is harmless, but naming the wrong machine is not: the whole value
41
+ * here is "is the thing on MY laptop usable", and an answer about a sandbox
42
+ * the user forgot about is worse than no answer, because it looks like one.
43
+ */
44
+ private daemonIdFor;
45
+ }
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../base-command");
5
+ const data_provider_1 = require("@skrr-ai/data-provider");
6
+ const cli_installers_1 = require("../../lib/cli-installers");
7
+ const daemon_ref_1 = require("../../lib/daemon-ref");
8
+ const daemon_target_1 = require("../../lib/daemon-target");
9
+ const format_1 = require("../../lib/format");
10
+ /**
11
+ * What each managed provider's state is on one machine.
12
+ *
13
+ * ## Why this command exists
14
+ *
15
+ * `d878b79581` replaced "is the binary there" with four states — installed,
16
+ * ready, interactive-required, headless-key-required — and routed them from
17
+ * `cli-installers` through health to the web harness create modal. It reached
18
+ * exactly one surface. From the CLI there was no way to ask.
19
+ *
20
+ * That gap is not cosmetic, because `skrr harnesses list` answers a DIFFERENT
21
+ * question and looks like it answers this one. Its `status` is the daemon's
22
+ * socket, so on this account `skrr-code` read `online` with no binary installed
23
+ * at all, and `harnesses ping` answered it in 78ms — the daemon was there, the
24
+ * harness was not. A user could not discover that from inside the product; it
25
+ * took `command -v` in a shell.
26
+ *
27
+ * So this prints what the DAEMON says about each provider: whether the binary
28
+ * is present, which one and where, and whether it is signed in. The four states
29
+ * keep their server-side names here rather than being re-prosed, so that a
30
+ * message quoted from the CLI matches the one in the modal.
31
+ *
32
+ * `unknown` is a first-class answer and is not `ready`. A provider whose recipe
33
+ * declares no credential resolver genuinely cannot be assessed from here, and
34
+ * saying so is the point — the failure this whole vocabulary exists to prevent
35
+ * is a surface that reports ready for a harness that will refuse on first use.
36
+ */
37
+ class HarnessesInstallers extends base_command_1.BaseCommand {
38
+ static description = 'What each managed coding-agent CLI looks like on one machine — installed, which version, where, and whether it is signed in';
39
+ static examples = [
40
+ '<%= config.bin %> harnesses installers',
41
+ '<%= config.bin %> harnesses installers --daemon daemon_07c086e3c249_cli_default',
42
+ '<%= config.bin %> harnesses installers --json',
43
+ ];
44
+ static flags = {
45
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
46
+ daemon: core_1.Flags.string({
47
+ description: 'Which machine to ask. Defaults to your only active daemon; required when several are active.',
48
+ }),
49
+ };
50
+ async run() {
51
+ this.requireAuth();
52
+ const { flags } = await this.parse(HarnessesInstallers);
53
+ const target = await (0, daemon_target_1.resolveDaemonTarget)({
54
+ explicit: flags.daemon,
55
+ local: (0, daemon_target_1.localMachine)(),
56
+ listActiveDaemons: () => (0, daemon_target_1.listActiveDaemons)(this.resolvedCredential),
57
+ });
58
+ const daemonId = this.daemonIdFor(target);
59
+ let res;
60
+ try {
61
+ res = await (0, cli_installers_1.listCliInstallers)(daemonId, this.resolvedCredential);
62
+ }
63
+ catch (err) {
64
+ this.handleApiError(err);
65
+ return;
66
+ }
67
+ const providers = res.providers ?? [];
68
+ if (flags.json) {
69
+ this.log(JSON.stringify({ daemonId, platform: res.platform, providers }, null, 2));
70
+ return;
71
+ }
72
+ if (providers.length === 0) {
73
+ // A daemon too old to carry the inventory answers with no providers. That
74
+ // is not "you have none" — say which it is, since the remedy differs.
75
+ this.log(`The daemon ${daemonId} returned no installer inventory. ` +
76
+ 'It may predate managed CLI installers — update the skrr app or run `skrr daemon restart`.');
77
+ return;
78
+ }
79
+ // The RAW id, not `formatDaemonRef`. This header identifies which machine
80
+ // the answer is about, and the id is what the reader pastes back into
81
+ // `--daemon`; an 8-character prefix of a `daemon_<hash>_<source>_<profile>`
82
+ // id is `d_daemon_0...`, which distinguishes nothing and cannot be pasted.
83
+ this.log(`Managed coding-agent CLIs on ${daemonId} (${res.platform ?? '?'}, daemon ${res.daemonVersion ?? 'version not reported'})`);
84
+ this.log('');
85
+ (0, format_1.renderTable)(providers.map((p) => {
86
+ const readiness = (0, cli_installers_1.readinessOf)(p);
87
+ return {
88
+ provider: (0, format_1.formatCell)(p.provider),
89
+ readiness: readiness.label,
90
+ version: (0, format_1.formatCell)(p.version),
91
+ where: p.resolvedPath ?? '-',
92
+ };
93
+ }), [
94
+ { key: 'provider', header: 'PROVIDER' },
95
+ { key: 'readiness', header: 'READINESS' },
96
+ { key: 'version', header: 'VERSION' },
97
+ // Verbatim and UNCLIPPED. The resolved path answers "which copy is it
98
+ // using", and the cases where that matters are exactly the long ones:
99
+ // Devin's CLI lives at
100
+ // /Applications/Devin.app/Contents/Resources/app/extensions/windsurf/devin/bin/devin
101
+ // and is never on PATH, so `command -v devin` says it is not installed.
102
+ // Clipping from the right would hide the binary name and keep the
103
+ // surprise this column exists to reveal.
104
+ { key: 'where', header: 'RESOLVED PATH', verbatim: true },
105
+ ], (line) => this.log(` ${line}`));
106
+ // The detail lines carry the daemon's own wording. Printing them under the
107
+ // table rather than in a column keeps a full sentence readable — these are
108
+ // instructions ("Run `devin auth login`"), not statuses.
109
+ const notes = providers
110
+ .map((p) => ({ provider: p.provider, ...(0, cli_installers_1.readinessOf)(p) }))
111
+ .filter((n) => !n.ok && n.detail);
112
+ if (notes.length > 0) {
113
+ this.log('');
114
+ for (const note of notes)
115
+ this.log(` ${note.provider}: ${note.detail}`);
116
+ }
117
+ // A provider the platform knows and this daemon did not mention. Without
118
+ // this it simply is not on the list, which reads as dropped support rather
119
+ // than as an older daemon — `devin` arrived in daemon 0.8.43, so 0.8.35
120
+ // answers with six providers and says nothing about the seventh.
121
+ const missing = (0, cli_installers_1.providersNotOffered)(providers, data_provider_1.MANAGED_CLI_PROVIDER_IDS);
122
+ if (missing.length > 0) {
123
+ this.log('');
124
+ this.log(` Not offered by this daemon: ${missing.join(', ')}. ` +
125
+ 'That usually means the daemon is older than the provider — ' +
126
+ 'check `skrr daemon status` for its version and how to update it.');
127
+ }
128
+ }
129
+ /**
130
+ * Refuse rather than guess which machine to report on.
131
+ *
132
+ * Reading is harmless, but naming the wrong machine is not: the whole value
133
+ * here is "is the thing on MY laptop usable", and an answer about a sandbox
134
+ * the user forgot about is worse than no answer, because it looks like one.
135
+ */
136
+ daemonIdFor(target) {
137
+ if (target.kind === 'daemon')
138
+ return target.daemonId;
139
+ if (target.kind === 'this-machine') {
140
+ if (target.daemonId)
141
+ return target.daemonId;
142
+ this.error('This machine is a Dedicated Runtime whose daemon has not published an id this process can read. Name a machine with --daemon.', { exit: 1 });
143
+ }
144
+ if (target.kind === 'none') {
145
+ this.error(`No active daemon is registered for this account, so nothing can report on it. Start the local runtime with \`${this.config.bin} daemon start\`.`, { exit: 1 });
146
+ }
147
+ const choices = target.daemons
148
+ .slice(0, 10)
149
+ .map((d) => ` ${(0, daemon_ref_1.formatDaemonRef)(d.id)} ${d.name || '—'}`)
150
+ .join('\n');
151
+ this.error(`${target.daemons.length} active daemons — say which machine to report on:\n${choices}\n` +
152
+ `Pass one with --daemon, or see them all with \`${this.config.bin} list-daemons\`.`, { exit: 1 });
153
+ }
154
+ }
155
+ exports.default = HarnessesInstallers;
@@ -18,7 +18,68 @@ export type HarnessRow = {
18
18
  machineProduct?: string;
19
19
  source?: string;
20
20
  };
21
+ /**
22
+ * The name everyone else sees, when YOUR alias is covering it.
23
+ *
24
+ * `name` is the effective one. Two commands write a harness's name and mean
25
+ * different things — `harnesses alias` sets yours, `harnesses update --name`
26
+ * sets the shared one — and the server used to overwrite `name` with the
27
+ * alias, so the shared value was unobservable to the person who wrote it.
28
+ * Present only when an alias is actually shadowing something.
29
+ */
30
+ sharedName?: string;
31
+ aliased?: boolean;
32
+ /**
33
+ * A later registration of the same provider on the same machine replaced this
34
+ * row, so it will never come back online. The server derives it (one rule, in
35
+ * `MachineService`); this type must carry it or the column silently reports
36
+ * `offline` for a row `machines show` calls `superseded` — and those two ask
37
+ * the reader for opposite actions.
38
+ */
39
+ superseded?: boolean;
21
40
  };
41
+ /**
42
+ * Products skrr provisions and owns. A row that says it is one of these is
43
+ * cloud, whatever `harnessLocation` reads.
44
+ */
45
+ /**
46
+ * An 8-character machine label that is still a machine.
47
+ *
48
+ * `machineUuid` is only a bare uuid when the row's identity kind is
49
+ * `machine-uuid`. The other kinds carry a PREFIXED value —
50
+ * `dedicated-runtime:rtl_…`, `hosted-machine:…` — so a blind `.slice(0, 8)`
51
+ * printed the literal word `dedicate` for every dedicated runtime, identically,
52
+ * across machines that had nothing to do with each other. Keeping the tail is
53
+ * what makes the column distinguish anything: the prefix is the part every row
54
+ * of that kind shares.
55
+ */
56
+ export declare function shortMachineLabel(machineUuid?: string): string;
57
+ /**
58
+ * What to say when the table would be empty.
59
+ *
60
+ * A `--provider` filter that matched nothing is not evidence about the daemon.
61
+ * The old copy asked "Is your daemon connected?" regardless, which sent a user
62
+ * to debug a connection they had just listed six online rows from — and read
63
+ * identically for `--provider devin` (a real provider with no row here) and
64
+ * `--provider bogus-xyz` (a typo).
65
+ *
66
+ * The provider set is OPEN — a daemon registers a row per capability it
67
+ * detects — so a typo cannot honestly be rejected up front. But it can be told
68
+ * apart from an empty account, by naming the filter that did the excluding and
69
+ * pointing at the one command that shows which providers actually exist.
70
+ */
71
+ export declare function emptyListMessage(input: {
72
+ provider: string | null;
73
+ local: boolean;
74
+ }): string;
75
+ /**
76
+ * What the DEFAULT column says for one row.
77
+ *
78
+ * Names the SCOPE rather than printing a bare marker, because a workspace
79
+ * default suppresses a user one — so "mine is set and nothing changed" is a
80
+ * real state, and a column that only said "yes" could not explain it.
81
+ */
82
+ export declare function defaultMarker(harnessId: string, defaults: HarnessDefaults): string;
22
83
  export type HarnessSelection = {
23
84
  /** Rows to print, after every filter and the `--limit` slice. */
24
85
  visible: HarnessRow[];
@@ -96,9 +157,29 @@ export type HarnessesFilters = {
96
157
  limit: number | null;
97
158
  };
98
159
  /** The exact object `skrr harnesses list --json` prints. */
160
+ /**
161
+ * The default-harness block `GET /api/harnesses` already returns.
162
+ *
163
+ * `harnesses set-default` writes it and, until now, nothing in the CLI read it
164
+ * back: no column, no JSON field, and no `get-default` command. At
165
+ * `--scope workspace` — which the setter's own help says "affects every
166
+ * member" — that meant a user could change the default for everyone and then
167
+ * have no command that reports what it currently is.
168
+ *
169
+ * The scope matters as much as the id, because a workspace default SUPPRESSES a
170
+ * user one. Reporting only "which harness" would leave the more confusing case
171
+ * ("I set mine and it did not take effect") unexplained.
172
+ */
173
+ export type HarnessDefaults = {
174
+ effectiveDefaultHarnessId?: string | null;
175
+ defaultHarnessScope?: 'user' | 'workspace' | null;
176
+ userDefaultHarnessId?: string | null;
177
+ workspaceDefaultHarnessId?: string | null;
178
+ };
99
179
  export type HarnessesJson = {
100
180
  harnesses: HarnessRow[];
101
181
  filters: HarnessesFilters;
182
+ defaults: HarnessDefaults;
102
183
  counts: {
103
184
  /** `harnesses.length`. */
104
185
  returned: number;
@@ -132,7 +213,7 @@ export type HarnessesJson = {
132
213
  * (`compareHarnessRows`), not in server order. Round 1 introduced that sort;
133
214
  * it is deliberate, so `.harnesses[0]` is the row you almost always want.
134
215
  */
135
- export declare function buildHarnessesJson(selection: HarnessSelection, filters: HarnessesFilters): HarnessesJson;
216
+ export declare function buildHarnessesJson(selection: HarnessSelection, filters: HarnessesFilters, defaults?: HarnessDefaults): HarnessesJson;
136
217
  /**
137
218
  * Rows with the provider in its CANONICAL spelling.
138
219
  *
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.shortMachineLabel = shortMachineLabel;
4
+ exports.emptyListMessage = emptyListMessage;
5
+ exports.defaultMarker = defaultMarker;
3
6
  exports.isLocalHarnessRow = isLocalHarnessRow;
4
7
  exports.selectHarnessRows = selectHarnessRows;
5
8
  exports.buildHarnessesJson = buildHarnessesJson;
@@ -14,6 +17,67 @@ const harness_provider_input_1 = require("../../lib/harness-provider-input");
14
17
  * Products skrr provisions and owns. A row that says it is one of these is
15
18
  * cloud, whatever `harnessLocation` reads.
16
19
  */
20
+ /**
21
+ * An 8-character machine label that is still a machine.
22
+ *
23
+ * `machineUuid` is only a bare uuid when the row's identity kind is
24
+ * `machine-uuid`. The other kinds carry a PREFIXED value —
25
+ * `dedicated-runtime:rtl_…`, `hosted-machine:…` — so a blind `.slice(0, 8)`
26
+ * printed the literal word `dedicate` for every dedicated runtime, identically,
27
+ * across machines that had nothing to do with each other. Keeping the tail is
28
+ * what makes the column distinguish anything: the prefix is the part every row
29
+ * of that kind shares.
30
+ */
31
+ function shortMachineLabel(machineUuid) {
32
+ if (!machineUuid)
33
+ return '-';
34
+ const separator = machineUuid.indexOf(':');
35
+ const identifier = separator === -1 ? machineUuid : machineUuid.slice(separator + 1);
36
+ return identifier.slice(0, 8) || '-';
37
+ }
38
+ /**
39
+ * What to say when the table would be empty.
40
+ *
41
+ * A `--provider` filter that matched nothing is not evidence about the daemon.
42
+ * The old copy asked "Is your daemon connected?" regardless, which sent a user
43
+ * to debug a connection they had just listed six online rows from — and read
44
+ * identically for `--provider devin` (a real provider with no row here) and
45
+ * `--provider bogus-xyz` (a typo).
46
+ *
47
+ * The provider set is OPEN — a daemon registers a row per capability it
48
+ * detects — so a typo cannot honestly be rejected up front. But it can be told
49
+ * apart from an empty account, by naming the filter that did the excluding and
50
+ * pointing at the one command that shows which providers actually exist.
51
+ */
52
+ function emptyListMessage(input) {
53
+ const hiddenByDefault = input.local
54
+ ? ' Offline rows are hidden by default (--all shows them) and hosted rows are excluded (--no-local includes them).'
55
+ : '';
56
+ if (input.provider) {
57
+ return (`No harnesses with provider "${input.provider}".${hiddenByDefault}` +
58
+ ' Run `skrr harnesses list --all` without --provider to see which providers this account has registered.');
59
+ }
60
+ return input.local
61
+ ? `No harnesses found. Is your daemon connected?${hiddenByDefault}`
62
+ : 'No harnesses found.';
63
+ }
64
+ /**
65
+ * What the DEFAULT column says for one row.
66
+ *
67
+ * Names the SCOPE rather than printing a bare marker, because a workspace
68
+ * default suppresses a user one — so "mine is set and nothing changed" is a
69
+ * real state, and a column that only said "yes" could not explain it.
70
+ */
71
+ function defaultMarker(harnessId, defaults) {
72
+ if (defaults.effectiveDefaultHarnessId === harnessId) {
73
+ return defaults.defaultHarnessScope === 'workspace' ? 'workspace' : 'you';
74
+ }
75
+ // Set by this user, but a workspace default is winning. Saying so here is the
76
+ // only place a user finds out why their own choice is not in effect.
77
+ if (defaults.userDefaultHarnessId === harnessId)
78
+ return 'you (overridden)';
79
+ return '-';
80
+ }
17
81
  const MANAGED_MACHINE_PRODUCTS = new Set(['hosted-machine', 'hosted-job-machine']);
18
82
  const MANAGED_REGISTRATION_SOURCES = new Set([
19
83
  'hosted-machine',
@@ -114,10 +178,16 @@ function selectHarnessRows(rows, options) {
114
178
  * (`compareHarnessRows`), not in server order. Round 1 introduced that sort;
115
179
  * it is deliberate, so `.harnesses[0]` is the row you almost always want.
116
180
  */
117
- function buildHarnessesJson(selection, filters) {
181
+ function buildHarnessesJson(selection, filters, defaults = {}) {
118
182
  return {
119
183
  harnesses: selection.visible,
120
184
  filters,
185
+ defaults: {
186
+ effectiveDefaultHarnessId: defaults.effectiveDefaultHarnessId ?? null,
187
+ defaultHarnessScope: defaults.defaultHarnessScope ?? null,
188
+ userDefaultHarnessId: defaults.userDefaultHarnessId ?? null,
189
+ workspaceDefaultHarnessId: defaults.workspaceDefaultHarnessId ?? null,
190
+ },
121
191
  counts: {
122
192
  returned: selection.visible.length,
123
193
  matched: selection.matched,
@@ -167,7 +237,13 @@ function lastSeenMs(row) {
167
237
  * previously undiscoverable.
168
238
  */
169
239
  class HarnessesList extends base_command_1.BaseCommand {
170
- static description = `List your harnesses — your local Claude Code, Codex, ${auth_core_1.FIRST_PARTY_HARNESS.displayName}, one row per (machine, provider). The id here is what \`agents attach-harness\` pins. Online only unless --all.`;
240
+ // Deliberately NOT an enumeration. The provider set is open — a daemon
241
+ // registers a row per capability it detects, so this account carries `hermes`
242
+ // and `gemini` rows that appear in no hand-written list in this package, while
243
+ // every list that was written omitted `devin` and `goose`. A subset printed as
244
+ // if it were the set is the drift root `CLAUDE.md` names: "a second
245
+ // declaration is how a surface silently drifts".
246
+ static description = 'List your harnesses — your local coding agents, one row per (machine, provider). The id here is what `agents attach-harness` pins. Online only unless --all.';
171
247
  static examples = [
172
248
  '<%= config.bin %> harnesses list',
173
249
  '<%= config.bin %> harnesses list --provider claude',
@@ -190,7 +266,12 @@ class HarnessesList extends base_command_1.BaseCommand {
190
266
  exclusive: ['status', 'online'],
191
267
  }),
192
268
  provider: core_1.Flags.string({
193
- description: `Filter by provider (e.g. claude, codex, opencode, openclaw, ${auth_core_1.FIRST_PARTY_HARNESS.provider}).`,
269
+ // Two examples, not a set. One of them is the first-party harness under
270
+ // its CANONICAL spelling, which `harness-provider-input.spec.ts` pins so
271
+ // help can never teach a retired one — but the old text read as an
272
+ // enumeration and omitted `devin`, `goose`, `hermes` and `gemini`, all of
273
+ // which are real values of this flag.
274
+ description: `Filter by provider, e.g. claude or ${auth_core_1.FIRST_PARTY_HARNESS.provider}. The PROVIDER column lists what this machine actually registered; it is wider than these examples.`,
194
275
  }),
195
276
  daemon: core_1.Flags.string({
196
277
  description: 'Filter by daemon id, matching any substring — e.g. the machine hash in daemon_<hash>_<source>_<profile>.',
@@ -222,9 +303,7 @@ class HarnessesList extends base_command_1.BaseCommand {
222
303
  const query = params.toString() ? `?${params.toString()}` : '';
223
304
  let res;
224
305
  try {
225
- res = await (0, api_fetch_1.apiFetch)(`/api/harnesses${query}`, {
226
- credential: this.resolvedCredential,
227
- });
306
+ res = await (0, api_fetch_1.apiFetch)(`/api/harnesses${query}`, { credential: this.resolvedCredential });
228
307
  }
229
308
  catch (err) {
230
309
  this.handleApiError(err);
@@ -242,7 +321,7 @@ class HarnessesList extends base_command_1.BaseCommand {
242
321
  provider: provider ?? null,
243
322
  daemon: flags.daemon ?? null,
244
323
  limit: flags.limit ?? null,
245
- });
324
+ }, res);
246
325
  this.log(JSON.stringify(payload, null, 2));
247
326
  return;
248
327
  }
@@ -263,21 +342,27 @@ class HarnessesList extends base_command_1.BaseCommand {
263
342
  this.log(`No harnesses match the default filters. Hidden: ${hidden.join(', ')}.`);
264
343
  return;
265
344
  }
266
- this.log(flags.local
267
- ? 'No harnesses found. Is your daemon connected? Offline rows are hidden by default (--all shows them) and hosted rows are excluded (--no-local includes them).'
268
- : 'No harnesses found.');
345
+ this.log(emptyListMessage({ provider: provider ?? null, local: flags.local }));
269
346
  return;
270
347
  }
271
348
  const tableRows = selection.visible.map((r) => ({
272
349
  id: (0, format_1.formatCell)(r._id),
273
350
  name: (0, format_1.formatCell)(r.name),
274
351
  provider: (0, format_1.formatCell)(r.provider),
275
- status: (0, format_1.formatCell)(r.status),
352
+ status: r.superseded ? 'superseded' : (0, format_1.formatCell)(r.status),
353
+ default: defaultMarker(r._id, res),
276
354
  daemon: (0, format_1.formatCell)(r.daemonId),
277
355
  model: (0, format_1.formatCell)(r.model),
278
- machine: r.machineUuid ? r.machineUuid.slice(0, 8) : '-',
356
+ machine: shortMachineLabel(r.machineUuid),
279
357
  seen: (0, format_1.localStamp)(r.lastSeenAt),
280
358
  }));
359
+ // `machines list` reports a fraction like `6/7` whose denominator counts
360
+ // LIVE rows — every row minus the ones a later registration replaced. From
361
+ // this table that number was underivable: ~60 rows printed for one machine
362
+ // and nothing on screen added up to 7, with the reconciling sentence living
363
+ // only inside a third command (`machines show`). Now that the server labels
364
+ // supersession here too, say it where the rows are.
365
+ const supersededShown = selection.visible.filter((r) => r.superseded).length;
281
366
  (0, format_1.renderTable)(tableRows, [
282
367
  { key: 'id', header: 'HARNESS ID' },
283
368
  // `harnesses alias` exists to make a row recognisable in a LIST. Without
@@ -286,11 +371,18 @@ class HarnessesList extends base_command_1.BaseCommand {
286
371
  { key: 'name', header: 'NAME', maxWidth: 22 },
287
372
  { key: 'provider', header: 'PROVIDER' },
288
373
  { key: 'status', header: 'STATUS' },
374
+ // `set-default` had no reader anywhere in the CLI. This is it.
375
+ { key: 'default', header: 'DEFAULT' },
289
376
  { key: 'daemon', header: 'DAEMON ID', verbatim: true },
290
377
  { key: 'model', header: 'MODEL', maxWidth: 28 },
291
378
  { key: 'machine', header: 'MACHINE' },
292
379
  { key: 'seen', header: `LAST SEEN (${(0, format_1.localZone)()})` },
293
380
  ], (line) => this.log(line));
381
+ if (supersededShown > 0) {
382
+ // This is the sentence that makes `machines list`'s fraction derivable
383
+ // from here: live = printed − superseded.
384
+ this.log(`(${supersededShown} of ${selection.visible.length} shown are superseded — a later registration on the same machine replaced them, so they will not come back. ${selection.visible.length - supersededShown} live.)`);
385
+ }
294
386
  if (selection.hiddenByLocal > 0) {
295
387
  // Same reason as the JSON `counts` block: the `--local` default is the
296
388
  // single biggest source of "the list is shorter than I expected".