@skrr-ai/cli 0.1.37 → 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dev-fallback.js +175 -4
- package/dist/base-command.js +46 -2
- package/dist/commands/agents/actions/create.js +18 -7
- 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.js +5 -3
- package/dist/commands/agents/api-actions/add.js +5 -2
- package/dist/commands/agents/api-actions/index.d.ts +11 -0
- package/dist/commands/agents/api-actions/index.js +27 -1
- package/dist/commands/agents/avatar/default/index.d.ts +11 -0
- package/dist/commands/agents/avatar/default/index.js +17 -0
- package/dist/commands/agents/avatar/default/set.d.ts +2 -1
- package/dist/commands/agents/avatar/default/set.js +32 -7
- package/dist/commands/agents/avatar/upload.js +7 -1
- package/dist/commands/agents/categories/delete.js +8 -0
- package/dist/commands/agents/categories/index.d.ts +21 -5
- package/dist/commands/agents/categories/index.js +68 -15
- package/dist/commands/agents/chat.d.ts +21 -0
- package/dist/commands/agents/chat.js +94 -7
- package/dist/commands/agents/create.js +11 -4
- package/dist/commands/agents/list.d.ts +15 -0
- package/dist/commands/agents/list.js +31 -1
- 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/calls.d.ts +23 -5
- package/dist/commands/agents/tools/calls.js +81 -18
- package/dist/commands/agents/update.js +15 -5
- 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.js +3 -2
- package/dist/commands/followups/cancel.d.ts +6 -3
- package/dist/commands/followups/cancel.js +16 -6
- package/dist/commands/followups/observability.d.ts +23 -0
- package/dist/commands/followups/observability.js +114 -0
- package/dist/commands/followups/reschedule.js +1 -1
- package/dist/commands/followups/resolve.js +3 -3
- package/dist/commands/followups/show.js +2 -1
- package/dist/commands/followups/watch.js +9 -5
- 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/machines/dedicated/catalog.js +4 -15
- package/dist/commands/machines/dedicated/create.d.ts +8 -0
- package/dist/commands/machines/dedicated/create.js +63 -2
- package/dist/commands/machines/dedicated/destroy.js +19 -5
- package/dist/commands/machines/dedicated/exec.js +3 -1
- package/dist/commands/machines/dedicated/list.js +9 -6
- package/dist/commands/machines/dedicated/price-book.d.ts +17 -0
- package/dist/commands/machines/dedicated/price-book.js +35 -0
- 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/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/label-summaries.d.ts +27 -0
- package/dist/commands/spaces/label-summaries.js +64 -0
- package/dist/commands/spaces/task-rollups.d.ts +5 -0
- package/dist/commands/spaces/task-rollups.js +15 -4
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +63 -3
- package/dist/commands/tasks/actions/attach.js +32 -7
- package/dist/commands/tasks/complete.js +13 -0
- package/dist/commands/tasks/create.js +1 -1
- package/dist/commands/tasks/events/append.js +29 -6
- package/dist/commands/tasks/expectations/assess.d.ts +1 -0
- package/dist/commands/tasks/expectations/assess.js +43 -5
- package/dist/commands/tasks/expectations.js +77 -16
- package/dist/commands/tasks/follow-up.js +2 -2
- package/dist/commands/tasks/lifecycle/definitions.js +21 -2
- package/dist/commands/tasks/lifecycle/detach.d.ts +7 -0
- package/dist/commands/tasks/lifecycle/detach.js +43 -3
- package/dist/commands/tasks/lifecycle/migrate.d.ts +2 -0
- package/dist/commands/tasks/lifecycle/migrate.js +42 -3
- package/dist/commands/tasks/lifecycle/reopen.d.ts +2 -0
- package/dist/commands/tasks/lifecycle/reopen.js +40 -3
- package/dist/commands/tasks/lifecycle/transition.js +33 -2
- package/dist/commands/tasks/list.js +16 -9
- package/dist/commands/tasks/move.js +11 -13
- package/dist/commands/tasks/review-queue.d.ts +15 -0
- package/dist/commands/tasks/review-queue.js +180 -31
- package/dist/commands/tasks/show.d.ts +11 -0
- package/dist/commands/tasks/show.js +105 -12
- package/dist/commands/tasks/trust.js +7 -2
- package/dist/commands/tasks/update.js +46 -10
- 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 +57 -2
- package/dist/lib/agent-actions.js +113 -21
- package/dist/lib/agent-pin-mute.d.ts +10 -5
- package/dist/lib/agent-pin-mute.js +17 -4
- package/dist/lib/agent-visual-refs.d.ts +27 -0
- package/dist/lib/agent-visual-refs.js +127 -2
- 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 +16 -1
- package/dist/lib/dedicated-lease-command.js +26 -3
- package/dist/lib/dedicated-machines.d.ts +135 -1
- package/dist/lib/dedicated-machines.js +307 -3
- 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 +9 -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 +14 -6
- package/dist/lib/followups.js +31 -6
- 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-scope.d.ts +15 -0
- package/dist/lib/label-scope.js +25 -5
- 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 +3 -2
- package/dist/lib/task-closure.js +18 -20
- package/dist/lib/task-lifecycle-output.js +117 -9
- package/dist/lib/task-ref-resolver.d.ts +1 -17
- package/dist/lib/task-ref-resolver.js +18 -8
- package/dist/lib/tasks.js +9 -3
- package/dist/node_modules/@skrr-ai/data-provider/index.js +5076 -4947
- package/oclif.manifest.json +15505 -14460
- package/package.json +9 -3
|
@@ -7,6 +7,7 @@ exports.describeMember = describeMember;
|
|
|
7
7
|
exports.describeCandidates = describeCandidates;
|
|
8
8
|
exports.resolveAssigneeReferences = resolveAssigneeReferences;
|
|
9
9
|
exports.formatResolutionFailure = formatResolutionFailure;
|
|
10
|
+
const failed_lookup_1 = require("./failed-lookup");
|
|
10
11
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
11
12
|
const OBJECT_ID_RE = /^[0-9a-fA-F]{24}$/;
|
|
12
13
|
const UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
@@ -84,6 +85,11 @@ function resolveMember(query, members, kind) {
|
|
|
84
85
|
if (!q) {
|
|
85
86
|
return { ok: false, code: 'NOT_FOUND', query: raw, kind, candidates: pool };
|
|
86
87
|
}
|
|
88
|
+
// A failed lookup's output is refused before the name tiers can match it.
|
|
89
|
+
// See `failed-lookup.ts`.
|
|
90
|
+
if ((0, failed_lookup_1.isFailedLookupValue)(raw)) {
|
|
91
|
+
return { ok: false, code: 'NOT_A_REFERENCE', query: raw, kind, candidates: [] };
|
|
92
|
+
}
|
|
87
93
|
const tiers = [
|
|
88
94
|
// 1. Exact id. Case-sensitive on purpose — ids are opaque.
|
|
89
95
|
(m) => m.id === raw,
|
|
@@ -186,6 +192,8 @@ function formatResolutionFailure(failure, bin = 'skrr') {
|
|
|
186
192
|
const noun = failure.kind === 'user' ? 'person' : 'agent';
|
|
187
193
|
const plural = failure.kind === 'user' ? 'people' : 'agents';
|
|
188
194
|
switch (failure.code) {
|
|
195
|
+
case 'NOT_A_REFERENCE':
|
|
196
|
+
return (0, failed_lookup_1.describeFailedLookupValue)(failure.query, noun);
|
|
189
197
|
case 'AMBIGUOUS':
|
|
190
198
|
return (`"${failure.query}" matches ${failure.candidates.length} ${plural} in this space:\n` +
|
|
191
199
|
`${describeCandidates(failure.candidates)}\n` +
|
|
@@ -29,6 +29,18 @@ export type BulkApplyResult = {
|
|
|
29
29
|
* semantics match `skrr tasks bulk-update`). `apply` returns whether the task
|
|
30
30
|
* actually changed (already-satisfied tasks are reported as no-ops).
|
|
31
31
|
*/
|
|
32
|
+
/**
|
|
33
|
+
* The server's reason for a failed per-task write, not the transport's.
|
|
34
|
+
*
|
|
35
|
+
* A bulk run used to record `err.message`, which for a dataService call is the
|
|
36
|
+
* transport line — `HTTP 400 Bad Request — PATCH https://…/api/tasks/<id>` — so
|
|
37
|
+
* `tasks actions attach --all-in-space` printed that once per task and named
|
|
38
|
+
* neither the ref nor the rule that refused it (OSK-9652). The same call made
|
|
39
|
+
* for a single task goes through `handleApiError`, which reads the body. This
|
|
40
|
+
* reads it the same way: `message`, then `error`, plus the first validation
|
|
41
|
+
* detail when the server sent one.
|
|
42
|
+
*/
|
|
43
|
+
export declare function bulkFailureMessage(err: unknown): string;
|
|
32
44
|
export declare function applyToTaskTargets(targets: Array<{
|
|
33
45
|
id: string;
|
|
34
46
|
identifier?: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.listSpaceTaskTargets = listSpaceTaskTargets;
|
|
4
|
+
exports.bulkFailureMessage = bulkFailureMessage;
|
|
4
5
|
exports.applyToTaskTargets = applyToTaskTargets;
|
|
5
6
|
exports.formatBulkOutcome = formatBulkOutcome;
|
|
6
7
|
exports.parseStatusFilter = parseStatusFilter;
|
|
@@ -34,6 +35,38 @@ async function listSpaceTaskTargets(options) {
|
|
|
34
35
|
* semantics match `skrr tasks bulk-update`). `apply` returns whether the task
|
|
35
36
|
* actually changed (already-satisfied tasks are reported as no-ops).
|
|
36
37
|
*/
|
|
38
|
+
/**
|
|
39
|
+
* The server's reason for a failed per-task write, not the transport's.
|
|
40
|
+
*
|
|
41
|
+
* A bulk run used to record `err.message`, which for a dataService call is the
|
|
42
|
+
* transport line — `HTTP 400 Bad Request — PATCH https://…/api/tasks/<id>` — so
|
|
43
|
+
* `tasks actions attach --all-in-space` printed that once per task and named
|
|
44
|
+
* neither the ref nor the rule that refused it (OSK-9652). The same call made
|
|
45
|
+
* for a single task goes through `handleApiError`, which reads the body. This
|
|
46
|
+
* reads it the same way: `message`, then `error`, plus the first validation
|
|
47
|
+
* detail when the server sent one.
|
|
48
|
+
*/
|
|
49
|
+
function bulkFailureMessage(err) {
|
|
50
|
+
const e = err;
|
|
51
|
+
if (e && typeof e.body === 'string' && e.body.trim()) {
|
|
52
|
+
try {
|
|
53
|
+
const body = JSON.parse(e.body);
|
|
54
|
+
const reason = [body.message, body.error].find((value) => typeof value === 'string' && value.trim().length > 0);
|
|
55
|
+
if (reason) {
|
|
56
|
+
const detail = Array.isArray(body.details) ? body.details[0] : undefined;
|
|
57
|
+
const detailText = detail && typeof detail.message === 'string'
|
|
58
|
+
? ` — ${Array.isArray(detail.path) && detail.path.length ? `${detail.path.join('.')}: ` : ''}${detail.message}`
|
|
59
|
+
: '';
|
|
60
|
+
const status = typeof e.status === 'number' ? ` (HTTP ${e.status})` : '';
|
|
61
|
+
return `${reason}${detailText}${status}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
/* not JSON: fall back to the transport message below */
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return err instanceof Error ? err.message : String(err);
|
|
69
|
+
}
|
|
37
70
|
async function applyToTaskTargets(targets, apply) {
|
|
38
71
|
const result = { succeeded: [], failed: [] };
|
|
39
72
|
for (const target of targets) {
|
|
@@ -46,7 +79,7 @@ async function applyToTaskTargets(targets, apply) {
|
|
|
46
79
|
result.failed.push({
|
|
47
80
|
id: target.id,
|
|
48
81
|
label,
|
|
49
|
-
error:
|
|
82
|
+
error: bulkFailureMessage(err),
|
|
50
83
|
});
|
|
51
84
|
}
|
|
52
85
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/** Matches `ManagedCliAuthenticationState` in `daemon/src/cli-installers.ts`. */
|
|
2
|
+
export interface ManagedCliAuthentication {
|
|
3
|
+
status: 'ready' | 'interactive-required' | 'headless-key-required';
|
|
4
|
+
source: 'environment' | 'browser' | 'none';
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Matches `ManagedCliProviderState` in `daemon/src/cli-installers.ts`.
|
|
9
|
+
*
|
|
10
|
+
* Every field optional on purpose: this crosses a version boundary. The daemon
|
|
11
|
+
* on the other end may be older than this CLI (they ship separately, and this
|
|
12
|
+
* machine routinely runs a daemon bundled inside the desktop app), so a missing
|
|
13
|
+
* field has to read as "this daemon does not say", never as a falsy answer.
|
|
14
|
+
*/
|
|
15
|
+
export interface ManagedCliProviderState {
|
|
16
|
+
provider?: string;
|
|
17
|
+
displayName?: string;
|
|
18
|
+
binaryName?: string;
|
|
19
|
+
installed?: boolean;
|
|
20
|
+
available?: boolean;
|
|
21
|
+
version?: string;
|
|
22
|
+
minVersion?: string;
|
|
23
|
+
meetsMinimum?: boolean;
|
|
24
|
+
resolvedPath?: string | null;
|
|
25
|
+
source?: string;
|
|
26
|
+
installable?: boolean;
|
|
27
|
+
installDisabledReason?: string | null;
|
|
28
|
+
platformSupported?: boolean;
|
|
29
|
+
authentication?: ManagedCliAuthentication | null;
|
|
30
|
+
job?: {
|
|
31
|
+
id?: string;
|
|
32
|
+
status?: string;
|
|
33
|
+
} | null;
|
|
34
|
+
}
|
|
35
|
+
export interface CliInstallersResponse {
|
|
36
|
+
daemonId?: string;
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
platform?: string;
|
|
39
|
+
/** Absent from a daemon older than the field — render as "not reported". */
|
|
40
|
+
daemonVersion?: string;
|
|
41
|
+
providers?: ManagedCliProviderState[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Providers the platform knows about that this daemon did not offer.
|
|
45
|
+
*
|
|
46
|
+
* The inventory is whatever the CONNECTED daemon supports, and an older daemon
|
|
47
|
+
* legitimately supports fewer: `devin` arrived in daemon 0.8.43, so a machine
|
|
48
|
+
* running 0.8.35 answers with six providers instead of seven. Observed here —
|
|
49
|
+
* the desktop app restarted the daemon from its bundled 0.8.35 copy and Devin
|
|
50
|
+
* disappeared from the install list between two page loads, with nothing on
|
|
51
|
+
* either screen to explain it. A provider that silently stops existing reads as
|
|
52
|
+
* dropped support.
|
|
53
|
+
*
|
|
54
|
+
* This works WITHOUT a daemon update, which is the point: `daemonVersion` only
|
|
55
|
+
* helps once apps ship a daemon that reports it, whereas the shared vocabulary
|
|
56
|
+
* is already on this side of the wire. Naming the gap is what turns "Devin is
|
|
57
|
+
* gone" into "this daemon is older than Devin".
|
|
58
|
+
*/
|
|
59
|
+
export declare function providersNotOffered(offered: readonly ManagedCliProviderState[] | undefined, known: readonly string[]): string[];
|
|
60
|
+
export declare function listCliInstallers(daemonId: string, credential?: unknown): Promise<CliInstallersResponse>;
|
|
61
|
+
/**
|
|
62
|
+
* The one-word readiness verdict for a provider, and whether it is good news.
|
|
63
|
+
*
|
|
64
|
+
* Four states, and the fourth is the one that matters most to get right:
|
|
65
|
+
* `unknown`. A provider whose recipe declares no credential resolver reports
|
|
66
|
+
* `authentication: null`, and that is NOT `ready` — this daemon simply has no
|
|
67
|
+
* way to read that vendor's credential state. Collapsing the two is the exact
|
|
68
|
+
* failure `d878b79581` was written to stop, so the word is different and the
|
|
69
|
+
* detail says why.
|
|
70
|
+
*/
|
|
71
|
+
export declare function readinessOf(provider: ManagedCliProviderState): {
|
|
72
|
+
label: string;
|
|
73
|
+
detail: string;
|
|
74
|
+
ok: boolean;
|
|
75
|
+
};
|
|
76
|
+
/** A job the daemon runs to install one managed provider. */
|
|
77
|
+
export interface ManagedCliInstallJob {
|
|
78
|
+
id: string;
|
|
79
|
+
status: 'queued' | 'running' | 'succeeded' | 'failed';
|
|
80
|
+
error?: string;
|
|
81
|
+
log?: string[];
|
|
82
|
+
}
|
|
83
|
+
export interface StartInstallResponse {
|
|
84
|
+
started?: boolean;
|
|
85
|
+
provider?: ManagedCliProviderState;
|
|
86
|
+
job?: {
|
|
87
|
+
id?: string;
|
|
88
|
+
status?: string;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export declare function startManagedCliInstall(daemonId: string, provider: string, credential?: unknown): Promise<StartInstallResponse>;
|
|
92
|
+
export declare function readManagedCliInstallJob(daemonId: string, jobId: string, credential?: unknown): Promise<{
|
|
93
|
+
job?: ManagedCliInstallJob;
|
|
94
|
+
}>;
|
|
95
|
+
/** A 100 MB download over a hotel connection is the case this has to survive. */
|
|
96
|
+
export declare const INSTALL_MAX_WAIT_MS: number;
|
|
97
|
+
export declare const INSTALL_POLL_INTERVAL_MS = 2000;
|
|
98
|
+
export type FollowJobOutcome = {
|
|
99
|
+
kind: 'settled';
|
|
100
|
+
job: ManagedCliInstallJob;
|
|
101
|
+
}
|
|
102
|
+
/** The daemon is still working. NOT a failure — see below. */
|
|
103
|
+
| {
|
|
104
|
+
kind: 'timeout';
|
|
105
|
+
jobId: string;
|
|
106
|
+
} | {
|
|
107
|
+
kind: 'missing';
|
|
108
|
+
jobId: string;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Poll an install job to completion, printing each new log line once.
|
|
112
|
+
*
|
|
113
|
+
* The daemon writes the only account of a download the user is waiting on
|
|
114
|
+
* ("Fetching the signed release manifest", "Verified and installed …"), so the
|
|
115
|
+
* loop relays it; printing only the unseen tail keeps a poll from repeating the
|
|
116
|
+
* whole log each time.
|
|
117
|
+
*
|
|
118
|
+
* A timeout is returned, never thrown as a failure. Saying "install failed"
|
|
119
|
+
* because this process ran out of patience is a lie that sends someone to debug
|
|
120
|
+
* a download that is going fine — so the caller decides the wording, and both
|
|
121
|
+
* callers say the daemon is still working.
|
|
122
|
+
*/
|
|
123
|
+
export declare function followManagedCliInstallJob(input: {
|
|
124
|
+
daemonId: string;
|
|
125
|
+
jobId: string;
|
|
126
|
+
credential?: unknown;
|
|
127
|
+
onLogLine?: (line: string) => void;
|
|
128
|
+
maxWaitMs?: number;
|
|
129
|
+
pollIntervalMs?: number;
|
|
130
|
+
}): Promise<FollowJobOutcome>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.INSTALL_POLL_INTERVAL_MS = exports.INSTALL_MAX_WAIT_MS = void 0;
|
|
4
|
+
exports.providersNotOffered = providersNotOffered;
|
|
5
|
+
exports.listCliInstallers = listCliInstallers;
|
|
6
|
+
exports.readinessOf = readinessOf;
|
|
7
|
+
exports.startManagedCliInstall = startManagedCliInstall;
|
|
8
|
+
exports.readManagedCliInstallJob = readManagedCliInstallJob;
|
|
9
|
+
exports.followManagedCliInstallJob = followManagedCliInstallJob;
|
|
10
|
+
/**
|
|
11
|
+
* The daemon's managed-CLI installer inventory, as the CLI reads it.
|
|
12
|
+
*
|
|
13
|
+
* ## Why this file exists
|
|
14
|
+
*
|
|
15
|
+
* `GET /api/daemons/:id/cli-installers` is where `d878b79581` put the answer to
|
|
16
|
+
* "is this harness actually usable" — `installed`, and for providers whose
|
|
17
|
+
* credential state the daemon can read, one of `ready` /
|
|
18
|
+
* `interactive-required` / `headless-key-required`. The whole point of that
|
|
19
|
+
* change was to stop surfaces answering "is the binary there" instead.
|
|
20
|
+
*
|
|
21
|
+
* The CLI called the route from exactly one place (`commands/code/install.ts`),
|
|
22
|
+
* for one provider, to read one field, and showed the user none of it. So from
|
|
23
|
+
* the CLI there was no way to learn that a harness listed as `online` had no
|
|
24
|
+
* binary at all — which is the state this account's `skrr-code` row was in.
|
|
25
|
+
*
|
|
26
|
+
* The shape lives here rather than beside a command because two commands read
|
|
27
|
+
* it now. Root `CLAUDE.md`: a second declaration is how a surface silently
|
|
28
|
+
* drifts from the server that produced it.
|
|
29
|
+
*/
|
|
30
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
31
|
+
const api_fetch_1 = require("./api-fetch");
|
|
32
|
+
/**
|
|
33
|
+
* Providers the platform knows about that this daemon did not offer.
|
|
34
|
+
*
|
|
35
|
+
* The inventory is whatever the CONNECTED daemon supports, and an older daemon
|
|
36
|
+
* legitimately supports fewer: `devin` arrived in daemon 0.8.43, so a machine
|
|
37
|
+
* running 0.8.35 answers with six providers instead of seven. Observed here —
|
|
38
|
+
* the desktop app restarted the daemon from its bundled 0.8.35 copy and Devin
|
|
39
|
+
* disappeared from the install list between two page loads, with nothing on
|
|
40
|
+
* either screen to explain it. A provider that silently stops existing reads as
|
|
41
|
+
* dropped support.
|
|
42
|
+
*
|
|
43
|
+
* This works WITHOUT a daemon update, which is the point: `daemonVersion` only
|
|
44
|
+
* helps once apps ship a daemon that reports it, whereas the shared vocabulary
|
|
45
|
+
* is already on this side of the wire. Naming the gap is what turns "Devin is
|
|
46
|
+
* gone" into "this daemon is older than Devin".
|
|
47
|
+
*/
|
|
48
|
+
function providersNotOffered(offered, known) {
|
|
49
|
+
// Canonicalize BOTH sides. An older daemon answers under the spelling it was
|
|
50
|
+
// built with — the 0.8.35 build reports the first-party harness as
|
|
51
|
+
// `sky-code`, so a raw comparison against `skrr-code` reported it missing on
|
|
52
|
+
// a machine where it is installed and working. Caught by running this against
|
|
53
|
+
// the very daemon the finding was about: the first version of this helper
|
|
54
|
+
// said "Not offered: devin, skrr-code" when only `devin` was true.
|
|
55
|
+
const canonical = (value) => typeof value === 'string' ? String((0, data_provider_1.canonicalHarnessProvider)(value) ?? value) : '';
|
|
56
|
+
const seen = new Set((offered ?? []).map((provider) => canonical(provider.provider)).filter(Boolean));
|
|
57
|
+
return known.filter((provider) => !seen.has(canonical(provider)));
|
|
58
|
+
}
|
|
59
|
+
async function listCliInstallers(daemonId, credential) {
|
|
60
|
+
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers`, { credential: credential });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The one-word readiness verdict for a provider, and whether it is good news.
|
|
64
|
+
*
|
|
65
|
+
* Four states, and the fourth is the one that matters most to get right:
|
|
66
|
+
* `unknown`. A provider whose recipe declares no credential resolver reports
|
|
67
|
+
* `authentication: null`, and that is NOT `ready` — this daemon simply has no
|
|
68
|
+
* way to read that vendor's credential state. Collapsing the two is the exact
|
|
69
|
+
* failure `d878b79581` was written to stop, so the word is different and the
|
|
70
|
+
* detail says why.
|
|
71
|
+
*/
|
|
72
|
+
function readinessOf(provider) {
|
|
73
|
+
if (provider.platformSupported === false) {
|
|
74
|
+
return { label: 'unsupported', detail: 'Not available on this platform.', ok: false };
|
|
75
|
+
}
|
|
76
|
+
if (!provider.installed) {
|
|
77
|
+
// `available` without `meetsMinimum` is a REAL third case and reads very
|
|
78
|
+
// differently from "not installed": the binary is right there and too old.
|
|
79
|
+
if (provider.available && provider.meetsMinimum === false) {
|
|
80
|
+
return {
|
|
81
|
+
label: 'too-old',
|
|
82
|
+
detail: provider.minVersion
|
|
83
|
+
? `Found ${provider.version || 'an older build'}; needs ${provider.minVersion} or newer.`
|
|
84
|
+
: 'The installed build is older than the minimum this harness needs.',
|
|
85
|
+
ok: false,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
label: 'not-installed',
|
|
90
|
+
detail: provider.installDisabledReason || 'No binary for this provider on this machine.',
|
|
91
|
+
ok: false,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const auth = provider.authentication;
|
|
95
|
+
if (!auth) {
|
|
96
|
+
return {
|
|
97
|
+
label: 'unknown',
|
|
98
|
+
detail: 'Installed. This daemon cannot read this provider’s credential state.',
|
|
99
|
+
ok: false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return { label: auth.status, detail: auth.message, ok: auth.status === 'ready' };
|
|
103
|
+
}
|
|
104
|
+
async function startManagedCliInstall(daemonId, provider, credential) {
|
|
105
|
+
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/${encodeURIComponent(provider)}/install`, { method: 'POST', body: {}, credential: credential });
|
|
106
|
+
}
|
|
107
|
+
async function readManagedCliInstallJob(daemonId, jobId, credential) {
|
|
108
|
+
return (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/jobs/${encodeURIComponent(jobId)}`, { credential: credential });
|
|
109
|
+
}
|
|
110
|
+
/** A 100 MB download over a hotel connection is the case this has to survive. */
|
|
111
|
+
exports.INSTALL_MAX_WAIT_MS = 10 * 60_000;
|
|
112
|
+
exports.INSTALL_POLL_INTERVAL_MS = 2_000;
|
|
113
|
+
/**
|
|
114
|
+
* Poll an install job to completion, printing each new log line once.
|
|
115
|
+
*
|
|
116
|
+
* The daemon writes the only account of a download the user is waiting on
|
|
117
|
+
* ("Fetching the signed release manifest", "Verified and installed …"), so the
|
|
118
|
+
* loop relays it; printing only the unseen tail keeps a poll from repeating the
|
|
119
|
+
* whole log each time.
|
|
120
|
+
*
|
|
121
|
+
* A timeout is returned, never thrown as a failure. Saying "install failed"
|
|
122
|
+
* because this process ran out of patience is a lie that sends someone to debug
|
|
123
|
+
* a download that is going fine — so the caller decides the wording, and both
|
|
124
|
+
* callers say the daemon is still working.
|
|
125
|
+
*/
|
|
126
|
+
async function followManagedCliInstallJob(input) {
|
|
127
|
+
const deadline = Date.now() + (input.maxWaitMs ?? exports.INSTALL_MAX_WAIT_MS);
|
|
128
|
+
let printed = 0;
|
|
129
|
+
for (;;) {
|
|
130
|
+
const res = await readManagedCliInstallJob(input.daemonId, input.jobId, input.credential);
|
|
131
|
+
const job = res.job;
|
|
132
|
+
if (!job)
|
|
133
|
+
return { kind: 'missing', jobId: input.jobId };
|
|
134
|
+
if (input.onLogLine) {
|
|
135
|
+
for (const line of (job.log ?? []).slice(printed))
|
|
136
|
+
input.onLogLine(line);
|
|
137
|
+
printed = (job.log ?? []).length;
|
|
138
|
+
}
|
|
139
|
+
if (job.status === 'succeeded' || job.status === 'failed')
|
|
140
|
+
return { kind: 'settled', job };
|
|
141
|
+
if (Date.now() > deadline)
|
|
142
|
+
return { kind: 'timeout', jobId: input.jobId };
|
|
143
|
+
await new Promise((resolve) => setTimeout(resolve, input.pollIntervalMs ?? exports.INSTALL_POLL_INTERVAL_MS));
|
|
144
|
+
}
|
|
145
|
+
}
|
package/dist/lib/command-miss.js
CHANGED
|
@@ -104,17 +104,65 @@ function describeCommandMiss(config, id) {
|
|
|
104
104
|
// command id, so a whole-string comparison finds nothing and the reader is
|
|
105
105
|
// told only that they are wrong — when the useful answer is "that topic
|
|
106
106
|
// exists and here is what it does have".
|
|
107
|
+
const typedId = parts.join(':');
|
|
108
|
+
const spokenId = `${config.bin} ${parts.join(' ')}`;
|
|
109
|
+
// The id IS registered and the lookup still could not produce it. That is not
|
|
110
|
+
// a typo, so the "is not a command" wording below would be false — and worse,
|
|
111
|
+
// the suggestion search then finds the id itself and hands the reader their
|
|
112
|
+
// own input back:
|
|
113
|
+
//
|
|
114
|
+
// $ skrr agents list --help
|
|
115
|
+
// Error: `skrr agents list` is not a command.
|
|
116
|
+
// Did you mean: `skrr agents list`?
|
|
117
|
+
//
|
|
118
|
+
// Observed against a real build, for ten commands in a row, and it is actively
|
|
119
|
+
// misleading rather than merely unhelpful: a dogfood run read it as proof that
|
|
120
|
+
// a shipped release had not shipped its commands. The two facts come from two
|
|
121
|
+
// sources — `findCommand` loads, `commandIDs` lists — and when they disagree
|
|
122
|
+
// the honest thing to report is the disagreement, which has a known cause and
|
|
123
|
+
// a next step.
|
|
124
|
+
if (config.commandIDs.includes(typedId) || config.topicNames.includes(typedId)) {
|
|
125
|
+
return (`\`${spokenId}\` is registered but could not be loaded from this build.\n` +
|
|
126
|
+
`Its compiled output is missing or stale — run \`npm --prefix cli run build\` in a ` +
|
|
127
|
+
`development checkout, or reinstall the CLI.`);
|
|
128
|
+
}
|
|
107
129
|
const siblings = config.commandIDs.filter((c) => c.startsWith(`${parts[0]}:`));
|
|
108
|
-
const suggestions = siblings.length
|
|
130
|
+
const suggestions = (siblings.length
|
|
109
131
|
? nearestCommands(parts.slice(0, 2).join(':'), siblings)
|
|
110
|
-
: nearestCommands(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
132
|
+
: nearestCommands(typedId, [...config.commandIDs, ...config.topicNames]))
|
|
133
|
+
// A suggestion identical to what was typed is an echo, not a correction.
|
|
134
|
+
// It can outrank real alternatives too, since nothing is closer to the id
|
|
135
|
+
// than the id, so it costs a suggestion slot as well as the reader's trust.
|
|
136
|
+
.filter((candidate) => candidate !== typedId);
|
|
137
|
+
if (suggestions.length) {
|
|
138
|
+
return (`\`${spokenId}\` is not a command.` +
|
|
139
|
+
`\nDid you mean: ${suggestions
|
|
114
140
|
.map((s) => `\`${config.bin} ${s.split(':').join(' ')}\``)
|
|
115
|
-
.join(', ')}?`
|
|
116
|
-
|
|
141
|
+
.join(', ')}?`);
|
|
142
|
+
}
|
|
143
|
+
// The topic the reader typed is real, and nothing in it is spelled like what
|
|
144
|
+
// they asked for — a different word for it, not a typo. "See the topics"
|
|
145
|
+
// sent them back to the top level to rediscover the topic they had already
|
|
146
|
+
// found; what they need is what THIS topic has (OSK-9723: `skrr dm send`,
|
|
147
|
+
// where `dm` has no `send` at all).
|
|
148
|
+
if (siblings.length) {
|
|
149
|
+
const topic = parts[0];
|
|
150
|
+
const shown = siblings
|
|
151
|
+
.map((id) => id
|
|
152
|
+
.slice(topic.length + 1)
|
|
153
|
+
.split(':')
|
|
154
|
+
.join(' '))
|
|
155
|
+
.sort()
|
|
156
|
+
.slice(0, MAX_TOPIC_LISTING);
|
|
157
|
+
const more = siblings.length > shown.length ? ', …' : '';
|
|
158
|
+
return (`\`${spokenId}\` is not a command.\n` +
|
|
159
|
+
`\`${config.bin} ${topic}\` has: ${shown.map((c) => `\`${c}\``).join(', ')}${more}.\n` +
|
|
160
|
+
`See \`${config.bin} ${topic} --help\`.`);
|
|
161
|
+
}
|
|
162
|
+
return `\`${spokenId}\` is not a command.\nRun \`${config.bin} --help\` to see the topics.`;
|
|
117
163
|
}
|
|
164
|
+
/** Enough to scan at a glance; the topic's own `--help` has the rest. */
|
|
165
|
+
const MAX_TOPIC_LISTING = 10;
|
|
118
166
|
/** Adapt an oclif `Config` to the lookup shape above. */
|
|
119
167
|
function lookupFromConfig(config) {
|
|
120
168
|
return {
|
|
@@ -61,7 +61,22 @@ export declare abstract class DedicatedLeaseCommand extends BaseCommand {
|
|
|
61
61
|
timeout?: string;
|
|
62
62
|
interval?: number;
|
|
63
63
|
workspace?: string;
|
|
64
|
-
}, goal: DedicatedWaitGoal | null, verb: string
|
|
64
|
+
}, goal: DedicatedWaitGoal | null, verb: string,
|
|
65
|
+
/** Fields a `--json` caller needs that the server response does not carry. */
|
|
66
|
+
jsonExtra?: Record<string, unknown>): Promise<void>;
|
|
65
67
|
}
|
|
66
68
|
/** The same transient verdict BaseCommand gives an ordinary API call. */
|
|
67
69
|
export declare function dedicatedRuntimeApiErrorRetryable(err: unknown): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Accept the id our own output printed.
|
|
72
|
+
*
|
|
73
|
+
* `skrr machines list --all` renders a Dedicated Runtime's MACHINE identity,
|
|
74
|
+
* which the server builds as `lease:${leaseId}` (`machineIdentityForHarness`).
|
|
75
|
+
* The lease routes want the bare `rtl_…`. Pasting the one you were just shown
|
|
76
|
+
* therefore 404'd, and the refusal blamed workspace scope — sending you to try
|
|
77
|
+
* `--workspace` when the fix was deleting six characters.
|
|
78
|
+
*
|
|
79
|
+
* Only the prefixes this product itself prints are stripped, so a genuinely
|
|
80
|
+
* unknown id still fails as an unknown id rather than being silently reshaped.
|
|
81
|
+
*/
|
|
82
|
+
export declare function stripMachineIdentityPrefix(ref: string): string;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DedicatedLeaseCommand = exports.DEDICATED_LEASE_ARG_DESCRIPTION = exports.DEDICATED_SELF_LEASE = exports.dedicatedWaitFlags = void 0;
|
|
4
4
|
exports.dedicatedRuntimeApiErrorRetryable = dedicatedRuntimeApiErrorRetryable;
|
|
5
|
+
exports.stripMachineIdentityPrefix = stripMachineIdentityPrefix;
|
|
5
6
|
const core_1 = require("@oclif/core");
|
|
6
7
|
const base_command_1 = require("../base-command");
|
|
7
8
|
const api_fetch_1 = require("./api-fetch");
|
|
@@ -51,7 +52,7 @@ class DedicatedLeaseCommand extends base_command_1.BaseCommand {
|
|
|
51
52
|
*/
|
|
52
53
|
async resolveLeaseArg(lease) {
|
|
53
54
|
if (lease !== exports.DEDICATED_SELF_LEASE)
|
|
54
|
-
return lease;
|
|
55
|
+
return stripMachineIdentityPrefix(lease);
|
|
55
56
|
const descriptor = (0, daemonBroker_1.readBootstrap)(daemonBroker_1.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR);
|
|
56
57
|
if (!descriptor?.daemonId) {
|
|
57
58
|
this.error('`self` means the Dedicated Runtime this command runs on, and this is not one (or its daemon is not running). Pass a lease id.', { exit: 2 });
|
|
@@ -144,7 +145,9 @@ class DedicatedLeaseCommand extends base_command_1.BaseCommand {
|
|
|
144
145
|
* Print the lease an action returned, or wait for its operation to finish
|
|
145
146
|
* first when `--wait` was passed.
|
|
146
147
|
*/
|
|
147
|
-
async reportLeaseAction(response, flags, goal, verb
|
|
148
|
+
async reportLeaseAction(response, flags, goal, verb,
|
|
149
|
+
/** Fields a `--json` caller needs that the server response does not carry. */
|
|
150
|
+
jsonExtra = {}) {
|
|
148
151
|
let lease = response?.lease;
|
|
149
152
|
const id = (0, dedicated_machines_1.dedicatedLeaseId)(lease);
|
|
150
153
|
if (flags.wait && goal && id) {
|
|
@@ -200,7 +203,7 @@ class DedicatedLeaseCommand extends base_command_1.BaseCommand {
|
|
|
200
203
|
}
|
|
201
204
|
}
|
|
202
205
|
if (flags.json) {
|
|
203
|
-
this.log(JSON.stringify({ ...(response || {}), lease }, null, 2));
|
|
206
|
+
this.log(JSON.stringify({ ...(response || {}), lease, ...jsonExtra }, null, 2));
|
|
204
207
|
return;
|
|
205
208
|
}
|
|
206
209
|
this.log(`Dedicated Runtime ${id || 'unknown'} ${(0, dedicated_machines_1.dedicatedLeaseState)(lease)}.`);
|
|
@@ -226,3 +229,23 @@ function dedicatedRuntimeApiErrorRetryable(err) {
|
|
|
226
229
|
}
|
|
227
230
|
return (0, base_command_1.isRetryableFailure)(err.status, body);
|
|
228
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Accept the id our own output printed.
|
|
234
|
+
*
|
|
235
|
+
* `skrr machines list --all` renders a Dedicated Runtime's MACHINE identity,
|
|
236
|
+
* which the server builds as `lease:${leaseId}` (`machineIdentityForHarness`).
|
|
237
|
+
* The lease routes want the bare `rtl_…`. Pasting the one you were just shown
|
|
238
|
+
* therefore 404'd, and the refusal blamed workspace scope — sending you to try
|
|
239
|
+
* `--workspace` when the fix was deleting six characters.
|
|
240
|
+
*
|
|
241
|
+
* Only the prefixes this product itself prints are stripped, so a genuinely
|
|
242
|
+
* unknown id still fails as an unknown id rather than being silently reshaped.
|
|
243
|
+
*/
|
|
244
|
+
function stripMachineIdentityPrefix(ref) {
|
|
245
|
+
const trimmed = ref.trim();
|
|
246
|
+
for (const prefix of ['lease:', 'dedicated-runtime:']) {
|
|
247
|
+
if (trimmed.startsWith(prefix))
|
|
248
|
+
return trimmed.slice(prefix.length);
|
|
249
|
+
}
|
|
250
|
+
return trimmed;
|
|
251
|
+
}
|