@skrr-ai/cli 0.1.94 → 0.1.96
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 +175 -165
- package/bin/run.js +32 -0
- package/bin/update-npm-runner.cjs +33 -0
- package/bin/update-session.cjs +194 -0
- package/bin/update-worker.cjs +208 -0
- package/dist/base-command.js +0 -15
- package/dist/commands/agent/message/send.d.ts +2 -0
- package/dist/commands/agent/message/send.js +75 -5
- package/dist/commands/agents/chat.js +1 -1
- package/dist/commands/agents/create.d.ts +0 -3
- package/dist/commands/agents/create.js +0 -26
- package/dist/commands/agents/egress.d.ts +17 -0
- package/dist/commands/agents/egress.js +84 -0
- package/dist/commands/agents/mailbox/messages.d.ts +14 -0
- package/dist/commands/agents/mailbox/messages.js +52 -0
- package/dist/commands/agents/mailbox/show.d.ts +13 -0
- package/dist/commands/agents/mailbox/show.js +52 -0
- package/dist/commands/agents/operating-plan.js +8 -0
- package/dist/commands/agents/roles/capture.d.ts +16 -0
- package/dist/commands/agents/roles/capture.js +53 -0
- package/dist/commands/agents/roles/install.d.ts +1 -0
- package/dist/commands/agents/roles/install.js +8 -1
- package/dist/commands/agents/roles/preview.d.ts +2 -0
- package/dist/commands/agents/roles/preview.js +27 -3
- package/dist/commands/agents/update.d.ts +0 -3
- package/dist/commands/agents/update.js +1 -31
- package/dist/commands/agents/usage-cap.d.ts +4 -1
- package/dist/commands/agents/usage-cap.js +49 -10
- package/dist/commands/code/doctor.d.ts +1 -0
- package/dist/commands/code/doctor.js +6 -0
- package/dist/commands/code/index.js +4 -0
- package/dist/commands/code/install.d.ts +5 -62
- package/dist/commands/code/install.js +86 -279
- package/dist/commands/code/jobs/show.js +12 -0
- package/dist/commands/code/rollback.d.ts +8 -0
- package/dist/commands/code/rollback.js +20 -0
- package/dist/commands/code/update.d.ts +8 -37
- package/dist/commands/code/update.js +134 -213
- package/dist/commands/commitments/action-proposals/decide.d.ts +2 -0
- package/dist/commands/commitments/action-proposals/decide.js +12 -0
- package/dist/commands/commitments/action-proposals.js +7 -0
- package/dist/commands/commitments/create.d.ts +1 -0
- package/dist/commands/commitments/create.js +17 -1
- package/dist/commands/commitments/delivery.d.ts +1 -0
- package/dist/commands/commitments/delivery.js +11 -1
- package/dist/commands/commitments/doctor.js +4 -0
- package/dist/commands/commitments/effective-policy.js +26 -0
- package/dist/commands/commitments/endpoints/create.js +5 -2
- package/dist/commands/commitments/observations.d.ts +10 -0
- package/dist/commands/commitments/observations.js +88 -0
- package/dist/commands/commitments/pause.d.ts +1 -1
- package/dist/commands/commitments/pause.js +4 -3
- package/dist/commands/commitments/preflight.js +2 -1
- package/dist/commands/commitments/reports.d.ts +2 -0
- package/dist/commands/commitments/reports.js +22 -4
- package/dist/commands/commitments/update.d.ts +1 -0
- package/dist/commands/commitments/update.js +35 -5
- package/dist/commands/daemon/install.js +4 -0
- package/dist/commands/generate-recovery-code.d.ts +13 -1
- package/dist/commands/generate-recovery-code.js +146 -13
- package/dist/commands/grants/create.d.ts +1 -0
- package/dist/commands/grants/create.js +8 -0
- package/dist/commands/grants/rotate.d.ts +5 -0
- package/dist/commands/grants/rotate.js +13 -1
- package/dist/commands/grants/update.d.ts +12 -0
- package/dist/commands/grants/update.js +27 -0
- package/dist/commands/instructions/guide.d.ts +20 -0
- package/dist/commands/instructions/guide.js +84 -0
- package/dist/commands/instructions/install.js +57 -33
- package/dist/commands/instructions/setup.d.ts +14 -0
- package/dist/commands/instructions/setup.js +72 -0
- package/dist/commands/integrations/execute.js +1 -1
- package/dist/commands/login.d.ts +5 -6
- package/dist/commands/login.js +27 -11
- package/dist/commands/logout.js +7 -7
- package/dist/commands/machines/hosted/start.js +7 -1
- package/dist/commands/machines/list.js +4 -4
- package/dist/commands/measures/confirm.d.ts +1 -7
- package/dist/commands/measures/confirm.js +15 -14
- package/dist/commands/measures/list.d.ts +1 -7
- package/dist/commands/measures/list.js +1 -7
- package/dist/commands/measures/record.js +4 -4
- package/dist/commands/measures/show.d.ts +11 -0
- package/dist/commands/measures/show.js +26 -0
- package/dist/commands/northstars/governance/configure.d.ts +1 -0
- package/dist/commands/northstars/governance/configure.js +8 -1
- package/dist/commands/proposals/discard.d.ts +5 -0
- package/dist/commands/proposals/discard.js +8 -0
- package/dist/commands/proposals/keep.d.ts +5 -0
- package/dist/commands/proposals/keep.js +8 -0
- package/dist/commands/proposals/list.d.ts +15 -0
- package/dist/commands/proposals/list.js +74 -0
- package/dist/commands/proposals/show.d.ts +12 -0
- package/dist/commands/proposals/show.js +27 -0
- package/dist/commands/proposals/snooze.d.ts +5 -0
- package/dist/commands/proposals/snooze.js +8 -0
- package/dist/commands/security/overview.d.ts +11 -0
- package/dist/commands/security/overview.js +158 -0
- package/dist/commands/signals/monitors/create.d.ts +9 -0
- package/dist/commands/signals/monitors/create.js +29 -0
- package/dist/commands/signals/monitors/list.d.ts +8 -0
- package/dist/commands/signals/monitors/list.js +16 -0
- package/dist/commands/signals/monitors/pause.d.ts +11 -0
- package/dist/commands/signals/monitors/pause.js +17 -0
- package/dist/commands/signals/monitors/resume.d.ts +11 -0
- package/dist/commands/signals/monitors/resume.js +17 -0
- package/dist/commands/signals/monitors/revise.d.ts +12 -0
- package/dist/commands/signals/monitors/revise.js +25 -0
- package/dist/commands/signals/monitors/show.d.ts +11 -0
- package/dist/commands/signals/monitors/show.js +17 -0
- package/dist/commands/signals/monitors/test.d.ts +12 -0
- package/dist/commands/signals/monitors/test.js +25 -0
- package/dist/commands/signals/monitors/transitions.d.ts +11 -0
- package/dist/commands/signals/monitors/transitions.js +17 -0
- package/dist/commands/signals/monitors/validate.d.ts +9 -0
- package/dist/commands/signals/monitors/validate.js +24 -0
- package/dist/commands/signals/schemas/list.d.ts +8 -0
- package/dist/commands/signals/schemas/list.js +16 -0
- package/dist/commands/signals/schemas/show.d.ts +11 -0
- package/dist/commands/signals/schemas/show.js +17 -0
- package/dist/commands/signals/sources/list.d.ts +8 -0
- package/dist/commands/signals/sources/list.js +16 -0
- package/dist/commands/signals/sources/receipts.d.ts +11 -0
- package/dist/commands/signals/sources/receipts.js +17 -0
- package/dist/commands/signals/sources/show.d.ts +11 -0
- package/dist/commands/signals/sources/show.js +17 -0
- package/dist/commands/spaces/claim.d.ts +12 -0
- package/dist/commands/spaces/claim.js +42 -0
- package/dist/commands/spaces/ownership.d.ts +12 -0
- package/dist/commands/spaces/ownership.js +46 -0
- package/dist/commands/spaces/usage.d.ts +12 -0
- package/dist/commands/spaces/usage.js +28 -0
- package/dist/commands/spaces/workflows/run.js +3 -0
- package/dist/commands/spaces/workflows/runs.js +2 -1
- package/dist/commands/spaces/workflows/update.d.ts +6 -1
- package/dist/commands/spaces/workflows/update.js +34 -2
- package/dist/commands/tasks/cancel-cascade.js +1 -1
- package/dist/commands/tasks/cascade-status.d.ts +2 -0
- package/dist/commands/tasks/cascade-status.js +27 -1
- package/dist/commands/tasks/create-tree.d.ts +5 -1
- package/dist/commands/tasks/create-tree.js +35 -7
- package/dist/commands/tasks/create.d.ts +4 -0
- package/dist/commands/tasks/create.js +16 -0
- package/dist/commands/tasks/deliverable/add.js +12 -3
- package/dist/commands/tasks/economics-compare.d.ts +15 -0
- package/dist/commands/tasks/economics-compare.js +60 -0
- package/dist/commands/tasks/expectations/evaluate.d.ts +13 -0
- package/dist/commands/tasks/expectations/evaluate.js +43 -0
- package/dist/commands/tasks/interrupt.js +2 -2
- package/dist/commands/tasks/list.d.ts +1 -0
- package/dist/commands/tasks/list.js +44 -1
- package/dist/commands/tasks/pause-schedule.js +1 -1
- package/dist/commands/tasks/show.d.ts +5 -0
- package/dist/commands/tasks/show.js +17 -0
- package/dist/commands/tasks/update.d.ts +4 -0
- package/dist/commands/tasks/update.js +9 -0
- package/dist/commands/tasks/workflow-findings.js +2 -2
- package/dist/commands/token/list.d.ts +3 -5
- package/dist/commands/token/list.js +99 -57
- package/dist/commands/triggers/create.d.ts +2 -0
- package/dist/commands/triggers/create.js +15 -0
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.js +61 -0
- package/dist/commands/workspaces/grants.d.ts +12 -0
- package/dist/commands/workspaces/grants.js +31 -0
- package/dist/commands/workspaces/stewardship.d.ts +13 -0
- package/dist/commands/workspaces/stewardship.js +34 -0
- package/dist/generated/managed-instruction-guides.json +9 -0
- package/dist/hooks/init.d.ts +5 -0
- package/dist/hooks/init.js +27 -0
- package/dist/lib/agent-config.d.ts +0 -12
- package/dist/lib/agent-config.js +1 -27
- package/dist/lib/agent-mailbox.d.ts +35 -0
- package/dist/lib/agent-mailbox.js +55 -0
- package/dist/lib/commitment-endpoints.d.ts +2 -1
- package/dist/lib/commitment-endpoints.js +2 -5
- package/dist/lib/commitment-product.d.ts +3 -1
- package/dist/lib/commitment-product.js +10 -0
- package/dist/lib/commitment-terms.js +1 -1
- package/dist/lib/commitments.d.ts +17 -15
- package/dist/lib/commitments.js +173 -137
- package/dist/lib/compasses.d.ts +14 -0
- package/dist/lib/compasses.js +25 -11
- package/dist/lib/daemon-installer.d.ts +2 -0
- package/dist/lib/daemon-installer.js +100 -24
- package/dist/lib/first-party-harness-doctor.js +31 -1
- package/dist/lib/first-party-harness-remote-install.d.ts +71 -0
- package/dist/lib/first-party-harness-remote-install.js +310 -0
- package/dist/lib/first-party-harness-remote-update.d.ts +46 -0
- package/dist/lib/first-party-harness-remote-update.js +242 -0
- package/dist/lib/first-party-harness-startup-update.d.ts +26 -0
- package/dist/lib/first-party-harness-startup-update.js +166 -0
- package/dist/lib/first-party-harness-update.d.ts +46 -0
- package/dist/lib/first-party-harness-update.js +289 -0
- package/dist/lib/first-party-harness.js +7 -4
- package/dist/lib/grants.d.ts +5 -1
- package/dist/lib/grants.js +9 -1
- package/dist/lib/hosted-machines.d.ts +7 -0
- package/dist/lib/mailbox-human-command.d.ts +11 -0
- package/dist/lib/mailbox-human-command.js +41 -0
- package/dist/lib/managed-instruction-local.d.ts +12 -0
- package/dist/lib/managed-instruction-local.js +58 -0
- package/dist/lib/managed-instruction-setup.d.ts +35 -0
- package/dist/lib/managed-instruction-setup.js +306 -0
- package/dist/lib/measures.d.ts +28 -2
- package/dist/lib/measures.js +38 -14
- package/dist/lib/memory-sync.d.ts +1 -0
- package/dist/lib/memory-sync.js +279 -68
- package/dist/lib/message-intent.d.ts +6 -2
- package/dist/lib/message-intent.js +1 -0
- package/dist/lib/northstars.js +2 -1
- package/dist/lib/platform-update-local.d.ts +2 -0
- package/dist/lib/platform-update-local.js +113 -0
- package/dist/lib/platform-update.d.ts +39 -0
- package/dist/lib/platform-update.js +84 -0
- package/dist/lib/profile-machine-identity-guard.d.ts +2 -0
- package/dist/lib/profile-machine-identity-guard.js +13 -0
- package/dist/lib/prompt.d.ts +1 -0
- package/dist/lib/prompt.js +4 -3
- package/dist/lib/proposal-decision-command.d.ts +19 -0
- package/dist/lib/proposal-decision-command.js +64 -0
- package/dist/lib/proposals.d.ts +8 -0
- package/dist/lib/proposals.js +22 -0
- package/dist/lib/roles.d.ts +141 -6
- package/dist/lib/roles.js +8 -2
- package/dist/lib/signals.d.ts +20 -0
- package/dist/lib/signals.js +24 -0
- package/dist/lib/space-usage.d.ts +3 -0
- package/dist/lib/space-usage.js +17 -0
- package/dist/lib/stewardship-output.d.ts +4 -0
- package/dist/lib/stewardship-output.js +82 -0
- package/dist/lib/task-asset-upload.d.ts +3 -1
- package/dist/lib/task-asset-upload.js +1 -0
- package/dist/lib/task-batch.d.ts +9 -0
- package/dist/lib/task-batch.js +94 -17
- package/dist/lib/task-economics.d.ts +16 -0
- package/dist/lib/task-economics.js +22 -0
- package/dist/lib/task-instruction-offer.d.ts +1 -4
- package/dist/lib/task-instruction-offer.js +3 -129
- package/dist/lib/triggers.d.ts +11 -3
- package/dist/lib/triggers.js +58 -1
- package/dist/lib/update-check.d.ts +61 -53
- package/dist/lib/update-check.js +453 -314
- package/dist/lib/usage-discovery.d.ts +10 -0
- package/dist/lib/usage-discovery.js +24 -3
- package/dist/lib/workflow-run-budget.d.ts +12 -0
- package/dist/lib/workflow-run-budget.js +37 -0
- package/dist/lib/workspaces.d.ts +48 -0
- package/dist/lib/workspaces.js +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +14 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.d.ts +3 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.js +3 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessReleaseKeys.d.ts +82 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessReleaseKeys.js +105 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessUpdater.d.ts +110 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessUpdater.js +945 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/githubPushRefusal.d.ts +9 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/githubPushRefusal.js +49 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessPermissionControls.js +62 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +35 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releasePolicy.d.ts +63 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releasePolicy.js +169 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseVersions.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseVersions.js +44 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +12 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.d.ts +3 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.js +3 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessReleaseKeys.d.ts +82 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessReleaseKeys.js +102 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessUpdater.d.ts +110 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessUpdater.js +923 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/githubPushRefusal.d.ts +9 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/githubPushRefusal.js +44 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessPermissionControls.js +59 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +35 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releasePolicy.d.ts +63 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releasePolicy.js +163 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseVersions.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseVersions.js +41 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +41 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +8069 -7121
- package/docs/v1/README.md +28 -0
- package/docs/v1/api.md +55 -0
- package/docs/v1/changelog.md +16 -0
- package/docs/v1/check-examples.mjs +178 -0
- package/docs/v1/examples/api-readonly.mjs +144 -0
- package/docs/v1/operations.md +87 -0
- package/docs/v1/quickstart.md +71 -0
- package/oclif.manifest.json +49424 -46174
- package/package.json +34 -11
|
@@ -105,19 +105,106 @@ function daemonPlatformKey(platform = process.platform, arch = process.arch) {
|
|
|
105
105
|
function fail(code, error) {
|
|
106
106
|
return { ok: false, code, error };
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
class DownloadSizeLimitError extends Error {
|
|
109
|
+
constructor(maxBytes) {
|
|
110
|
+
super(`download exceeds the size ceiling (${maxBytes} bytes)`);
|
|
111
|
+
this.name = 'DownloadSizeLimitError';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Own the request until EOF, rather than releasing its deadline at headers.
|
|
116
|
+
* Count delivered bytes before retaining them; Content-Length is only an early
|
|
117
|
+
* refusal, since it may be absent, incorrect, or describe compressed bytes.
|
|
118
|
+
*/
|
|
119
|
+
async function getBytesWithTimeout(url, timeoutMs, maxBytes, fetchImpl, signal) {
|
|
120
|
+
signal?.throwIfAborted();
|
|
109
121
|
const controller = new AbortController();
|
|
110
|
-
|
|
122
|
+
let response;
|
|
123
|
+
let reader;
|
|
124
|
+
const cancelBody = (reason) => {
|
|
125
|
+
// Cancellation is best effort: a broken producer must not extend the
|
|
126
|
+
// deadline by returning a cancel promise that never settles.
|
|
127
|
+
try {
|
|
128
|
+
const cancelled = reader ? reader.cancel(reason) : response?.body?.cancel(reason);
|
|
129
|
+
void cancelled?.catch(() => undefined);
|
|
130
|
+
}
|
|
131
|
+
catch { /* response may already be closed */ }
|
|
132
|
+
};
|
|
133
|
+
let rejectAbort;
|
|
134
|
+
const aborted = new Promise((_, reject) => { rejectAbort = reject; });
|
|
135
|
+
const onAbort = () => {
|
|
136
|
+
rejectAbort(controller.signal.reason);
|
|
137
|
+
cancelBody(controller.signal.reason);
|
|
138
|
+
};
|
|
139
|
+
controller.signal.addEventListener('abort', onAbort, { once: true });
|
|
140
|
+
const onCallerAbort = () => controller.abort(signal?.reason);
|
|
141
|
+
signal?.addEventListener('abort', onCallerAbort, { once: true });
|
|
142
|
+
const timer = setTimeout(() => controller.abort(new Error(`download timed out after ${timeoutMs} ms`)), timeoutMs);
|
|
111
143
|
try {
|
|
112
|
-
|
|
144
|
+
const requested = fetchImpl(url, {
|
|
113
145
|
signal: controller.signal,
|
|
114
146
|
headers: { 'User-Agent': 'skrr-cli (daemon install)' },
|
|
147
|
+
}).then((res) => {
|
|
148
|
+
response = res;
|
|
149
|
+
// Injected transports can ignore AbortSignal. Dispose a response that
|
|
150
|
+
// arrives after our deadline rather than leaving its connection open.
|
|
151
|
+
if (controller.signal.aborted)
|
|
152
|
+
cancelBody(controller.signal.reason);
|
|
153
|
+
return res;
|
|
115
154
|
});
|
|
155
|
+
const downloaded = (async () => {
|
|
156
|
+
response = await requested;
|
|
157
|
+
controller.signal.throwIfAborted();
|
|
158
|
+
if (!response.ok)
|
|
159
|
+
throw new Error(`returned HTTP ${response.status}`);
|
|
160
|
+
const contentLength = response.headers.get('content-length');
|
|
161
|
+
if (contentLength && /^\d+$/.test(contentLength.trim()) && Number(contentLength) > maxBytes) {
|
|
162
|
+
throw new DownloadSizeLimitError(maxBytes);
|
|
163
|
+
}
|
|
164
|
+
if (!response.body)
|
|
165
|
+
return Buffer.alloc(0);
|
|
166
|
+
reader = response.body.getReader();
|
|
167
|
+
// Grow one buffer rather than retaining a separate allocation per chunk.
|
|
168
|
+
// A producer emitting tiny chunks must not defeat the byte ceiling with
|
|
169
|
+
// unbounded chunk metadata.
|
|
170
|
+
let bytes = Buffer.alloc(0);
|
|
171
|
+
let receivedBytes = 0;
|
|
172
|
+
for (;;) {
|
|
173
|
+
const { done, value } = await reader.read();
|
|
174
|
+
controller.signal.throwIfAborted();
|
|
175
|
+
if (done)
|
|
176
|
+
break;
|
|
177
|
+
if (value.byteLength > maxBytes - receivedBytes)
|
|
178
|
+
throw new DownloadSizeLimitError(maxBytes);
|
|
179
|
+
const neededBytes = receivedBytes + value.byteLength;
|
|
180
|
+
if (neededBytes > bytes.byteLength) {
|
|
181
|
+
const nextBytes = Buffer.allocUnsafe(Math.min(maxBytes, Math.max(64 * 1_024, bytes.byteLength * 2, neededBytes)));
|
|
182
|
+
bytes.copy(nextBytes, 0, 0, receivedBytes);
|
|
183
|
+
bytes = nextBytes;
|
|
184
|
+
}
|
|
185
|
+
bytes.set(value, receivedBytes);
|
|
186
|
+
receivedBytes += value.byteLength;
|
|
187
|
+
}
|
|
188
|
+
return bytes.subarray(0, receivedBytes);
|
|
189
|
+
})();
|
|
190
|
+
// Race the entire transfer once: racing each read retains an abort-promise
|
|
191
|
+
// reaction per chunk until the download ends.
|
|
192
|
+
return await Promise.race([downloaded, aborted]);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
controller.abort(err);
|
|
196
|
+
throw err;
|
|
116
197
|
}
|
|
117
198
|
finally {
|
|
118
199
|
clearTimeout(timer);
|
|
200
|
+
signal?.removeEventListener('abort', onCallerAbort);
|
|
201
|
+
controller.signal.removeEventListener('abort', onAbort);
|
|
202
|
+
reader?.releaseLock();
|
|
119
203
|
}
|
|
120
204
|
}
|
|
205
|
+
function errorMessage(error) {
|
|
206
|
+
return error instanceof Error ? error.message : String(error);
|
|
207
|
+
}
|
|
121
208
|
/** Fetch `<feedBase>/latest/daemon-version.json` and verify its signature. */
|
|
122
209
|
async function fetchVerifiedDaemonManifest(opts = {}) {
|
|
123
210
|
const env = opts.env ?? process.env;
|
|
@@ -125,19 +212,16 @@ async function fetchVerifiedDaemonManifest(opts = {}) {
|
|
|
125
212
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
126
213
|
const keys = opts.keys ?? auth_core_1.OVERSKY_RELEASE_PUBLIC_KEYS;
|
|
127
214
|
const url = `${feedBase}/latest/daemon-version.json`;
|
|
128
|
-
let
|
|
215
|
+
let text;
|
|
129
216
|
try {
|
|
130
|
-
|
|
217
|
+
const bytes = await getBytesWithTimeout(url, MANIFEST_TIMEOUT_MS, MAX_MANIFEST_BYTES, fetchImpl, opts.signal);
|
|
218
|
+
text = bytes.toString('utf8');
|
|
131
219
|
}
|
|
132
220
|
catch (err) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return fail('manifest_unreachable', `${url}
|
|
137
|
-
}
|
|
138
|
-
const text = await res.text();
|
|
139
|
-
if (text.length > MAX_MANIFEST_BYTES) {
|
|
140
|
-
return fail('manifest_malformed', 'release manifest is implausibly large');
|
|
221
|
+
if (err instanceof DownloadSizeLimitError) {
|
|
222
|
+
return fail('manifest_malformed', 'release manifest is implausibly large');
|
|
223
|
+
}
|
|
224
|
+
return fail('manifest_unreachable', `${url}: ${errorMessage(err)}`);
|
|
141
225
|
}
|
|
142
226
|
let manifest;
|
|
143
227
|
try {
|
|
@@ -172,7 +256,7 @@ async function installDaemon(opts = {}) {
|
|
|
172
256
|
if (!platformKey) {
|
|
173
257
|
return fail('unsupported_platform', `no daemon build for ${process.platform}-${process.arch}`);
|
|
174
258
|
}
|
|
175
|
-
const fetched = await fetchVerifiedDaemonManifest({ env, feedBase, fetchImpl, keys });
|
|
259
|
+
const fetched = await fetchVerifiedDaemonManifest({ env, feedBase, fetchImpl, keys, signal: opts.signal });
|
|
176
260
|
if (!('manifest' in fetched))
|
|
177
261
|
return fetched;
|
|
178
262
|
const { manifest } = fetched;
|
|
@@ -193,18 +277,10 @@ async function installDaemon(opts = {}) {
|
|
|
193
277
|
}
|
|
194
278
|
let bytes;
|
|
195
279
|
try {
|
|
196
|
-
|
|
197
|
-
if (!res.ok) {
|
|
198
|
-
return fail('download_failed', `${artifact.url} returned HTTP ${res.status}`);
|
|
199
|
-
}
|
|
200
|
-
const buf = Buffer.from(await res.arrayBuffer());
|
|
201
|
-
if (buf.byteLength > MAX_ARTIFACT_BYTES) {
|
|
202
|
-
return fail('download_failed', 'downloaded artifact exceeds the size ceiling');
|
|
203
|
-
}
|
|
204
|
-
bytes = buf;
|
|
280
|
+
bytes = await getBytesWithTimeout(artifact.url, ARTIFACT_TIMEOUT_MS, MAX_ARTIFACT_BYTES, fetchImpl, opts.signal);
|
|
205
281
|
}
|
|
206
282
|
catch (err) {
|
|
207
|
-
return fail('download_failed', `${artifact.url}: ${err
|
|
283
|
+
return fail('download_failed', `${artifact.url}: ${errorMessage(err)}`);
|
|
208
284
|
}
|
|
209
285
|
const actual = (0, node_crypto_1.createHash)('sha256').update(bytes).digest('hex');
|
|
210
286
|
if (actual !== artifact.sha256.toLowerCase()) {
|
|
@@ -41,6 +41,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
41
41
|
const first_party_harness_1 = require("./first-party-harness");
|
|
42
42
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
43
43
|
const harness_tiers_1 = require("./harness-tiers");
|
|
44
|
+
const first_party_harness_update_1 = require("./first-party-harness-update");
|
|
44
45
|
const first_party_harness_managed_1 = require("./first-party-harness-managed");
|
|
45
46
|
const first_party_harness_project_trust_1 = require("./first-party-harness-project-trust");
|
|
46
47
|
/**
|
|
@@ -103,7 +104,7 @@ function releaseChannelCheck(env) {
|
|
|
103
104
|
name: 'release-channel',
|
|
104
105
|
status: 'ok',
|
|
105
106
|
detail: `${channel} — ${canonical}`,
|
|
106
|
-
remedy: '
|
|
107
|
+
remedy: 'Run `skrr code update --check` to verify the published release, or fetch that URL to inspect it.' +
|
|
107
108
|
(alsoTried.length ? ` If it has none, an installer also tries ${alsoTried.join(', ')}.` : ''),
|
|
108
109
|
};
|
|
109
110
|
}
|
|
@@ -245,6 +246,34 @@ function engineCheck(env) {
|
|
|
245
246
|
remedy: `Install the engine (\`${auth_core_1.FIRST_PARTY_HARNESS.command} install\`), or set ${pathVar} to an existing build.`,
|
|
246
247
|
};
|
|
247
248
|
}
|
|
249
|
+
function engineUpdateCheck(env) {
|
|
250
|
+
const update = (0, first_party_harness_update_1.getLocalCodeUpdateStatus)(env);
|
|
251
|
+
const last = update.lastAttempt;
|
|
252
|
+
const completed = typeof last?.completedAt === 'number' ? new Date(last.completedAt) : null;
|
|
253
|
+
const completedAt = completed && Number.isFinite(completed.getTime()) ? completed.toISOString() : null;
|
|
254
|
+
const details = [
|
|
255
|
+
`installed ${update.installedVersion ?? (update.activePath ? 'version not recorded' : 'none')}`,
|
|
256
|
+
`owner ${update.owner}`,
|
|
257
|
+
update.installedVerified ? 'signed release verified' : null,
|
|
258
|
+
update.offeredVersion ? `latest ${update.offeredVersion}` : 'channel not checked recently',
|
|
259
|
+
last
|
|
260
|
+
? `last ${last.operation}: ${last.status}${completedAt ? ` at ${completedAt}` : ''}`
|
|
261
|
+
: 'no recorded update attempt',
|
|
262
|
+
]
|
|
263
|
+
.filter(Boolean)
|
|
264
|
+
.join('; ');
|
|
265
|
+
return {
|
|
266
|
+
name: 'engine-update',
|
|
267
|
+
status: last?.status === 'failed' || update.state === 'check-failed' ? 'warn' : 'ok',
|
|
268
|
+
detail: details,
|
|
269
|
+
remedy: last?.status === 'failed'
|
|
270
|
+
? `${last.error ?? last.code ?? 'Update failed.'} ${update.owner === 'managed' ? 'Retry with `skrr code update`.' : (update.message ?? update.command)}`
|
|
271
|
+
: (update.message ??
|
|
272
|
+
(update.state === 'update-available'
|
|
273
|
+
? 'Run `skrr code update` to install the signed release.'
|
|
274
|
+
: 'Run `skrr code update --check` to check the signed channel; `skrr code rollback` restores the previous verified release.')),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
248
277
|
function stateDirCheck(cwd, env) {
|
|
249
278
|
const project = projectStateDir(cwd);
|
|
250
279
|
const user = (0, first_party_harness_1.engineHome)(env);
|
|
@@ -863,6 +892,7 @@ function agentAttributionCheck() {
|
|
|
863
892
|
function runDoctor(cwd = process.cwd(), env = process.env) {
|
|
864
893
|
const checks = [
|
|
865
894
|
engineCheck(env),
|
|
895
|
+
engineUpdateCheck(env),
|
|
866
896
|
releaseChannelCheck(env),
|
|
867
897
|
stateDirCheck(cwd, env),
|
|
868
898
|
compatCheck(cwd, env),
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { BaseCommand } from '../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* `skrr code install` — install the engine from the terminal.
|
|
4
|
+
*
|
|
5
|
+
* ── Why this command exists ────────────────────────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* `skrr code`'s not-installed message offered exactly two routes and neither
|
|
8
|
+
* worked without a browser: "In the web app: the runtime menu … → Install", or
|
|
9
|
+
* point the engine-path override at a build you already have — which is the thing
|
|
10
|
+
* you are trying to obtain, and the route that gives up provenance. So the
|
|
11
|
+
* ordinary case for a coding harness (a dev box over SSH, a CI runner, a cloud
|
|
12
|
+
* VM) had no way in (dogfood OSK-276).
|
|
13
|
+
*
|
|
14
|
+
* Nothing underneath was missing. The daemon has performed the verified install
|
|
15
|
+
* since 2026-08-08, and `POST /api/daemons/:id/cli-installers/:provider/install`
|
|
16
|
+
* has fronted it since June. The install was API-exposed and CLI-unreachable —
|
|
17
|
+
* the shape the root `CLAUDE.md` names under CLI ↔ API parity: "an endpoint the
|
|
18
|
+
* API exposes must be reachable from `skrr`."
|
|
19
|
+
*
|
|
20
|
+
* So this adds a SURFACE and no mechanism. Same route the web app posts, same
|
|
21
|
+
* daemon job, same signature verification. A second install path would be a
|
|
22
|
+
* second thing to keep in step with the first, and the first is the one that
|
|
23
|
+
* verifies the signed manifest.
|
|
24
|
+
*/
|
|
25
|
+
export default class CodeInstall extends BaseCommand {
|
|
26
|
+
static description: string;
|
|
27
|
+
static examples: string[];
|
|
28
|
+
static flags: {
|
|
29
|
+
daemon: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
30
|
+
wait: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
31
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
32
|
+
};
|
|
33
|
+
run(): Promise<void>;
|
|
34
|
+
/** Which daemon the install targets, by the rule every daemon-picking command shares. */
|
|
35
|
+
private resolveTarget;
|
|
36
|
+
/**
|
|
37
|
+
* The daemon id to install through, or the refusal.
|
|
38
|
+
*
|
|
39
|
+
* Refuses rather than guessing when several are active. Picking one would mean
|
|
40
|
+
* installing a hundred megabytes onto a machine the user was not thinking
|
|
41
|
+
* about. The candidates are CONNECTED daemons by the id the installer routes
|
|
42
|
+
* take (`listConnectedDaemons`), so the error names values that work — the
|
|
43
|
+
* Daemon resource list `skrr list-daemons` prints is a different id space
|
|
44
|
+
* (OSK-9993).
|
|
45
|
+
*
|
|
46
|
+
* A Dedicated Runtime's own machine is refused here, before any request. The
|
|
47
|
+
* daemon and the API refuse it too; saying so locally is what keeps the answer
|
|
48
|
+
* the same whether or not this machine's daemon is reachable, and whether or
|
|
49
|
+
* not it has published its id. Another of the owner's machines, named with
|
|
50
|
+
* `--daemon`, is not this machine and goes to the API like anywhere else.
|
|
51
|
+
*/
|
|
52
|
+
private installTarget;
|
|
53
|
+
/**
|
|
54
|
+
* What the daemon says about this provider, when it started no job.
|
|
55
|
+
*
|
|
56
|
+
* Read from the SAME list route the web app uses, so the CLI reports the
|
|
57
|
+
* daemon's own words rather than inventing an explanation for silence. Best
|
|
58
|
+
* effort: a failure here means falling back to the honest "does not say why",
|
|
59
|
+
* never to a guess.
|
|
60
|
+
*/
|
|
61
|
+
private installerNote;
|
|
62
|
+
/**
|
|
63
|
+
* Poll the job until it settles, printing each new log line.
|
|
64
|
+
*
|
|
65
|
+
* The daemon already writes a progress log — "Fetching the signed release
|
|
66
|
+
* manifest", "Verified and installed …" — and it is the only account
|
|
67
|
+
* of a hundred-megabyte download the user is waiting on. Printing only the
|
|
68
|
+
* lines not yet seen keeps a poll loop from repeating the whole log.
|
|
69
|
+
*/
|
|
70
|
+
private followJob;
|
|
71
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
5
|
+
const base_command_1 = require("../base-command");
|
|
6
|
+
const api_fetch_1 = require("./api-fetch");
|
|
7
|
+
const daemon_target_1 = require("./daemon-target");
|
|
8
|
+
const first_party_harness_1 = require("./first-party-harness");
|
|
9
|
+
/**
|
|
10
|
+
* `skrr code install` — install the engine from the terminal.
|
|
11
|
+
*
|
|
12
|
+
* ── Why this command exists ────────────────────────────────────────────────
|
|
13
|
+
*
|
|
14
|
+
* `skrr code`'s not-installed message offered exactly two routes and neither
|
|
15
|
+
* worked without a browser: "In the web app: the runtime menu … → Install", or
|
|
16
|
+
* point the engine-path override at a build you already have — which is the thing
|
|
17
|
+
* you are trying to obtain, and the route that gives up provenance. So the
|
|
18
|
+
* ordinary case for a coding harness (a dev box over SSH, a CI runner, a cloud
|
|
19
|
+
* VM) had no way in (dogfood OSK-276).
|
|
20
|
+
*
|
|
21
|
+
* Nothing underneath was missing. The daemon has performed the verified install
|
|
22
|
+
* since 2026-08-08, and `POST /api/daemons/:id/cli-installers/:provider/install`
|
|
23
|
+
* has fronted it since June. The install was API-exposed and CLI-unreachable —
|
|
24
|
+
* the shape the root `CLAUDE.md` names under CLI ↔ API parity: "an endpoint the
|
|
25
|
+
* API exposes must be reachable from `skrr`."
|
|
26
|
+
*
|
|
27
|
+
* So this adds a SURFACE and no mechanism. Same route the web app posts, same
|
|
28
|
+
* daemon job, same signature verification. A second install path would be a
|
|
29
|
+
* second thing to keep in step with the first, and the first is the one that
|
|
30
|
+
* verifies the signed manifest.
|
|
31
|
+
*/
|
|
32
|
+
class CodeInstall extends base_command_1.BaseCommand {
|
|
33
|
+
static description = `Install the ${auth_core_1.FIRST_PARTY_HARNESS.displayName} engine on this machine, through the daemon`;
|
|
34
|
+
static examples = [
|
|
35
|
+
'<%= config.bin %> code install',
|
|
36
|
+
'<%= config.bin %> code install --daemon <daemon-id>',
|
|
37
|
+
'<%= config.bin %> code install --json',
|
|
38
|
+
];
|
|
39
|
+
static flags = {
|
|
40
|
+
daemon: core_1.Flags.string({
|
|
41
|
+
description: 'Which daemon installs it. Defaults to this machine on a Dedicated Runtime, otherwise to the only active one; required when you have several.',
|
|
42
|
+
}),
|
|
43
|
+
wait: core_1.Flags.boolean({
|
|
44
|
+
default: true,
|
|
45
|
+
allowNo: true,
|
|
46
|
+
description: 'Follow the install job until it finishes. --no-wait returns the job id.',
|
|
47
|
+
}),
|
|
48
|
+
json: core_1.Flags.boolean({ description: 'Emit the result as JSON' }),
|
|
49
|
+
};
|
|
50
|
+
async run() {
|
|
51
|
+
const { flags } = await this.parse(CodeInstall);
|
|
52
|
+
const daemonId = this.installTarget(await this.resolveTarget(flags.daemon));
|
|
53
|
+
let started;
|
|
54
|
+
try {
|
|
55
|
+
started = await (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/${PROVIDER}/install`, { method: 'POST', body: {}, credential: this.resolvedCredential });
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
this.handleApiError(err);
|
|
59
|
+
}
|
|
60
|
+
// The daemon distinguishes three refusals, and only one of them attaches a
|
|
61
|
+
// job worth following:
|
|
62
|
+
//
|
|
63
|
+
// - `alreadyInstalled` carries the LAST job the provider ran — context, not
|
|
64
|
+
// this request's work. Following it replays a stale outcome as though it
|
|
65
|
+
// had just happened: `code install` on an engine one release behind
|
|
66
|
+
// reprinted the previous update's log tail verbatim and reported that job
|
|
67
|
+
// as its result (OSK-10650). The right answer is the daemon's own
|
|
68
|
+
// installDisabledReason plus where the update actually comes from.
|
|
69
|
+
// - `alreadyRunning` carries the LIVE job — following it attaches the user
|
|
70
|
+
// to work that is really in flight, which is what they asked for.
|
|
71
|
+
// - `started` carries the job this request began.
|
|
72
|
+
if (started.alreadyInstalled) {
|
|
73
|
+
if (flags.json) {
|
|
74
|
+
this.log(JSON.stringify({ daemonId, ...started }, null, 2));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const state = started.provider;
|
|
78
|
+
const reason = state?.installDisabledReason?.trim();
|
|
79
|
+
const version = state?.version?.trim();
|
|
80
|
+
this.log(`Nothing to do on ${daemonId} — ${reason || 'Already installed.'}${version ? ` (${version})` : ''}`);
|
|
81
|
+
if (state?.updatable === true) {
|
|
82
|
+
this.log(`To update it: ${this.config.bin} code update`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const why = state?.updateDisabledReason?.trim();
|
|
86
|
+
if (why)
|
|
87
|
+
this.log(`Updating: ${why}`);
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const jobId = started.job?.id;
|
|
92
|
+
// A 202 carrying NO job is not a queued install. It is the daemon
|
|
93
|
+
// acknowledging the call and returning nothing, and the route builds its
|
|
94
|
+
// body as `{ daemonId, ...result }` — so an empty result and a real one look
|
|
95
|
+
// identical from here. Dogfooded (OSK-287): this printed "Install requested"
|
|
96
|
+
// and exited 0 against a daemon that answered an unrelated RPC in 83ms and
|
|
97
|
+
// then installed nothing for 130 seconds. Reporting success for an empty
|
|
98
|
+
// acknowledgement is what let that reach a user as silence.
|
|
99
|
+
if (!jobId) {
|
|
100
|
+
if (flags.json) {
|
|
101
|
+
this.log(JSON.stringify({ daemonId, ...started }, null, 2));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// A no-op is not a failure. The daemon says WHY it started nothing, and
|
|
105
|
+
// the commonest reason is the happy one — the engine is already there.
|
|
106
|
+
//
|
|
107
|
+
// This branch used to treat every job-less response as "nothing has been
|
|
108
|
+
// shown to start", which was right while the control-RPC relay dropped
|
|
109
|
+
// every reply (OSK-287) and wrong the moment it was fixed: with real
|
|
110
|
+
// payloads flowing, running `code install` twice would have reported a
|
|
111
|
+
// problem for the ordinary case of already having the engine. That is the
|
|
112
|
+
// same defect this dogfood run kept finding in other people's error copy —
|
|
113
|
+
// a message asserting a cause it has not observed — so it does not get to
|
|
114
|
+
// survive in mine.
|
|
115
|
+
const reason = await this.installerNote(daemonId);
|
|
116
|
+
if (reason) {
|
|
117
|
+
this.log(`Nothing to do on ${daemonId} — ${reason}`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.failWithCliError({
|
|
121
|
+
message: `The daemon on ${daemonId} accepted the request and returned no ` +
|
|
122
|
+
'install job, and does not say why. Nothing has been shown to start. ' +
|
|
123
|
+
`Check what it offers with \`${this.config.bin} harnesses installers --daemon ${daemonId}\`, and ` +
|
|
124
|
+
`\`${this.config.bin} code doctor\` here once you expect it to have landed.`,
|
|
125
|
+
code: 'INSTALL_NOT_TRACKED',
|
|
126
|
+
exit: 1,
|
|
127
|
+
// Retryable: the daemon is reachable and the call was accepted, so the
|
|
128
|
+
// same command may well work once whatever swallowed it is understood.
|
|
129
|
+
retryable: true,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (!flags.wait) {
|
|
133
|
+
if (flags.json) {
|
|
134
|
+
this.log(JSON.stringify({ daemonId, ...started }, null, 2));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
this.log(`Install started on ${daemonId} — job ${jobId}.`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const job = await this.followJob(daemonId, jobId, flags.json === true);
|
|
141
|
+
if (flags.json) {
|
|
142
|
+
this.log(JSON.stringify({ daemonId, job }, null, 2));
|
|
143
|
+
}
|
|
144
|
+
if (job.status === 'failed') {
|
|
145
|
+
// The daemon's own message, verbatim. It carries the failure CODE
|
|
146
|
+
// (`runSignedInstall` puts it there deliberately) because "the feed is
|
|
147
|
+
// down" and "the signature did not verify" need very different responses.
|
|
148
|
+
this.failWithCliError({
|
|
149
|
+
message: job.error || 'The install failed and the daemon gave no reason.',
|
|
150
|
+
code: 'INSTALL_FAILED',
|
|
151
|
+
exit: 1,
|
|
152
|
+
retryable: false,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
if (!flags.json) {
|
|
156
|
+
this.log(`${auth_core_1.FIRST_PARTY_HARNESS.displayName} installed. Run \`${this.config.bin} code\` to start it.`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Which daemon the install targets, by the rule every daemon-picking command shares. */
|
|
160
|
+
async resolveTarget(explicit) {
|
|
161
|
+
try {
|
|
162
|
+
return await (0, daemon_target_1.resolveDaemonTarget)({
|
|
163
|
+
explicit,
|
|
164
|
+
local: (0, daemon_target_1.localMachine)({ baseURL: this.cliConfig?.baseURL }),
|
|
165
|
+
listActiveDaemons: () => (0, daemon_target_1.listConnectedDaemons)(this.resolvedCredential),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
this.handleApiError(err);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The daemon id to install through, or the refusal.
|
|
174
|
+
*
|
|
175
|
+
* Refuses rather than guessing when several are active. Picking one would mean
|
|
176
|
+
* installing a hundred megabytes onto a machine the user was not thinking
|
|
177
|
+
* about. The candidates are CONNECTED daemons by the id the installer routes
|
|
178
|
+
* take (`listConnectedDaemons`), so the error names values that work — the
|
|
179
|
+
* Daemon resource list `skrr list-daemons` prints is a different id space
|
|
180
|
+
* (OSK-9993).
|
|
181
|
+
*
|
|
182
|
+
* A Dedicated Runtime's own machine is refused here, before any request. The
|
|
183
|
+
* daemon and the API refuse it too; saying so locally is what keeps the answer
|
|
184
|
+
* the same whether or not this machine's daemon is reachable, and whether or
|
|
185
|
+
* not it has published its id. Another of the owner's machines, named with
|
|
186
|
+
* `--daemon`, is not this machine and goes to the API like anywhere else.
|
|
187
|
+
*/
|
|
188
|
+
installTarget(target) {
|
|
189
|
+
if (target.kind === 'daemon')
|
|
190
|
+
return target.daemonId;
|
|
191
|
+
if (target.kind === 'this-machine') {
|
|
192
|
+
this.failWithCliError({
|
|
193
|
+
message: target.explicit
|
|
194
|
+
? first_party_harness_1.DEDICATED_RUNTIME_ENGINE_MESSAGE
|
|
195
|
+
: `${first_party_harness_1.DEDICATED_RUNTIME_ENGINE_MESSAGE} This command installs on the machine it runs on; ` +
|
|
196
|
+
'to install on another of your machines, name it with --daemon.',
|
|
197
|
+
code: 'DEDICATED_RUNTIME_ENGINE_SHIPS_WITH_IMAGE',
|
|
198
|
+
exit: 1,
|
|
199
|
+
retryable: false,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (target.kind === 'none') {
|
|
203
|
+
this.failWithCliError({
|
|
204
|
+
message: 'No daemon is connected for this account, so nothing can perform the install. ' +
|
|
205
|
+
`Start the background service on this computer with \`${this.config.bin} daemon start\`, then run this again.`,
|
|
206
|
+
code: 'NO_DAEMON',
|
|
207
|
+
exit: 1,
|
|
208
|
+
retryable: false,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
const { daemons } = target;
|
|
212
|
+
const choices = daemons
|
|
213
|
+
.slice(0, 10)
|
|
214
|
+
.map((d) => ` ${d.id} ${d.name || '—'}`)
|
|
215
|
+
.join('\n');
|
|
216
|
+
this.failWithCliError({
|
|
217
|
+
message: `${daemons.length} connected daemons — say which machine should install the engine:\n${choices}\n` +
|
|
218
|
+
`Pass one with --daemon, or see each daemon id in the DAEMON ID column of \`${this.config.bin} harnesses list\`.`,
|
|
219
|
+
code: 'AMBIGUOUS_DAEMON',
|
|
220
|
+
exit: 1,
|
|
221
|
+
retryable: false,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* What the daemon says about this provider, when it started no job.
|
|
226
|
+
*
|
|
227
|
+
* Read from the SAME list route the web app uses, so the CLI reports the
|
|
228
|
+
* daemon's own words rather than inventing an explanation for silence. Best
|
|
229
|
+
* effort: a failure here means falling back to the honest "does not say why",
|
|
230
|
+
* never to a guess.
|
|
231
|
+
*/
|
|
232
|
+
async installerNote(daemonId) {
|
|
233
|
+
try {
|
|
234
|
+
const res = await (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers`, { credential: this.resolvedCredential });
|
|
235
|
+
// Any accepted spelling: a daemon lists the provider the way it registered it,
|
|
236
|
+
// and during a rename's alias window one that predates or postdates the flip
|
|
237
|
+
// spells it differently.
|
|
238
|
+
const row = (res.providers ?? []).find((p) => (0, auth_core_1.isFirstPartyHarnessProvider)(p.provider));
|
|
239
|
+
const reason = row?.installDisabledReason?.trim();
|
|
240
|
+
if (!reason)
|
|
241
|
+
return null;
|
|
242
|
+
// The version is what turns "Already installed." into something a reader
|
|
243
|
+
// can act on — it is the difference between reassurance and a shrug.
|
|
244
|
+
return row?.version ? `${reason} (${row.version})` : reason;
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Poll the job until it settles, printing each new log line.
|
|
252
|
+
*
|
|
253
|
+
* The daemon already writes a progress log — "Fetching the signed release
|
|
254
|
+
* manifest", "Verified and installed …" — and it is the only account
|
|
255
|
+
* of a hundred-megabyte download the user is waiting on. Printing only the
|
|
256
|
+
* lines not yet seen keeps a poll loop from repeating the whole log.
|
|
257
|
+
*/
|
|
258
|
+
async followJob(daemonId, jobId, quiet) {
|
|
259
|
+
const deadline = Date.now() + MAX_WAIT_MS;
|
|
260
|
+
let printed = 0;
|
|
261
|
+
for (;;) {
|
|
262
|
+
let res;
|
|
263
|
+
try {
|
|
264
|
+
res = await (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers/jobs/${encodeURIComponent(jobId)}`, { credential: this.resolvedCredential });
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
this.handleApiError(err);
|
|
268
|
+
}
|
|
269
|
+
const job = res.job;
|
|
270
|
+
if (!job) {
|
|
271
|
+
this.failWithCliError({
|
|
272
|
+
message: `The daemon no longer has install job ${jobId}.`,
|
|
273
|
+
code: 'JOB_MISSING',
|
|
274
|
+
exit: 1,
|
|
275
|
+
retryable: false,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
if (!quiet) {
|
|
279
|
+
for (const line of (job.log ?? []).slice(printed))
|
|
280
|
+
this.logToStderr(` ${line}`);
|
|
281
|
+
printed = (job.log ?? []).length;
|
|
282
|
+
}
|
|
283
|
+
if (job.status === 'succeeded' || job.status === 'failed')
|
|
284
|
+
return job;
|
|
285
|
+
if (Date.now() > deadline) {
|
|
286
|
+
// NOT a failure: the daemon is still working, and saying "install
|
|
287
|
+
// failed" because this process ran out of patience would be a lie that
|
|
288
|
+
// sends someone to debug a download that is going fine.
|
|
289
|
+
this.failWithCliError({
|
|
290
|
+
message: `Still installing after ${Math.round(MAX_WAIT_MS / 60000)} minutes — the daemon is ` +
|
|
291
|
+
`still working on job ${jobId}. Check \`${this.config.bin} code doctor\` in a while.`,
|
|
292
|
+
code: 'INSTALL_PENDING',
|
|
293
|
+
exit: 1,
|
|
294
|
+
retryable: true,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
exports.default = CodeInstall;
|
|
302
|
+
/**
|
|
303
|
+
* The daemon's id for skrr's own engine — the CANONICAL spelling, read from the
|
|
304
|
+
* identity module rather than written here. The install route accepts every
|
|
305
|
+
* spelling; canonical is what the platform stores and returns.
|
|
306
|
+
*/
|
|
307
|
+
const PROVIDER = auth_core_1.FIRST_PARTY_HARNESS.provider;
|
|
308
|
+
/** A 100 MB download over a hotel connection is the case this has to survive. */
|
|
309
|
+
const MAX_WAIT_MS = 10 * 60_000;
|
|
310
|
+
const POLL_INTERVAL_MS = 2_000;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { BaseCommand } from '../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* `skrr code update` — pull the engine's signed release down through the daemon.
|
|
4
|
+
*
|
|
5
|
+
* ── Why this command exists ────────────────────────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* The update verb has been API-exposed and daemon-implemented since OSK-10096 —
|
|
8
|
+
* `POST /api/daemons/:id/cli-installers/:provider/update` re-fetches the signed
|
|
9
|
+
* manifest and installs whatever the channel points at. But the only CLI that
|
|
10
|
+
* reached it was `skrr harnesses install <provider> --update`: a flag on a
|
|
11
|
+
* command a `code` user has no reason to read. On stable that left no
|
|
12
|
+
* discoverable path at all — `code install` answered "Already installed." with
|
|
13
|
+
* no next step, and the channel moving past the installed build was invisible
|
|
14
|
+
* from the surface that owns the engine (OSK-10650).
|
|
15
|
+
*
|
|
16
|
+
* So this adds a SURFACE and no mechanism. Same route `harnesses install
|
|
17
|
+
* --update` posts, same inventory pre-check, same 504 recovery — one update
|
|
18
|
+
* path, spelled where the engine's users look.
|
|
19
|
+
*/
|
|
20
|
+
export default class CodeUpdate extends BaseCommand {
|
|
21
|
+
static description: string;
|
|
22
|
+
static examples: string[];
|
|
23
|
+
static flags: {
|
|
24
|
+
daemon: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
25
|
+
wait: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
26
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
27
|
+
};
|
|
28
|
+
run(): Promise<void>;
|
|
29
|
+
/** Which daemon the update targets, by the rule every daemon-picking command shares. */
|
|
30
|
+
private resolveTarget;
|
|
31
|
+
/**
|
|
32
|
+
* The daemon id to update through, or the refusal — identical rules to
|
|
33
|
+
* `code install`: a Dedicated Runtime's own machine is image-managed, several
|
|
34
|
+
* connected daemons get named rather than guessed.
|
|
35
|
+
*/
|
|
36
|
+
private updateTarget;
|
|
37
|
+
/**
|
|
38
|
+
* Start the update, and survive a gateway that cut the request off — the same
|
|
39
|
+
* 504 recovery `harnesses install --update` uses: the relay can drop the
|
|
40
|
+
* answer while the daemon runs the job it started, so on a server-side
|
|
41
|
+
* failure we ask the daemon and follow the job it reports.
|
|
42
|
+
*/
|
|
43
|
+
private start;
|
|
44
|
+
/** Follow a job to its end, or report why it could not be followed (and return nothing). */
|
|
45
|
+
private follow;
|
|
46
|
+
}
|