@skrr-ai/cli 0.1.40 → 0.1.41
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.
- package/dist/base-command.d.ts +9 -0
- package/dist/base-command.js +18 -3
- package/dist/commands/agent-shares/list-for.js +4 -11
- package/dist/commands/agent-shares/received.js +4 -11
- package/dist/commands/agent-shares/sent.js +4 -11
- package/dist/commands/agent-shares/with.js +3 -10
- package/dist/commands/agents/activity.js +1 -1
- package/dist/commands/agents/api-actions/add.js +6 -0
- package/dist/commands/agents/chat.js +6 -1
- package/dist/commands/agents/create.js +6 -1
- package/dist/commands/agents/manager/ensure.d.ts +18 -0
- package/dist/commands/agents/manager/ensure.js +56 -0
- package/dist/commands/agents/show.js +4 -0
- package/dist/commands/agents/tools/auth.d.ts +6 -0
- package/dist/commands/agents/tools/auth.js +27 -1
- package/dist/commands/agents/tools/index.js +11 -1
- package/dist/commands/agents/update.js +6 -1
- package/dist/commands/code/install.d.ts +4 -2
- package/dist/commands/code/install.js +12 -10
- package/dist/commands/convos/list.js +1 -3
- package/dist/commands/convos/show.js +4 -11
- package/dist/commands/daemon/index.js +3 -3
- package/dist/commands/daemon/install.js +4 -4
- package/dist/commands/daemon/login.js +1 -1
- package/dist/commands/daemon/logs.d.ts +3 -1
- package/dist/commands/daemon/logs.js +16 -1
- package/dist/commands/daemon/start.js +3 -3
- package/dist/commands/daemon/status.d.ts +9 -0
- package/dist/commands/daemon/status.js +22 -0
- package/dist/commands/dm/messages.js +1 -11
- package/dist/commands/followups/cancel.js +1 -1
- package/dist/commands/followups/resolve.js +1 -1
- package/dist/commands/followups/show.js +16 -0
- package/dist/commands/harnesses/forget.js +20 -3
- package/dist/commands/harnesses/install.js +18 -6
- package/dist/commands/harnesses/installers.d.ts +1 -1
- package/dist/commands/harnesses/installers.js +36 -12
- package/dist/commands/harnesses/list.d.ts +74 -2
- package/dist/commands/harnesses/list.js +231 -15
- package/dist/commands/harnesses/ping.d.ts +2 -0
- package/dist/commands/harnesses/ping.js +21 -0
- package/dist/commands/harnesses/show.d.ts +22 -0
- package/dist/commands/harnesses/show.js +116 -17
- package/dist/commands/harnesses/update.d.ts +8 -0
- package/dist/commands/harnesses/update.js +25 -1
- package/dist/commands/harnesses/usage.d.ts +2 -0
- package/dist/commands/harnesses/usage.js +7 -2
- package/dist/commands/instructions/status.js +13 -0
- package/dist/commands/instructions/update-target.d.ts +1 -0
- package/dist/commands/instructions/update-target.js +11 -3
- package/dist/commands/login.js +5 -5
- package/dist/commands/machines/list.js +8 -9
- package/dist/commands/machines/show.js +46 -17
- package/dist/commands/messages/show.js +3 -10
- package/dist/commands/shares/get.js +2 -9
- package/dist/commands/shares/list.js +1 -9
- package/dist/commands/shortcuts/show.js +4 -11
- package/dist/commands/skills/local.js +6 -0
- package/dist/commands/spaces/actions/run.js +3 -4
- package/dist/commands/spaces/briefs/get.js +2 -9
- package/dist/commands/spaces/briefs/list.js +1 -9
- package/dist/commands/spaces/conversations.js +1 -9
- package/dist/commands/spaces/heartbeats/list.js +1 -9
- package/dist/commands/spaces/heartbeats/status.js +2 -9
- package/dist/commands/tasks/activity.js +1 -10
- package/dist/commands/tasks/approvals/list.js +1 -9
- package/dist/commands/tasks/attachments/list.js +1 -9
- package/dist/commands/tasks/comments/list.js +1 -9
- package/dist/commands/tasks/dri-status.js +2 -9
- package/dist/commands/tasks/events/append.js +9 -7
- package/dist/commands/tasks/execution-timeline.js +1 -9
- package/dist/commands/tasks/monitor.js +1 -9
- package/dist/commands/tasks/result/submit.js +9 -0
- package/dist/commands/tasks/runs.js +1 -9
- package/dist/commands/tasks/show.js +5 -12
- package/dist/commands/token/list.d.ts +8 -0
- package/dist/commands/token/list.js +51 -6
- package/dist/help.js +8 -12
- package/dist/lib/agent-home-workspace.d.ts +19 -0
- package/dist/lib/agent-home-workspace.js +39 -0
- package/dist/lib/agent-resolver.js +2 -2
- package/dist/lib/agentic-stream.d.ts +22 -1
- package/dist/lib/agentic-stream.js +48 -7
- package/dist/lib/cli-installers.d.ts +84 -2
- package/dist/lib/cli-installers.js +146 -13
- package/dist/lib/command-miss.d.ts +22 -0
- package/dist/lib/command-miss.js +78 -0
- package/dist/lib/daemon-setup.js +2 -2
- package/dist/lib/daemon-target.d.ts +14 -0
- package/dist/lib/daemon-target.js +32 -0
- package/dist/lib/daemonBroker.js +1 -1
- package/dist/lib/daemonHandoff.js +1 -1
- package/dist/lib/exec-runtime-binary.js +2 -2
- package/dist/lib/followups.d.ts +10 -0
- package/dist/lib/followups.js +14 -1
- package/dist/lib/format.d.ts +11 -0
- package/dist/lib/format.js +21 -0
- package/dist/lib/harness-labels.d.ts +39 -0
- package/dist/lib/harness-labels.js +60 -0
- package/dist/lib/harnesses.d.ts +6 -0
- package/dist/lib/instruction-input.d.ts +13 -0
- package/dist/lib/instruction-input.js +20 -1
- package/dist/lib/local-skills.d.ts +17 -1
- package/dist/lib/local-skills.js +20 -2
- package/dist/lib/login.js +5 -5
- package/dist/lib/machines.d.ts +6 -0
- package/dist/lib/machines.js +68 -2
- package/dist/lib/runtime-tool-support.d.ts +34 -0
- package/dist/lib/runtime-tool-support.js +77 -0
- package/dist/lib/share-rows.d.ts +17 -0
- package/dist/lib/share-rows.js +35 -0
- package/dist/node_modules/@skrr-ai/data-provider/index.js +5029 -5007
- package/oclif.manifest.json +2689 -2584
- package/package.json +6 -3
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HARNESS_STATUS_FILTERS = exports.ALIAS_MARKER = void 0;
|
|
3
4
|
exports.shortMachineLabel = shortMachineLabel;
|
|
4
5
|
exports.emptyListMessage = emptyListMessage;
|
|
6
|
+
exports.harnessNameCell = harnessNameCell;
|
|
5
7
|
exports.defaultMarker = defaultMarker;
|
|
8
|
+
exports.serverStatusParam = serverStatusParam;
|
|
6
9
|
exports.isLocalHarnessRow = isLocalHarnessRow;
|
|
7
10
|
exports.selectHarnessRows = selectHarnessRows;
|
|
8
11
|
exports.buildHarnessesJson = buildHarnessesJson;
|
|
12
|
+
exports.readinessFootnotes = readinessFootnotes;
|
|
9
13
|
exports.canonicalHarnessRows = canonicalHarnessRows;
|
|
10
14
|
const core_1 = require("@oclif/core");
|
|
11
15
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
12
16
|
const base_command_1 = require("../../base-command");
|
|
13
17
|
const api_fetch_1 = require("../../lib/api-fetch");
|
|
18
|
+
const cli_installers_1 = require("../../lib/cli-installers");
|
|
14
19
|
const format_1 = require("../../lib/format");
|
|
15
20
|
const harness_provider_input_1 = require("../../lib/harness-provider-input");
|
|
21
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
22
|
+
const harness_labels_1 = require("../../lib/harness-labels");
|
|
16
23
|
/**
|
|
17
24
|
* Products skrr provisions and owns. A row that says it is one of these is
|
|
18
25
|
* cloud, whatever `harnessLocation` reads.
|
|
@@ -50,17 +57,48 @@ function shortMachineLabel(machineUuid) {
|
|
|
50
57
|
* pointing at the one command that shows which providers actually exist.
|
|
51
58
|
*/
|
|
52
59
|
function emptyListMessage(input) {
|
|
60
|
+
if (input.provider) {
|
|
61
|
+
const known = input.accountProviders;
|
|
62
|
+
// A provider that appears on no row of the account — a typo, or a harness
|
|
63
|
+
// never registered — is a different fact from one whose rows are merely
|
|
64
|
+
// hidden by a filter. Both used to print the same sentence (OSK-9285).
|
|
65
|
+
if (known && !known.includes(input.provider)) {
|
|
66
|
+
return known.length > 0
|
|
67
|
+
? `No harness on this account has provider "${input.provider}". Providers here: ${known.join(', ')}.`
|
|
68
|
+
: `No harness on this account has provider "${input.provider}", and the account has no harnesses yet.`;
|
|
69
|
+
}
|
|
70
|
+
const lifts = [];
|
|
71
|
+
if (!input.all)
|
|
72
|
+
lifts.push('offline rows are hidden by default (--all shows them)');
|
|
73
|
+
if (input.local)
|
|
74
|
+
lifts.push('hosted rows are excluded (--no-local includes them)');
|
|
75
|
+
const liftText = lifts.join('; ');
|
|
76
|
+
return (`No harnesses with provider "${input.provider}" match these filters.` +
|
|
77
|
+
(liftText ? ` ${liftText.charAt(0).toUpperCase()}${liftText.slice(1)}.` : '') +
|
|
78
|
+
(known
|
|
79
|
+
? ''
|
|
80
|
+
: ' Run `skrr harnesses list --all` without --provider to see which providers this account has registered.'));
|
|
81
|
+
}
|
|
53
82
|
const hiddenByDefault = input.local
|
|
54
83
|
? ' Offline rows are hidden by default (--all shows them) and hosted rows are excluded (--no-local includes them).'
|
|
55
84
|
: '';
|
|
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
85
|
return input.local
|
|
61
86
|
? `No harnesses found. Is your daemon connected?${hiddenByDefault}`
|
|
62
87
|
: 'No harnesses found.';
|
|
63
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* The NAME cell. `show` and `--json` said when YOUR alias covers the shared
|
|
91
|
+
* name; this table printed the alias as if it were the name everyone sees
|
|
92
|
+
* (OSK-10004). The marker leads because the column truncates from the right,
|
|
93
|
+
* and a trailing one is the first thing a long name would cut.
|
|
94
|
+
*/
|
|
95
|
+
exports.ALIAS_MARKER = '*';
|
|
96
|
+
function harnessNameCell(row) {
|
|
97
|
+
// The same rule the web sidebar renders (OSK-9290): a name stored under an
|
|
98
|
+
// older spelling or `oversky …` name is not a second product.
|
|
99
|
+
const name = row.name ? (0, data_provider_1.canonicalizeHarnessLabel)(row.name) : (0, format_1.formatCell)(row.name);
|
|
100
|
+
return (0, harness_labels_1.isAliasCoveringSharedName)(row) ? `${exports.ALIAS_MARKER} ${name}` : name;
|
|
101
|
+
}
|
|
64
102
|
/**
|
|
65
103
|
* What the DEFAULT column says for one row.
|
|
66
104
|
*
|
|
@@ -84,6 +122,26 @@ const MANAGED_REGISTRATION_SOURCES = new Set([
|
|
|
84
122
|
'hosted-job-machine',
|
|
85
123
|
'cloud-coding',
|
|
86
124
|
]);
|
|
125
|
+
/**
|
|
126
|
+
* What `--status` accepts: exactly what the STATUS column prints (OSK-10005).
|
|
127
|
+
*
|
|
128
|
+
* The column says `superseded` for a row a later registration on the same
|
|
129
|
+
* machine replaced; the filter accepted only online|offline, so the one value
|
|
130
|
+
* that says "never coming back" could be read and never selected.
|
|
131
|
+
*/
|
|
132
|
+
exports.HARNESS_STATUS_FILTERS = ['online', 'offline', 'superseded'];
|
|
133
|
+
/**
|
|
134
|
+
* The status to ask the SERVER for. Only `online` narrows there.
|
|
135
|
+
*
|
|
136
|
+
* Supersession is judged among the rows in one response — a row is superseded
|
|
137
|
+
* when a newer row for the same provider on the same machine exists. Asked for
|
|
138
|
+
* `offline` only, the server cannot see the live row that replaced an offline
|
|
139
|
+
* one, so it labelled that row `offline` while `--all` called it `superseded`.
|
|
140
|
+
* Online rows are never superseded, so that one filter is safe to push down.
|
|
141
|
+
*/
|
|
142
|
+
function serverStatusParam(status) {
|
|
143
|
+
return status === 'online' ? 'online' : undefined;
|
|
144
|
+
}
|
|
87
145
|
/**
|
|
88
146
|
* Harness rows created before `harnessLocation` was introduced do not carry that
|
|
89
147
|
* field. A daemon id is the durable discriminator for those legacy rows:
|
|
@@ -155,6 +213,16 @@ function selectHarnessRows(rows, options) {
|
|
|
155
213
|
const needle = options.daemon.toLowerCase();
|
|
156
214
|
filtered = filtered.filter((r) => (r.daemonId ?? '').toLowerCase().includes(needle));
|
|
157
215
|
}
|
|
216
|
+
let hiddenSuperseded = 0;
|
|
217
|
+
if (options.status) {
|
|
218
|
+
const wanted = options.status;
|
|
219
|
+
filtered = filtered.filter((r) => {
|
|
220
|
+
const label = (0, harness_labels_1.harnessStatusLabel)(r);
|
|
221
|
+
if (label !== wanted && label === 'superseded')
|
|
222
|
+
hiddenSuperseded += 1;
|
|
223
|
+
return label === wanted;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
158
226
|
const sorted = [...filtered].sort(compareHarnessRows);
|
|
159
227
|
const shown = options.onlineOnly ? sorted.filter((r) => r.status === 'online') : sorted;
|
|
160
228
|
const limited = options.limit != null && options.limit > 0 ? shown.slice(0, options.limit) : shown;
|
|
@@ -163,6 +231,7 @@ function selectHarnessRows(rows, options) {
|
|
|
163
231
|
hiddenByLocal,
|
|
164
232
|
hiddenOffline: sorted.length - shown.length,
|
|
165
233
|
hiddenByLimit: shown.length - limited.length,
|
|
234
|
+
hiddenSuperseded,
|
|
166
235
|
matched: sorted.length,
|
|
167
236
|
};
|
|
168
237
|
}
|
|
@@ -178,9 +247,13 @@ function selectHarnessRows(rows, options) {
|
|
|
178
247
|
* (`compareHarnessRows`), not in server order. Round 1 introduced that sort;
|
|
179
248
|
* it is deliberate, so `.harnesses[0]` is the row you almost always want.
|
|
180
249
|
*/
|
|
181
|
-
function buildHarnessesJson(selection, filters, defaults = {}) {
|
|
250
|
+
function buildHarnessesJson(selection, filters, defaults = {}, readiness) {
|
|
182
251
|
return {
|
|
183
|
-
|
|
252
|
+
// `readiness` is the per-provider answer the READINESS column prints —
|
|
253
|
+
// `null` where no daemon answered for that row, never a guess (OSK-10100).
|
|
254
|
+
harnesses: readiness
|
|
255
|
+
? selection.visible.map((row) => ({ ...row, readiness: readiness(row) ?? null }))
|
|
256
|
+
: selection.visible,
|
|
184
257
|
filters,
|
|
185
258
|
defaults: {
|
|
186
259
|
effectiveDefaultHarnessId: defaults.effectiveDefaultHarnessId ?? null,
|
|
@@ -193,10 +266,61 @@ function buildHarnessesJson(selection, filters, defaults = {}) {
|
|
|
193
266
|
matched: selection.matched,
|
|
194
267
|
hiddenByLocal: selection.hiddenByLocal,
|
|
195
268
|
hiddenByLimit: selection.hiddenByLimit,
|
|
269
|
+
hiddenSuperseded: selection.hiddenSuperseded,
|
|
196
270
|
online: selection.visible.filter((r) => r.status === 'online').length,
|
|
197
271
|
},
|
|
198
272
|
};
|
|
199
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* What the READINESS column's non-answers mean, counted from the rows shown.
|
|
276
|
+
*
|
|
277
|
+
* Each footnote names a different next step, so they are not merged: a `-` on
|
|
278
|
+
* an offline row wants `--all`/a started daemon, a `-` on a provider the daemon
|
|
279
|
+
* does not manage (hermes, gemini) wants nothing, `unknown` is installed but
|
|
280
|
+
* unverifiable, and only a definite not-ready label (interactive-required,
|
|
281
|
+
* not-installed, …) has something to fix. Counting `unknown` as not ready
|
|
282
|
+
* would send the reader to fix eleven rows that are fine as far as anyone can
|
|
283
|
+
* tell.
|
|
284
|
+
*/
|
|
285
|
+
function readinessFootnotes(rows, readiness) {
|
|
286
|
+
const lines = [];
|
|
287
|
+
let notLive = 0;
|
|
288
|
+
let unmanaged = 0;
|
|
289
|
+
let noAnswer = 0;
|
|
290
|
+
let unknown = 0;
|
|
291
|
+
let notReady = 0;
|
|
292
|
+
for (const row of rows) {
|
|
293
|
+
const verdict = readiness.lookup(row);
|
|
294
|
+
if (verdict) {
|
|
295
|
+
if (verdict.label === 'unknown')
|
|
296
|
+
unknown += 1;
|
|
297
|
+
else if (!verdict.ok)
|
|
298
|
+
notReady += 1;
|
|
299
|
+
}
|
|
300
|
+
else if (row.status !== 'online' || row.superseded)
|
|
301
|
+
notLive += 1;
|
|
302
|
+
else if (readiness.daemonAnswered(row))
|
|
303
|
+
unmanaged += 1;
|
|
304
|
+
else
|
|
305
|
+
noAnswer += 1;
|
|
306
|
+
}
|
|
307
|
+
const dash = [];
|
|
308
|
+
if (notLive > 0)
|
|
309
|
+
dash.push(`${notLive} offline or superseded`);
|
|
310
|
+
if (unmanaged > 0)
|
|
311
|
+
dash.push(`${unmanaged} a provider its daemon does not report on`);
|
|
312
|
+
if (noAnswer > 0)
|
|
313
|
+
dash.push(`${noAnswer} whose daemon did not answer in time`);
|
|
314
|
+
if (dash.length > 0)
|
|
315
|
+
lines.push(`(READINESS -: ${dash.join('; ')}.)`);
|
|
316
|
+
if (unknown > 0) {
|
|
317
|
+
lines.push(`(READINESS unknown: ${unknown} installed, but the daemon cannot read that provider's sign-in state.)`);
|
|
318
|
+
}
|
|
319
|
+
if (notReady > 0) {
|
|
320
|
+
lines.push(`(${notReady} not ready — \`skrr harnesses show <id>\` says what each needs.)`);
|
|
321
|
+
}
|
|
322
|
+
return lines;
|
|
323
|
+
}
|
|
200
324
|
/**
|
|
201
325
|
* Rows with the provider in its CANONICAL spelling.
|
|
202
326
|
*
|
|
@@ -253,8 +377,8 @@ class HarnessesList extends base_command_1.BaseCommand {
|
|
|
253
377
|
static flags = {
|
|
254
378
|
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
255
379
|
status: core_1.Flags.string({
|
|
256
|
-
description: 'Filter by
|
|
257
|
-
options: [
|
|
380
|
+
description: 'Filter by the STATUS column. `offline` leaves out superseded rows — a later registration on the same machine replaced them — and `superseded` lists only those.',
|
|
381
|
+
options: [...exports.HARNESS_STATUS_FILTERS],
|
|
258
382
|
exclusive: ['online', 'all'],
|
|
259
383
|
}),
|
|
260
384
|
online: core_1.Flags.boolean({
|
|
@@ -284,6 +408,11 @@ class HarnessesList extends base_command_1.BaseCommand {
|
|
|
284
408
|
default: true,
|
|
285
409
|
allowNo: true,
|
|
286
410
|
}),
|
|
411
|
+
readiness: core_1.Flags.boolean({
|
|
412
|
+
description: 'Ask each connected daemon whether its coding-agent CLIs are installed and signed in (the READINESS column; `readiness` in --json). One request per daemon. On by default — pass --no-readiness to skip it.',
|
|
413
|
+
default: true,
|
|
414
|
+
allowNo: true,
|
|
415
|
+
}),
|
|
287
416
|
};
|
|
288
417
|
async run() {
|
|
289
418
|
this.requireAuth();
|
|
@@ -296,8 +425,9 @@ class HarnessesList extends base_command_1.BaseCommand {
|
|
|
296
425
|
// asked for the canonical one, which it matches against every stored spelling.
|
|
297
426
|
const provider = flags.provider ? (0, harness_provider_input_1.canonicalHarnessInput)(flags.provider) : undefined;
|
|
298
427
|
const params = new URLSearchParams();
|
|
299
|
-
|
|
300
|
-
|
|
428
|
+
const serverStatus = serverStatusParam(status);
|
|
429
|
+
if (serverStatus)
|
|
430
|
+
params.set('status', serverStatus);
|
|
301
431
|
if (provider)
|
|
302
432
|
params.set('provider', provider);
|
|
303
433
|
const query = params.toString() ? `?${params.toString()}` : '';
|
|
@@ -312,16 +442,36 @@ class HarnessesList extends base_command_1.BaseCommand {
|
|
|
312
442
|
local: flags.local,
|
|
313
443
|
daemon: flags.daemon,
|
|
314
444
|
limit: flags.limit,
|
|
445
|
+
status,
|
|
315
446
|
onlineOnly: false,
|
|
316
447
|
});
|
|
448
|
+
// STATUS is the daemon's socket, not the coding agent: a Devin that needs
|
|
449
|
+
// sign-in read `online` exactly like a ready one, and `list` is where a
|
|
450
|
+
// user picks a harness (OSK-10100). One read per connected daemon, bounded;
|
|
451
|
+
// a daemon that cannot answer leaves its rows unknown rather than failing
|
|
452
|
+
// the list.
|
|
453
|
+
const readiness = flags.readiness
|
|
454
|
+
? await (0, cli_installers_1.readinessForRows)(selection.visible, this.resolvedCredential)
|
|
455
|
+
: null;
|
|
317
456
|
if (flags.json) {
|
|
457
|
+
// A provider no row on the account has is a typo (or a harness never
|
|
458
|
+
// registered), and a script that got an empty list and exit 0 could not
|
|
459
|
+
// tell it from a filter that hid real rows — the silent failure OSK-9285
|
|
460
|
+
// describes. Refused exactly as the table refuses it; a real provider
|
|
461
|
+
// whose rows a filter hid still gets its empty list.
|
|
462
|
+
if (provider && selection.visible.length === 0) {
|
|
463
|
+
const accountProviders = await this.accountProviders();
|
|
464
|
+
if (accountProviders && !accountProviders.includes(provider)) {
|
|
465
|
+
this.error(emptyListMessage({ provider, local: flags.local, all: flags.all, accountProviders }), { exit: 1 });
|
|
466
|
+
}
|
|
467
|
+
}
|
|
318
468
|
const payload = buildHarnessesJson(selection, {
|
|
319
469
|
local: flags.local,
|
|
320
470
|
status: status ?? null,
|
|
321
471
|
provider: provider ?? null,
|
|
322
472
|
daemon: flags.daemon ?? null,
|
|
323
473
|
limit: flags.limit ?? null,
|
|
324
|
-
}, res);
|
|
474
|
+
}, res, readiness ? readiness.lookup : () => undefined);
|
|
325
475
|
this.log(JSON.stringify(payload, null, 2));
|
|
326
476
|
return;
|
|
327
477
|
}
|
|
@@ -338,18 +488,39 @@ class HarnessesList extends base_command_1.BaseCommand {
|
|
|
338
488
|
if (selection.hiddenByLocal > 0) {
|
|
339
489
|
hidden.push(`${selection.hiddenByLocal} hosted (--no-local)`);
|
|
340
490
|
}
|
|
491
|
+
if (selection.hiddenSuperseded > 0) {
|
|
492
|
+
hidden.push(`${selection.hiddenSuperseded} superseded (--status superseded)`);
|
|
493
|
+
}
|
|
341
494
|
if (hidden.length > 0) {
|
|
342
495
|
this.log(`No harnesses match the default filters. Hidden: ${hidden.join(', ')}.`);
|
|
343
496
|
return;
|
|
344
497
|
}
|
|
345
|
-
|
|
498
|
+
if (!provider) {
|
|
499
|
+
this.log(emptyListMessage({ provider: null, local: flags.local, all: flags.all }));
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const accountProviders = await this.accountProviders();
|
|
503
|
+
const message = emptyListMessage({
|
|
504
|
+
provider,
|
|
505
|
+
local: flags.local,
|
|
506
|
+
all: flags.all,
|
|
507
|
+
accountProviders,
|
|
508
|
+
});
|
|
509
|
+
if (accountProviders && !accountProviders.includes(provider)) {
|
|
510
|
+
// Nothing on the account answers to this name: exit non-zero so a script
|
|
511
|
+
// cannot mistake a typo for an empty-but-valid filter. `--json` refuses it
|
|
512
|
+
// the same way (above).
|
|
513
|
+
this.error(message, { exit: 1 });
|
|
514
|
+
}
|
|
515
|
+
this.log(message);
|
|
346
516
|
return;
|
|
347
517
|
}
|
|
348
518
|
const tableRows = selection.visible.map((r) => ({
|
|
349
519
|
id: (0, format_1.formatCell)(r._id),
|
|
350
|
-
name: (
|
|
351
|
-
provider: (0,
|
|
352
|
-
status:
|
|
520
|
+
name: harnessNameCell(r),
|
|
521
|
+
provider: (0, harness_labels_1.harnessProviderLabel)(r.provider),
|
|
522
|
+
status: (0, harness_labels_1.harnessStatusLabel)(r),
|
|
523
|
+
readiness: readiness?.lookup(r)?.label ?? '-',
|
|
353
524
|
default: defaultMarker(r._id, res),
|
|
354
525
|
daemon: (0, format_1.formatCell)(r.daemonId),
|
|
355
526
|
model: (0, format_1.formatCell)(r.model),
|
|
@@ -363,6 +534,7 @@ class HarnessesList extends base_command_1.BaseCommand {
|
|
|
363
534
|
// only inside a third command (`machines show`). Now that the server labels
|
|
364
535
|
// supersession here too, say it where the rows are.
|
|
365
536
|
const supersededShown = selection.visible.filter((r) => r.superseded).length;
|
|
537
|
+
const aliasedShown = selection.visible.filter((r) => (0, harness_labels_1.isAliasCoveringSharedName)(r)).length;
|
|
366
538
|
(0, format_1.renderTable)(tableRows, [
|
|
367
539
|
{ key: 'id', header: 'HARNESS ID' },
|
|
368
540
|
// `harnesses alias` exists to make a row recognisable in a LIST. Without
|
|
@@ -371,6 +543,11 @@ class HarnessesList extends base_command_1.BaseCommand {
|
|
|
371
543
|
{ key: 'name', header: 'NAME', maxWidth: 22 },
|
|
372
544
|
{ key: 'provider', header: 'PROVIDER' },
|
|
373
545
|
{ key: 'status', header: 'STATUS' },
|
|
546
|
+
// Whether the coding agent itself can run — installed, signed in — as
|
|
547
|
+
// opposed to STATUS, which is only whether its daemon is connected.
|
|
548
|
+
...(readiness && readiness.answered > 0
|
|
549
|
+
? [{ key: 'readiness', header: 'READINESS' }]
|
|
550
|
+
: []),
|
|
374
551
|
// `set-default` had no reader anywhere in the CLI. This is it.
|
|
375
552
|
{ key: 'default', header: 'DEFAULT' },
|
|
376
553
|
{ key: 'daemon', header: 'DAEMON ID', verbatim: true },
|
|
@@ -378,11 +555,28 @@ class HarnessesList extends base_command_1.BaseCommand {
|
|
|
378
555
|
{ key: 'machine', header: 'MACHINE' },
|
|
379
556
|
{ key: 'seen', header: `LAST SEEN (${(0, format_1.localZone)()})` },
|
|
380
557
|
], (line) => this.log(line));
|
|
558
|
+
if (readiness && readiness.answered > 0) {
|
|
559
|
+
for (const line of readinessFootnotes(selection.visible, readiness))
|
|
560
|
+
this.log(line);
|
|
561
|
+
}
|
|
562
|
+
else if (readiness && readiness.asked > 0) {
|
|
563
|
+
this.log('(Readiness not reported — no connected daemon answered. `skrr harnesses installers --daemon <DAEMON ID>` asks one directly.)');
|
|
564
|
+
}
|
|
565
|
+
const noCodingAgentShown = selection.visible.filter((r) => (0, harness_labels_1.isNoCodingAgentProvider)(r.provider)).length;
|
|
566
|
+
if (noCodingAgentShown > 0) {
|
|
567
|
+
this.log(`(provider ${harness_labels_1.NO_CODING_AGENT_PROVIDER_LABEL}: ${noCodingAgentShown} shown are machines with no coding agent installed — the row is how the machine is listed, not a harness named for a provider.)`);
|
|
568
|
+
}
|
|
569
|
+
if (aliasedShown > 0) {
|
|
570
|
+
this.log(`(${exports.ALIAS_MARKER} your alias — everyone else this harness is shared with sees its shared name. \`skrr harnesses show <id>\` prints both.)`);
|
|
571
|
+
}
|
|
381
572
|
if (supersededShown > 0) {
|
|
382
573
|
// This is the sentence that makes `machines list`'s fraction derivable
|
|
383
574
|
// from here: live = printed − superseded.
|
|
384
575
|
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
576
|
}
|
|
577
|
+
if (selection.hiddenSuperseded > 0) {
|
|
578
|
+
this.log(`(${selection.hiddenSuperseded} superseded hidden — \`--status superseded\` lists them.)`);
|
|
579
|
+
}
|
|
386
580
|
if (selection.hiddenByLocal > 0) {
|
|
387
581
|
// Same reason as the JSON `counts` block: the `--local` default is the
|
|
388
582
|
// single biggest source of "the list is shorter than I expected".
|
|
@@ -397,5 +591,27 @@ class HarnessesList extends base_command_1.BaseCommand {
|
|
|
397
591
|
this.log(`(showing ${selection.visible.length} of ${selection.visible.length + selection.hiddenByLimit} — raise --limit for the rest)`);
|
|
398
592
|
}
|
|
399
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* The canonical providers this account has any row for, or null when that
|
|
596
|
+
* cannot be read. Asked only when a --provider filter came back empty, so
|
|
597
|
+
* the ordinary path pays nothing for it.
|
|
598
|
+
*/
|
|
599
|
+
async accountProviders() {
|
|
600
|
+
try {
|
|
601
|
+
const all = await (0, api_fetch_1.apiFetch)('/api/harnesses', {
|
|
602
|
+
credential: this.resolvedCredential,
|
|
603
|
+
});
|
|
604
|
+
const providers = new Set();
|
|
605
|
+
for (const row of canonicalHarnessRows(all.harnesses ?? [])) {
|
|
606
|
+
if (typeof row.provider === 'string' && !(0, harness_labels_1.isNoCodingAgentProvider)(row.provider)) {
|
|
607
|
+
providers.add(row.provider);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return [...providers].sort();
|
|
611
|
+
}
|
|
612
|
+
catch {
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
400
616
|
}
|
|
401
617
|
exports.default = HarnessesList;
|
|
@@ -19,4 +19,6 @@ export default class HarnessesPing extends BaseCommand {
|
|
|
19
19
|
timeout: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
20
|
};
|
|
21
21
|
run(): Promise<void>;
|
|
22
|
+
/** Best-effort: never turns a successful ping into a failure. */
|
|
23
|
+
private logReadiness;
|
|
22
24
|
}
|
|
@@ -4,6 +4,7 @@ exports.isPingSuccess = isPingSuccess;
|
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
5
|
const base_command_1 = require("../../base-command");
|
|
6
6
|
const harnesses_1 = require("../../lib/harnesses");
|
|
7
|
+
const cli_installers_1 = require("../../lib/cli-installers");
|
|
7
8
|
/**
|
|
8
9
|
* Is this machine actually reachable right now?
|
|
9
10
|
*
|
|
@@ -84,6 +85,9 @@ class HarnessesPing extends base_command_1.BaseCommand {
|
|
|
84
85
|
this.log(result.latencyMs != null
|
|
85
86
|
? `Answered in ${result.latencyMs}ms.`
|
|
86
87
|
: 'Answered (no latency reported).');
|
|
88
|
+
// Answering proves the daemon is reachable, not that this harness can
|
|
89
|
+
// run a turn — a signed-out Devin answers too (OSK-9263).
|
|
90
|
+
await this.logReadiness(args.harness);
|
|
87
91
|
}
|
|
88
92
|
else {
|
|
89
93
|
this.log(`Ping ${result.status}${result.error ? `: ${result.error}` : ''}.`);
|
|
@@ -104,5 +108,22 @@ class HarnessesPing extends base_command_1.BaseCommand {
|
|
|
104
108
|
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
105
109
|
}
|
|
106
110
|
}
|
|
111
|
+
/** Best-effort: never turns a successful ping into a failure. */
|
|
112
|
+
async logReadiness(harnessId) {
|
|
113
|
+
try {
|
|
114
|
+
const response = await (0, harnesses_1.getHarness)(harnessId);
|
|
115
|
+
const harness = response?.harness ??
|
|
116
|
+
response;
|
|
117
|
+
if (!harness?.daemonId)
|
|
118
|
+
return;
|
|
119
|
+
const readiness = await (0, cli_installers_1.readinessByProvider)(harness.daemonId, this.resolvedCredential);
|
|
120
|
+
const verdict = readiness?.get((0, cli_installers_1.readinessKey)(harness.provider));
|
|
121
|
+
if (verdict)
|
|
122
|
+
this.log(`Readiness: ${verdict.label} — ${verdict.detail}`);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// A readiness read is extra information; the ping already answered.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
107
128
|
}
|
|
108
129
|
exports.default = HarnessesPing;
|
|
@@ -25,3 +25,25 @@ export default class HarnessesShow extends BaseCommand {
|
|
|
25
25
|
* still falls through to the general answer rather than being guessed at.
|
|
26
26
|
*/
|
|
27
27
|
export declare function describeWrongHarnessRef(ref: string): string;
|
|
28
|
+
type HarnessRefResolution = {
|
|
29
|
+
kind: 'harness';
|
|
30
|
+
id: string;
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'message';
|
|
33
|
+
message: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'none';
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a reference that is not a full harness id against this account's
|
|
39
|
+
* rows: a unique id prefix or a unique name selects the harness; a machine uuid
|
|
40
|
+
* prefix or an ambiguous match explains itself. `none` falls through to the
|
|
41
|
+
* shape-based message.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveHarnessRef(ref: string): Promise<HarnessRefResolution>;
|
|
44
|
+
export declare function pickHarnessRef(rows: Array<{
|
|
45
|
+
_id: string;
|
|
46
|
+
name?: string;
|
|
47
|
+
machineUuid?: string;
|
|
48
|
+
}>, ref: string): HarnessRefResolution;
|
|
49
|
+
export {};
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.describeWrongHarnessRef = describeWrongHarnessRef;
|
|
4
|
+
exports.resolveHarnessRef = resolveHarnessRef;
|
|
5
|
+
exports.pickHarnessRef = pickHarnessRef;
|
|
4
6
|
const core_1 = require("@oclif/core");
|
|
5
7
|
const base_command_1 = require("../../base-command");
|
|
6
8
|
const harness_provider_input_1 = require("../../lib/harness-provider-input");
|
|
7
9
|
const harnesses_1 = require("../../lib/harnesses");
|
|
10
|
+
const harness_labels_1 = require("../../lib/harness-labels");
|
|
11
|
+
const cli_installers_1 = require("../../lib/cli-installers");
|
|
8
12
|
const api_fetch_1 = require("../../lib/api-fetch");
|
|
13
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
9
14
|
class HarnessesShow extends base_command_1.BaseCommand {
|
|
10
15
|
static description = 'Show one harness — one provider on one machine';
|
|
11
16
|
static examples = [
|
|
@@ -29,20 +34,30 @@ class HarnessesShow extends base_command_1.BaseCommand {
|
|
|
29
34
|
}
|
|
30
35
|
catch (err) {
|
|
31
36
|
// The server answers a malformed id with "Invalid harness ID", which says
|
|
32
|
-
// nothing about what a valid one IS or where to get one. The
|
|
33
|
-
// `
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
// nothing about what a valid one IS or where to get one. The readable
|
|
38
|
+
// text lives in `body`, not `message`, and any 400 on this lookup is the
|
|
39
|
+
// id being wrong, so branch on the status.
|
|
40
|
+
if (!(err instanceof api_fetch_1.ApiFetchError && err.status === 400)) {
|
|
41
|
+
this.handleApiError(err);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Before refusing, try what other commands PRINT: the head of a harness
|
|
45
|
+
// id, a harness name, or a machine uuid. `machines show` accepts prefixes
|
|
46
|
+
// and names; this sibling answered the same input with a generic "24
|
|
47
|
+
// hexadecimal characters" (OSK-9313).
|
|
48
|
+
const resolved = await resolveHarnessRef(args.harness);
|
|
49
|
+
if (resolved.kind !== 'harness') {
|
|
50
|
+
const why = resolved.kind === 'message' ? resolved.message : describeWrongHarnessRef(args.harness);
|
|
51
|
+
this.error(`Not a harness id: ${args.harness}\n${why}`, { exit: 1 });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
response = await (0, harnesses_1.getHarness)(resolved.id);
|
|
56
|
+
}
|
|
57
|
+
catch (retryErr) {
|
|
58
|
+
this.handleApiError(retryErr);
|
|
42
59
|
return;
|
|
43
60
|
}
|
|
44
|
-
this.handleApiError(err);
|
|
45
|
-
return;
|
|
46
61
|
}
|
|
47
62
|
const stored = response?.harness;
|
|
48
63
|
// Canonical spelling in every output, whichever spelling the row was stored under.
|
|
@@ -53,13 +68,30 @@ class HarnessesShow extends base_command_1.BaseCommand {
|
|
|
53
68
|
this.error(`No harness ${args.harness}.`, { exit: 1 });
|
|
54
69
|
return;
|
|
55
70
|
}
|
|
71
|
+
// `status online` is a heartbeat, not "this harness can run a turn": a
|
|
72
|
+
// Devin that is installed and not signed in read as fully usable (OSK-9263).
|
|
73
|
+
// Asked of the daemon, so only for a live local row; best-effort.
|
|
74
|
+
const readiness = harness.daemonId &&
|
|
75
|
+
harness.harnessLocation === 'local' &&
|
|
76
|
+
harness.status === 'online' &&
|
|
77
|
+
!harness.superseded
|
|
78
|
+
? await (0, cli_installers_1.readinessByProvider)(harness.daemonId, this.resolvedCredential)
|
|
79
|
+
: null;
|
|
80
|
+
const verdict = readiness?.get((0, cli_installers_1.readinessKey)(harness.provider));
|
|
56
81
|
if (flags.json) {
|
|
57
|
-
|
|
82
|
+
// `authStatus` is the DAEMON's sign-in to skrr, the same on every row of a
|
|
83
|
+
// daemon, so a script read a signed-out Devin as "ok" (OSK-9263/OSK-9265
|
|
84
|
+
// verifiers). `readiness` is the per-provider answer the text prints;
|
|
85
|
+
// null when it was not asked (not a live local row) or not reported.
|
|
86
|
+
this.log(JSON.stringify({ ...harness, readiness: verdict ?? null }, null, 2));
|
|
58
87
|
return;
|
|
59
88
|
}
|
|
60
|
-
|
|
89
|
+
// The same rule the list applies (OSK-9649): a no-engine placeholder's
|
|
90
|
+
// stored name is "oversky — <machine>", which headed this view with a
|
|
91
|
+
// provider name that is not one.
|
|
92
|
+
this.log(`${harness.name ? (0, data_provider_1.canonicalizeHarnessLabel)(harness.name) : harness._id}`);
|
|
61
93
|
this.log(` id ${harness._id}`);
|
|
62
|
-
this.log(` provider ${harness.provider}`);
|
|
94
|
+
this.log(` provider ${(0, harness_labels_1.harnessProviderDescription)(harness.provider)}`);
|
|
63
95
|
// `harnessLocation` is the difference between "your laptop" and "a cloud row",
|
|
64
96
|
// and it decides which verbs apply: ping and claudeEngine are local-only.
|
|
65
97
|
// When YOUR alias is covering the shared name, say so and show both. The
|
|
@@ -67,11 +99,11 @@ class HarnessesShow extends base_command_1.BaseCommand {
|
|
|
67
99
|
// value `harnesses update --name` just wrote appears on no surface at all,
|
|
68
100
|
// and you cannot tell you are reading your own label rather than the one
|
|
69
101
|
// everyone else sees.
|
|
70
|
-
if (
|
|
102
|
+
if ((0, harness_labels_1.isAliasCoveringSharedName)(harness)) {
|
|
71
103
|
this.log(` shared name ${harness.sharedName} (your alias is showing instead)`);
|
|
72
104
|
}
|
|
73
105
|
this.log(` mode ${harness.harnessLocation ?? 'unknown'}`);
|
|
74
|
-
this.log(` status ${harness
|
|
106
|
+
this.log(` status ${(0, harness_labels_1.harnessStatusLabel)(harness)}`);
|
|
75
107
|
// Only for the claude provider. The daemon stamps `claudeEngine` on every
|
|
76
108
|
// harness it registers, so printing it whenever it is set labels a
|
|
77
109
|
// first-party harness or `codex` row with how CLAUDE Code would run — a fact about a
|
|
@@ -81,6 +113,12 @@ class HarnessesShow extends base_command_1.BaseCommand {
|
|
|
81
113
|
}
|
|
82
114
|
if (harness.daemonId)
|
|
83
115
|
this.log(` daemon ${harness.daemonId}`);
|
|
116
|
+
if (verdict) {
|
|
117
|
+
this.log(` readiness ${verdict.label} — ${verdict.detail}`);
|
|
118
|
+
}
|
|
119
|
+
else if (readiness) {
|
|
120
|
+
this.log(' readiness not reported — this daemon does not track credentials for this provider');
|
|
121
|
+
}
|
|
84
122
|
if (harness.lastSeenAt)
|
|
85
123
|
this.log(` last seen ${harness.lastSeenAt}`);
|
|
86
124
|
}
|
|
@@ -105,6 +143,14 @@ function describeWrongHarnessRef(ref) {
|
|
|
105
143
|
if (/^daemon_/.test(ref)) {
|
|
106
144
|
return `That is a daemon id. \`skrr machines show ${ref}\` takes those. ${seeIds}`;
|
|
107
145
|
}
|
|
146
|
+
if (/^(dedicated-runtime-|hosted-machine-|hosted-runtime-)/.test(ref)) {
|
|
147
|
+
// Printed in the DAEMON ID column for a rented machine, so it is the same
|
|
148
|
+
// redirect as `daemon_…`, not a malformed harness id (OSK-9313).
|
|
149
|
+
return `That is a daemon id. \`skrr machines show ${ref}\` takes those. ${seeIds}`;
|
|
150
|
+
}
|
|
151
|
+
if (/^sbs_/.test(ref)) {
|
|
152
|
+
return `That is a hosted machine id. \`skrr machines show ${ref}\` takes those. ${seeIds}`;
|
|
153
|
+
}
|
|
108
154
|
if (/^machine:/.test(ref)) {
|
|
109
155
|
return `That is a machine id. \`skrr machines show ${ref}\` takes those. ${seeIds}`;
|
|
110
156
|
}
|
|
@@ -118,3 +164,56 @@ function describeWrongHarnessRef(ref) {
|
|
|
118
164
|
}
|
|
119
165
|
return `A harness id is 24 hexadecimal characters. ${seeIds}`;
|
|
120
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Resolve a reference that is not a full harness id against this account's
|
|
169
|
+
* rows: a unique id prefix or a unique name selects the harness; a machine uuid
|
|
170
|
+
* prefix or an ambiguous match explains itself. `none` falls through to the
|
|
171
|
+
* shape-based message.
|
|
172
|
+
*/
|
|
173
|
+
async function resolveHarnessRef(ref) {
|
|
174
|
+
if (!ref.trim())
|
|
175
|
+
return { kind: 'none' };
|
|
176
|
+
let rows;
|
|
177
|
+
try {
|
|
178
|
+
const res = await (0, api_fetch_1.apiFetch)('/api/harnesses');
|
|
179
|
+
rows = res.harnesses ?? [];
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return { kind: 'none' };
|
|
183
|
+
}
|
|
184
|
+
return pickHarnessRef(rows, ref);
|
|
185
|
+
}
|
|
186
|
+
function pickHarnessRef(rows, ref) {
|
|
187
|
+
const typed = ref.trim();
|
|
188
|
+
const needle = typed.toLowerCase();
|
|
189
|
+
const byId = rows.filter((r) => r._id.toLowerCase().startsWith(needle));
|
|
190
|
+
if (byId.length === 1)
|
|
191
|
+
return { kind: 'harness', id: byId[0]._id };
|
|
192
|
+
const byName = rows.filter((r) => typeof r.name === 'string' &&
|
|
193
|
+
(r.name.toLowerCase() === needle ||
|
|
194
|
+
(0, data_provider_1.canonicalizeHarnessLabel)(r.name).toLowerCase() === needle));
|
|
195
|
+
if (byName.length === 1)
|
|
196
|
+
return { kind: 'harness', id: byName[0]._id };
|
|
197
|
+
const ambiguous = byId.length > 1 ? byId : byName;
|
|
198
|
+
if (ambiguous.length > 1) {
|
|
199
|
+
const shown = ambiguous
|
|
200
|
+
.slice(0, 5)
|
|
201
|
+
.map((r) => `${r._id}${r.name ? ` (${(0, data_provider_1.canonicalizeHarnessLabel)(r.name)})` : ''}`);
|
|
202
|
+
return {
|
|
203
|
+
kind: 'message',
|
|
204
|
+
message: `"${typed}" matches ${ambiguous.length} harnesses: ${shown.join(', ')}${ambiguous.length > shown.length ? `, and ${ambiguous.length - shown.length} more` : ''}. Use a longer prefix or the full id.`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const machine = rows.find((r) => {
|
|
208
|
+
const uuid = (r.machineUuid ?? '').toLowerCase();
|
|
209
|
+
const tail = uuid.includes(':') ? uuid.slice(uuid.indexOf(':') + 1) : uuid;
|
|
210
|
+
return uuid.startsWith(needle) || tail.startsWith(needle);
|
|
211
|
+
});
|
|
212
|
+
if (machine) {
|
|
213
|
+
return {
|
|
214
|
+
kind: 'message',
|
|
215
|
+
message: `That is a machine. \`skrr machines show ${typed}\` takes those; its harnesses are listed there.`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return { kind: 'none' };
|
|
219
|
+
}
|