@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.
- package/README.md +17 -4
- package/bin/dev-fallback.js +167 -4
- package/dist/base-command.js +46 -2
- package/dist/commands/agents/actions/create.d.ts +5 -0
- package/dist/commands/agents/actions/create.js +42 -5
- package/dist/commands/agents/actions/delete.d.ts +1 -0
- package/dist/commands/agents/actions/delete.js +7 -0
- package/dist/commands/agents/actions/update.d.ts +5 -0
- package/dist/commands/agents/actions/update.js +27 -1
- package/dist/commands/agents/api-actions/add.d.ts +41 -0
- package/dist/commands/agents/api-actions/add.js +205 -0
- package/dist/commands/agents/api-actions/index.d.ts +39 -0
- package/dist/commands/agents/api-actions/index.js +103 -0
- package/dist/commands/agents/api-actions/remove.d.ts +14 -0
- package/dist/commands/agents/api-actions/remove.js +36 -0
- package/dist/commands/agents/avatar/default/apply.d.ts +25 -0
- package/dist/commands/agents/avatar/default/apply.js +56 -0
- package/dist/commands/agents/avatar/default/index.d.ts +29 -0
- package/dist/commands/agents/avatar/default/index.js +61 -0
- package/dist/commands/agents/avatar/default/set.d.ts +24 -0
- package/dist/commands/agents/avatar/default/set.js +120 -0
- package/dist/commands/agents/avatar/library/delete.d.ts +20 -0
- package/dist/commands/agents/avatar/library/delete.js +47 -0
- package/dist/commands/agents/avatar/library/index.d.ts +16 -0
- package/dist/commands/agents/avatar/library/index.js +56 -0
- package/dist/commands/agents/avatar/presets.d.ts +19 -0
- package/dist/commands/agents/avatar/presets.js +56 -0
- package/dist/commands/agents/avatar/upload.js +7 -1
- package/dist/commands/agents/categories/create.d.ts +14 -0
- package/dist/commands/agents/categories/create.js +46 -0
- package/dist/commands/agents/categories/delete.d.ts +13 -0
- package/dist/commands/agents/categories/delete.js +41 -0
- package/dist/commands/agents/categories/index.d.ts +36 -0
- package/dist/commands/agents/categories/index.js +135 -0
- package/dist/commands/agents/categories/update.d.ts +22 -0
- package/dist/commands/agents/categories/update.js +59 -0
- package/dist/commands/agents/chat.d.ts +21 -0
- package/dist/commands/agents/chat.js +94 -7
- package/dist/commands/agents/create.d.ts +10 -0
- package/dist/commands/agents/create.js +64 -3
- package/dist/commands/agents/ensure-default.d.ts +27 -0
- package/dist/commands/agents/ensure-default.js +65 -0
- package/dist/commands/agents/list.d.ts +15 -0
- package/dist/commands/agents/list.js +31 -1
- package/dist/commands/agents/mute.d.ts +16 -0
- package/dist/commands/agents/mute.js +38 -0
- package/dist/commands/agents/pin.d.ts +16 -0
- package/dist/commands/agents/pin.js +38 -0
- package/dist/commands/agents/scene-background/upload.d.ts +30 -0
- package/dist/commands/agents/scene-background/upload.js +97 -0
- package/dist/commands/agents/show.js +13 -0
- package/dist/commands/agents/tools/auth.d.ts +19 -0
- package/dist/commands/agents/tools/auth.js +41 -0
- package/dist/commands/agents/tools/calls.d.ts +36 -0
- package/dist/commands/agents/tools/calls.js +117 -0
- package/dist/commands/agents/tools/index.d.ts +20 -0
- package/dist/commands/agents/tools/index.js +77 -0
- package/dist/commands/agents/unmute.d.ts +16 -0
- package/dist/commands/agents/unmute.js +38 -0
- package/dist/commands/agents/unpin.d.ts +16 -0
- package/dist/commands/agents/unpin.js +38 -0
- package/dist/commands/agents/update.d.ts +12 -0
- package/dist/commands/agents/update.js +74 -3
- package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
- package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
- package/dist/commands/convos/messages.d.ts +13 -0
- package/dist/commands/convos/messages.js +40 -16
- package/dist/commands/daemon/index.js +8 -1
- package/dist/commands/daemon/logs.d.ts +26 -0
- package/dist/commands/daemon/logs.js +56 -0
- package/dist/commands/daemon/uninstall.js +1 -1
- package/dist/commands/followups/act.d.ts +22 -0
- package/dist/commands/followups/act.js +103 -0
- package/dist/commands/followups/cancel.d.ts +20 -0
- package/dist/commands/followups/cancel.js +50 -0
- package/dist/commands/followups/checkin.d.ts +27 -0
- package/dist/commands/followups/checkin.js +107 -0
- package/dist/commands/followups/list.d.ts +24 -0
- package/dist/commands/followups/list.js +90 -0
- package/dist/commands/followups/observability.d.ts +23 -0
- package/dist/commands/followups/observability.js +114 -0
- package/dist/commands/followups/remind.d.ts +26 -0
- package/dist/commands/followups/remind.js +106 -0
- package/dist/commands/followups/reschedule.d.ts +15 -0
- package/dist/commands/followups/reschedule.js +48 -0
- package/dist/commands/followups/resolve.d.ts +23 -0
- package/dist/commands/followups/resolve.js +54 -0
- package/dist/commands/followups/show.d.ts +12 -0
- package/dist/commands/followups/show.js +46 -0
- package/dist/commands/followups/watch.d.ts +51 -0
- package/dist/commands/followups/watch.js +211 -0
- package/dist/commands/goals/labels/add.js +19 -2
- package/dist/commands/goals/labels/remove.js +22 -2
- package/dist/commands/harnesses/forget.js +28 -0
- package/dist/commands/harnesses/install.d.ts +41 -0
- package/dist/commands/harnesses/install.js +165 -0
- package/dist/commands/harnesses/installers.d.ts +45 -0
- package/dist/commands/harnesses/installers.js +155 -0
- package/dist/commands/harnesses/list.d.ts +82 -1
- package/dist/commands/harnesses/list.js +104 -12
- package/dist/commands/harnesses/models.js +10 -1
- package/dist/commands/harnesses/show.d.ts +15 -0
- package/dist/commands/harnesses/show.js +44 -3
- package/dist/commands/harnesses/update.js +1 -1
- package/dist/commands/harnesses/usage.d.ts +25 -0
- package/dist/commands/harnesses/usage.js +24 -2
- package/dist/commands/instructions/status.js +13 -1
- package/dist/commands/labels/list.d.ts +24 -1
- package/dist/commands/labels/list.js +62 -19
- package/dist/commands/machines/dedicated/catalog.js +4 -15
- package/dist/commands/machines/dedicated/create.d.ts +8 -0
- package/dist/commands/machines/dedicated/create.js +58 -1
- package/dist/commands/machines/dedicated/destroy.js +18 -4
- package/dist/commands/machines/dedicated/list.js +9 -6
- package/dist/commands/machines/hosted/list.js +12 -0
- package/dist/commands/machines/list.js +11 -0
- package/dist/commands/machines/show.d.ts +1 -0
- package/dist/commands/machines/show.js +40 -11
- package/dist/commands/memory/governance/index.d.ts +21 -0
- package/dist/commands/memory/governance/index.js +64 -0
- package/dist/commands/memory/governance/set.d.ts +24 -0
- package/dist/commands/memory/governance/set.js +95 -0
- package/dist/commands/memory/provenance.d.ts +18 -0
- package/dist/commands/memory/provenance.js +66 -0
- package/dist/commands/profiles/list.d.ts +23 -0
- package/dist/commands/profiles/list.js +96 -0
- package/dist/commands/search/skills.d.ts +11 -1
- package/dist/commands/search/skills.js +26 -8
- package/dist/commands/skills/details.d.ts +2 -0
- package/dist/commands/skills/details.js +24 -1
- package/dist/commands/skills/import-as-actions.d.ts +32 -0
- package/dist/commands/skills/import-as-actions.js +175 -0
- package/dist/commands/skills/install.d.ts +2 -0
- package/dist/commands/skills/install.js +24 -1
- package/dist/commands/skills/invocable.js +6 -2
- package/dist/commands/skills/local.d.ts +40 -0
- package/dist/commands/skills/local.js +160 -0
- package/dist/commands/skills/search.d.ts +11 -1
- package/dist/commands/skills/search.js +29 -8
- package/dist/commands/spaces/actions/add.js +23 -5
- package/dist/commands/spaces/actions/available.js +3 -0
- package/dist/commands/spaces/actions/list.d.ts +5 -0
- package/dist/commands/spaces/actions/list.js +49 -3
- package/dist/commands/spaces/actions/run.d.ts +28 -0
- package/dist/commands/spaces/actions/run.js +146 -0
- package/dist/commands/spaces/actions/update.js +27 -5
- package/dist/commands/spaces/create.d.ts +3 -0
- package/dist/commands/spaces/create.js +32 -2
- package/dist/commands/spaces/label-summaries.d.ts +27 -0
- package/dist/commands/spaces/label-summaries.js +64 -0
- package/dist/commands/spaces/labels/add.js +19 -2
- package/dist/commands/spaces/labels/remove.js +22 -2
- package/dist/commands/spaces/show.js +2 -0
- package/dist/commands/spaces/task-rollups.d.ts +5 -0
- package/dist/commands/spaces/task-rollups.js +15 -4
- package/dist/commands/spaces/update.d.ts +5 -0
- package/dist/commands/spaces/update.js +41 -4
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.d.ts +11 -0
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +105 -0
- package/dist/commands/tasks/actions/attach.js +32 -7
- package/dist/commands/tasks/complete.d.ts +1 -0
- package/dist/commands/tasks/complete.js +21 -1
- package/dist/commands/tasks/create.d.ts +3 -0
- package/dist/commands/tasks/create.js +34 -2
- package/dist/commands/tasks/events/append.js +29 -6
- package/dist/commands/tasks/expectations/assess.d.ts +12 -0
- package/dist/commands/tasks/expectations/assess.js +123 -14
- package/dist/commands/tasks/expectations.js +95 -17
- package/dist/commands/tasks/follow-up.js +2 -2
- package/dist/commands/tasks/labels/attach.js +20 -3
- package/dist/commands/tasks/labels/detach.js +21 -3
- package/dist/commands/tasks/lifecycle/attach.d.ts +14 -0
- package/dist/commands/tasks/lifecycle/attach.js +47 -0
- package/dist/commands/tasks/lifecycle/definitions.d.ts +11 -0
- package/dist/commands/tasks/lifecycle/definitions.js +59 -0
- package/dist/commands/tasks/lifecycle/detach.d.ts +19 -0
- package/dist/commands/tasks/lifecycle/detach.js +72 -0
- package/dist/commands/tasks/lifecycle/history.d.ts +13 -0
- package/dist/commands/tasks/lifecycle/history.js +38 -0
- package/dist/commands/tasks/lifecycle/migrate.d.ts +14 -0
- package/dist/commands/tasks/lifecycle/migrate.js +68 -0
- package/dist/commands/tasks/lifecycle/reconcile.d.ts +11 -0
- package/dist/commands/tasks/lifecycle/reconcile.js +27 -0
- package/dist/commands/tasks/lifecycle/reopen.d.ts +15 -0
- package/dist/commands/tasks/lifecycle/reopen.js +71 -0
- package/dist/commands/tasks/lifecycle/show.d.ts +11 -0
- package/dist/commands/tasks/lifecycle/show.js +27 -0
- package/dist/commands/tasks/lifecycle/start.d.ts +12 -0
- package/dist/commands/tasks/lifecycle/start.js +30 -0
- package/dist/commands/tasks/lifecycle/transition.d.ts +15 -0
- package/dist/commands/tasks/lifecycle/transition.js +109 -0
- package/dist/commands/tasks/list.js +16 -9
- package/dist/commands/tasks/move.d.ts +1 -0
- package/dist/commands/tasks/move.js +29 -0
- package/dist/commands/tasks/result/submit.js +11 -1
- package/dist/commands/tasks/review-queue.d.ts +44 -0
- package/dist/commands/tasks/review-queue.js +314 -0
- package/dist/commands/tasks/show.d.ts +11 -0
- package/dist/commands/tasks/show.js +139 -4
- package/dist/commands/tasks/trust.js +7 -2
- package/dist/commands/tasks/update.d.ts +1 -0
- package/dist/commands/tasks/update.js +83 -0
- package/dist/commands/tasks/updates/add.js +1 -1
- package/dist/commands/token/list.d.ts +22 -0
- package/dist/commands/token/list.js +68 -0
- package/dist/commands/token/revoke.d.ts +23 -0
- package/dist/commands/token/revoke.js +53 -0
- package/dist/lib/agent-actions.d.ts +92 -11
- package/dist/lib/agent-actions.js +171 -25
- package/dist/lib/agent-config.d.ts +11 -0
- package/dist/lib/agent-config.js +85 -3
- package/dist/lib/agent-pin-mute.d.ts +29 -0
- package/dist/lib/agent-pin-mute.js +30 -0
- package/dist/lib/agent-visual-refs.d.ts +33 -0
- package/dist/lib/agent-visual-refs.js +267 -0
- package/dist/lib/agentic-stream.d.ts +6 -0
- package/dist/lib/agentic-stream.js +34 -3
- package/dist/lib/assignee-resolver.d.ts +1 -1
- package/dist/lib/assignee-resolver.js +8 -0
- package/dist/lib/bulk-task-targets.d.ts +12 -0
- package/dist/lib/bulk-task-targets.js +34 -1
- package/dist/lib/cli-installers.d.ts +130 -0
- package/dist/lib/cli-installers.js +145 -0
- package/dist/lib/command-miss.js +55 -7
- package/dist/lib/dedicated-lease-command.d.ts +13 -0
- package/dist/lib/dedicated-lease-command.js +22 -1
- package/dist/lib/dedicated-machines.d.ts +95 -1
- package/dist/lib/dedicated-machines.js +236 -2
- package/dist/lib/failed-lookup.d.ts +3 -0
- package/dist/lib/failed-lookup.js +40 -0
- package/dist/lib/first-party-harness-agent.js +16 -2
- package/dist/lib/first-party-harness-doctor.d.ts +15 -0
- package/dist/lib/first-party-harness-doctor.js +126 -27
- package/dist/lib/followups.d.ts +115 -0
- package/dist/lib/followups.js +127 -0
- package/dist/lib/harnesses.d.ts +8 -0
- package/dist/lib/instruction-input.d.ts +19 -1
- package/dist/lib/instruction-input.js +52 -1
- package/dist/lib/label-ref.d.ts +95 -0
- package/dist/lib/label-ref.js +163 -0
- package/dist/lib/label-scope.d.ts +17 -2
- package/dist/lib/label-scope.js +27 -7
- package/dist/lib/local-skills.d.ts +98 -0
- package/dist/lib/local-skills.js +195 -0
- package/dist/lib/machines.d.ts +10 -10
- package/dist/lib/machines.js +23 -1
- package/dist/lib/search-query.d.ts +19 -0
- package/dist/lib/search-query.js +35 -0
- package/dist/lib/space-custom-actions.d.ts +72 -0
- package/dist/lib/space-custom-actions.js +117 -0
- package/dist/lib/space-resolver.d.ts +1 -21
- package/dist/lib/space-resolver.js +19 -10
- package/dist/lib/task-closure.d.ts +33 -0
- package/dist/lib/task-closure.js +104 -0
- package/dist/lib/task-lifecycle-output.d.ts +14 -0
- package/dist/lib/task-lifecycle-output.js +209 -0
- package/dist/lib/task-ref-resolver.d.ts +1 -17
- package/dist/lib/task-ref-resolver.js +18 -8
- package/dist/lib/tasks.d.ts +3 -0
- package/dist/lib/tasks.js +40 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +5559 -4897
- package/oclif.manifest.json +33890 -28690
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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".
|