@skrr-ai/cli 0.1.51 → 0.1.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-command.d.ts +7 -0
- package/dist/base-command.js +38 -8
- package/dist/commands/agents/chat.js +2 -1
- package/dist/commands/agents/create.js +21 -8
- package/dist/commands/agents/update.d.ts +55 -0
- package/dist/commands/agents/update.js +119 -23
- package/dist/commands/commitments/next-tick.js +9 -1
- package/dist/commands/context.js +2 -1
- package/dist/commands/daemon/config.js +16 -1
- package/dist/commands/doctor.js +8 -1
- package/dist/commands/login.d.ts +2 -1
- package/dist/commands/login.js +26 -7
- package/dist/commands/logout.js +3 -0
- package/dist/commands/machines/dedicated/show.js +25 -2
- package/dist/commands/tasks/approvals/list.d.ts +22 -0
- package/dist/commands/tasks/approvals/list.js +80 -3
- package/dist/commands/tasks/create.d.ts +5 -0
- package/dist/commands/tasks/create.js +28 -7
- package/dist/commands/tasks/start.d.ts +14 -0
- package/dist/commands/tasks/start.js +31 -1
- package/dist/commands/tasks/update.js +30 -3
- package/dist/commands/whoami.js +5 -3
- package/dist/lib/agent-config.d.ts +15 -0
- package/dist/lib/agent-config.js +46 -10
- package/dist/lib/agentic-stream.d.ts +21 -1
- package/dist/lib/agentic-stream.js +40 -6
- package/dist/lib/auth-failure.js +4 -1
- package/dist/lib/auth-storage.d.ts +24 -0
- package/dist/lib/auth-storage.js +348 -13
- package/dist/lib/cli-id.d.ts +22 -0
- package/dist/lib/cli-id.js +56 -0
- package/dist/lib/cli-identity.d.ts +49 -0
- package/dist/lib/cli-identity.js +159 -0
- package/dist/lib/commitments.d.ts +4 -0
- package/dist/lib/commitments.js +12 -1
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.js +15 -3
- package/dist/lib/cred-envelope.d.ts +12 -1
- package/dist/lib/cred-envelope.js +20 -3
- package/dist/lib/daemonBroker.d.ts +26 -4
- package/dist/lib/daemonBroker.js +82 -10
- package/dist/lib/daemonBrokerRefusal.d.ts +51 -0
- package/dist/lib/daemonBrokerRefusal.js +127 -0
- package/dist/lib/dedicated-machines.d.ts +53 -4
- package/dist/lib/dedicated-machines.js +95 -5
- package/dist/lib/delegated-cli.js +16 -0
- package/dist/lib/first-party-harness-broker.js +6 -4
- package/dist/lib/keychain.d.ts +39 -0
- package/dist/lib/keychain.js +254 -40
- package/dist/lib/label-ref.d.ts +34 -0
- package/dist/lib/label-ref.js +72 -1
- package/dist/lib/login.js +24 -12
- package/dist/lib/oauthLogin.js +2 -8
- package/dist/lib/refresh.js +6 -2
- package/dist/lib/tasks.js +33 -8
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +13 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +9 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +13 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +8 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3138 -3100
- package/oclif.manifest.json +26470 -26449
- package/package.json +1 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isAnsweredBrokerRefusal = isAnsweredBrokerRefusal;
|
|
4
|
+
exports.describeBrokerRefusal = describeBrokerRefusal;
|
|
5
|
+
/**
|
|
6
|
+
* True when a daemon ANSWERED the hand-off and refused it — as opposed to no
|
|
7
|
+
* daemon being there to ask (`no_bootstrap`, a stale descriptor nobody listens
|
|
8
|
+
* behind). This is the case the CLI must explain rather than reduce to "Not
|
|
9
|
+
* signed in": the person has a daemon, it said something specific, and that
|
|
10
|
+
* decides whether `skrr login` would even help.
|
|
11
|
+
*/
|
|
12
|
+
function isAnsweredBrokerRefusal(outcome) {
|
|
13
|
+
switch (outcome.reason) {
|
|
14
|
+
case 'no_bootstrap':
|
|
15
|
+
case 'bootstrap_parse':
|
|
16
|
+
case 'network':
|
|
17
|
+
return false;
|
|
18
|
+
case 'stale_bootstrap':
|
|
19
|
+
// A bare 401 is the daemon refusing a descriptor secret that rotated;
|
|
20
|
+
// re-reading the file next time heals it. A coded one is a real answer.
|
|
21
|
+
return Boolean(outcome.code);
|
|
22
|
+
default:
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Codes whose repair is signing the DAEMON in again (laptop only). */
|
|
27
|
+
const DAEMON_RELOGIN_CODES = new Set([
|
|
28
|
+
'DEVICE_PROOF_PIN_MISMATCH',
|
|
29
|
+
'SESSION_REVOKED',
|
|
30
|
+
'PRINCIPAL_UNRESOLVED',
|
|
31
|
+
'DAEMON_NOT_AUTHED',
|
|
32
|
+
]);
|
|
33
|
+
/** Codes whose repair is time. */
|
|
34
|
+
const TRANSIENT_CODES = new Set([
|
|
35
|
+
'DEVICE_PROOF_VERIFY_UNAVAILABLE',
|
|
36
|
+
'SERVICE_UNAVAILABLE',
|
|
37
|
+
'HANDOFF_RATE_LIMITED',
|
|
38
|
+
'RATE_LIMITED',
|
|
39
|
+
'UPSTREAM_UNREACHABLE',
|
|
40
|
+
]);
|
|
41
|
+
function statusAndCode(f) {
|
|
42
|
+
const parts = [typeof f.status === 'number' ? `HTTP ${f.status}` : '', f.code ?? ''].filter(Boolean);
|
|
43
|
+
return parts.length > 0 ? ` (${parts.join(' ')})` : '';
|
|
44
|
+
}
|
|
45
|
+
function sentence(detail) {
|
|
46
|
+
const d = (detail ?? '').trim();
|
|
47
|
+
if (!d)
|
|
48
|
+
return '';
|
|
49
|
+
return /[.!?]$/.test(d) ? ` ${d}` : ` ${d}.`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Describe a broker failure for a person, or null when there is nothing to
|
|
53
|
+
* say: no daemon was there to ask, or the failure already has its own message
|
|
54
|
+
* (`base_url_mismatch`, `rate_limited`).
|
|
55
|
+
*/
|
|
56
|
+
function describeBrokerRefusal(failure, bin = 'skrr') {
|
|
57
|
+
if (!isAnsweredBrokerRefusal(failure))
|
|
58
|
+
return null;
|
|
59
|
+
if (failure.reason === 'base_url_mismatch' || failure.reason === 'rate_limited')
|
|
60
|
+
return null;
|
|
61
|
+
const dedicatedGuest = failure.dedicatedGuest === true;
|
|
62
|
+
const code = failure.code ?? '';
|
|
63
|
+
const who = dedicatedGuest
|
|
64
|
+
? `This Dedicated Runtime's skrr service${failure.daemonId ? ` (daemon ${failure.daemonId})` : ''}`
|
|
65
|
+
: 'The skrr background service on this computer';
|
|
66
|
+
const refusal = failure.reason === 'timeout'
|
|
67
|
+
? `${who} did not answer the sign-in hand-off in time.`
|
|
68
|
+
: failure.refusedBy === 'server' || failure.reason === 'upstream_refused'
|
|
69
|
+
? `${who} asked the skrr server for a credential for this CLI, and the server refused${statusAndCode(failure)}.${sentence(failure.detail)}`
|
|
70
|
+
: `${who} could not hand this CLI a credential${statusAndCode(failure)}.${sentence(failure.detail)}`;
|
|
71
|
+
const transient = failure.reason === 'timeout' ||
|
|
72
|
+
TRANSIENT_CODES.has(code) ||
|
|
73
|
+
(failure.reason === 'server_error' && !DAEMON_RELOGIN_CODES.has(code));
|
|
74
|
+
let remedy;
|
|
75
|
+
if (dedicatedGuest) {
|
|
76
|
+
// Nobody signs in on a guest: the daemon's credential comes from the
|
|
77
|
+
// machine's attested bootstrap, and a tenant `skrr login` would only put a
|
|
78
|
+
// personal credential on a shared machine. Name the machine instead.
|
|
79
|
+
remedy = transient
|
|
80
|
+
? 'This is usually temporary; retry in a minute. Nobody signs in on a Dedicated Runtime — ' +
|
|
81
|
+
`if it persists, check the machine from your own computer with \`${bin} machines dedicated health-check <lease-id>\` ` +
|
|
82
|
+
`(\`${bin} machines dedicated list\` shows the lease id).`
|
|
83
|
+
: 'Nobody signs in on a Dedicated Runtime: its daemon holds the account. From your own computer, run ' +
|
|
84
|
+
`\`${bin} machines dedicated health-check <lease-id>\` (\`${bin} machines dedicated list\` shows the lease id), ` +
|
|
85
|
+
`and \`${bin} machines dedicated restart <lease-id>\` if the hand-off stays refused.`;
|
|
86
|
+
}
|
|
87
|
+
else if (code === 'DEVICE_PROOF_PIN_MISMATCH') {
|
|
88
|
+
remedy =
|
|
89
|
+
`Run \`${bin} daemon login\` to sign the background service in again — an interactive daemon login re-pins its device key — then retry. ` +
|
|
90
|
+
`\`${bin} login\` signs this CLI in on its own instead.`;
|
|
91
|
+
}
|
|
92
|
+
else if (DAEMON_RELOGIN_CODES.has(code) || failure.reason === 'daemon_not_authed') {
|
|
93
|
+
remedy =
|
|
94
|
+
`Run \`${bin} daemon login\` to sign the background service in again, then retry. ` +
|
|
95
|
+
`\`${bin} login\` signs this CLI in on its own instead.`;
|
|
96
|
+
}
|
|
97
|
+
else if (failure.reason === 'ci_token_refused') {
|
|
98
|
+
remedy =
|
|
99
|
+
`The background service is signed in with a CI token, which cannot sign a person in. Use \`${bin} login\`, ` +
|
|
100
|
+
`or \`${bin} create-token\` for a CI credential.`;
|
|
101
|
+
}
|
|
102
|
+
else if (transient) {
|
|
103
|
+
remedy =
|
|
104
|
+
`This is usually temporary; retry in a minute. \`${bin} daemon status\` and \`${bin} daemon logs\` show the service's side, ` +
|
|
105
|
+
`and \`${bin} login\` signs this CLI in without it.`;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
remedy =
|
|
109
|
+
`\`${bin} daemon status\` and \`${bin} daemon logs\` show the service's side. ` +
|
|
110
|
+
`\`${bin} login\` signs this CLI in without it.`;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
headline: refusal,
|
|
114
|
+
remedy,
|
|
115
|
+
transient,
|
|
116
|
+
dedicatedGuest,
|
|
117
|
+
broker: {
|
|
118
|
+
reason: failure.reason,
|
|
119
|
+
...(failure.refusedBy ? { refusedBy: failure.refusedBy } : {}),
|
|
120
|
+
...(typeof failure.status === 'number' ? { status: failure.status } : {}),
|
|
121
|
+
...(failure.code ? { code: failure.code } : {}),
|
|
122
|
+
...(failure.detail ? { detail: failure.detail } : {}),
|
|
123
|
+
...(failure.descriptorPath ? { descriptorPath: failure.descriptorPath } : {}),
|
|
124
|
+
...(failure.daemonId ? { daemonId: failure.daemonId } : {}),
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -111,8 +111,22 @@ export declare function describeDedicatedCheckpoint(lease: DedicatedLease | unde
|
|
|
111
111
|
* machine to the previous image. Files under the workspace survive both.
|
|
112
112
|
*/
|
|
113
113
|
export interface DedicatedImageStanding {
|
|
114
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* A newer APPROVED image exists that has not already failed on this machine.
|
|
116
|
+
* False is not "running the latest daemon": a release no approved image
|
|
117
|
+
* carries yet is not an update this machine can move to (OSK-12153).
|
|
118
|
+
*/
|
|
115
119
|
updateAvailable?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* The daemon the approved image carries, when the server can say: this
|
|
122
|
+
* machine runs that image and its daemon reported a version. Null otherwise.
|
|
123
|
+
*/
|
|
124
|
+
approvedImageDaemonVersion?: string | null;
|
|
125
|
+
/**
|
|
126
|
+
* Why `approvedImageDaemonVersion` is null, from a closed server vocabulary.
|
|
127
|
+
* Absent from a server older than this field.
|
|
128
|
+
*/
|
|
129
|
+
approvedImageDaemonUnknownReason?: 'image_superseded' | 'approval_unreadable' | 'daemon_unreported' | null;
|
|
116
130
|
/** When the older image stops being supported; null when there is no deadline. */
|
|
117
131
|
updateBy?: string | null;
|
|
118
132
|
lastUpdate?: {
|
|
@@ -121,8 +135,41 @@ export interface DedicatedImageStanding {
|
|
|
121
135
|
settledAt?: string | null;
|
|
122
136
|
} | null;
|
|
123
137
|
}
|
|
124
|
-
/**
|
|
125
|
-
|
|
138
|
+
/**
|
|
139
|
+
* The latest daemon release, as `show` read it from the signed release feed:
|
|
140
|
+
* a version, or why it could not be read. Never inferred from the machine.
|
|
141
|
+
*/
|
|
142
|
+
export type LatestDaemonRelease = {
|
|
143
|
+
version: string;
|
|
144
|
+
} | {
|
|
145
|
+
error: string;
|
|
146
|
+
};
|
|
147
|
+
/** -1 / 0 / 1, or null when either side is not a version. */
|
|
148
|
+
export declare function compareDaemonVersions(a: string, b: string): number | null;
|
|
149
|
+
/**
|
|
150
|
+
* Which daemon the approved image carries against the latest daemon release
|
|
151
|
+
* (OSK-12153). `image update: current` used to be the whole answer, while the
|
|
152
|
+
* daemon on the same machine said a newer release existed: "current" means no
|
|
153
|
+
* newer APPROVED image, and a release that no approved image carries yet is not
|
|
154
|
+
* something this machine can move to. Both halves are said, and an unknown
|
|
155
|
+
* half is said to be unknown rather than left out.
|
|
156
|
+
*/
|
|
157
|
+
export declare function describeDedicatedDaemonRelease(image: DedicatedImageStanding, latest: LatestDaemonRelease | null | undefined): string | null;
|
|
158
|
+
/**
|
|
159
|
+
* One line saying where a machine's image stands and what the owner can do.
|
|
160
|
+
*
|
|
161
|
+
* "current" is claimed only when it is KNOWN: no newer approved image AND this
|
|
162
|
+
* machine's own daemon is at the latest release (OSK-12153). A guest never
|
|
163
|
+
* updates its own daemon, so "no newer approved image" beside a daemon that is
|
|
164
|
+
* behind the latest release is a different — and worse — state than "current",
|
|
165
|
+
* and the line says so instead of collapsing toward the reassuring reading.
|
|
166
|
+
* With the release feed unread, or no daemon version, it says only what is
|
|
167
|
+
* known: there is no newer approved image.
|
|
168
|
+
*/
|
|
169
|
+
export declare function describeDedicatedImageStanding(image: DedicatedImageStanding, leaseId?: string, opts?: {
|
|
170
|
+
daemonVersion?: string | null;
|
|
171
|
+
latestDaemonRelease?: LatestDaemonRelease | null;
|
|
172
|
+
}): string;
|
|
126
173
|
/**
|
|
127
174
|
* A deleted workspace: restorable into a new machine until `recoverableUntil`,
|
|
128
175
|
* then every copy is deleted and `completedAt` records it. `withheld`: an unpaid
|
|
@@ -527,7 +574,9 @@ export declare function dedicatedSpendSummary(lease: DedicatedLease): string;
|
|
|
527
574
|
*/
|
|
528
575
|
/** One line saying what a deletion still allows. */
|
|
529
576
|
export declare function describeDedicatedDeletion(deletion: DedicatedLeaseDeletion, leaseId?: string): string;
|
|
530
|
-
export declare function describeDedicatedLease(lease: DedicatedLease, harnesses?: readonly Harness[]
|
|
577
|
+
export declare function describeDedicatedLease(lease: DedicatedLease, harnesses?: readonly Harness[], opts?: {
|
|
578
|
+
latestDaemonRelease?: LatestDaemonRelease | null;
|
|
579
|
+
}): string[];
|
|
531
580
|
/** "2026-09-30 14:00 UTC (in 17 days)" — a deadline readable without converting it. */
|
|
532
581
|
export declare function formatDedicatedDeadline(value: string | null | undefined, now?: Date): string;
|
|
533
582
|
/**
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEDICATED_FILE_MAX_BYTES = exports.DEDICATED_FILE_CHUNK_BYTES = exports.DEDICATED_EXEC_DEFAULT_TIMEOUT_MS = exports.DEDICATED_EXEC_MAX_TIMEOUT_MS = exports.DEDICATED_MACHINE_PRODUCT = exports.DEDICATED_LEASE_ACTIONS = void 0;
|
|
4
4
|
exports.describeDedicatedCheckpoint = describeDedicatedCheckpoint;
|
|
5
|
+
exports.compareDaemonVersions = compareDaemonVersions;
|
|
6
|
+
exports.describeDedicatedDaemonRelease = describeDedicatedDaemonRelease;
|
|
5
7
|
exports.describeDedicatedImageStanding = describeDedicatedImageStanding;
|
|
6
8
|
exports.dedicatedRuntimeCreateInput = dedicatedRuntimeCreateInput;
|
|
7
9
|
exports.isDedicatedRepositoryRef = isDedicatedRepositoryRef;
|
|
@@ -73,8 +75,81 @@ function describeDedicatedCheckpoint(lease) {
|
|
|
73
75
|
return null;
|
|
74
76
|
return (0, data_provider_1.describeMachineCheckpointOutcome)(operation.checkpoint, { action: operation.action });
|
|
75
77
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
function versionParts(version) {
|
|
79
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(String(version || '').trim());
|
|
80
|
+
return match ? match.slice(1, 4).map(Number) : null;
|
|
81
|
+
}
|
|
82
|
+
/** -1 / 0 / 1, or null when either side is not a version. */
|
|
83
|
+
function compareDaemonVersions(a, b) {
|
|
84
|
+
const left = versionParts(a);
|
|
85
|
+
const right = versionParts(b);
|
|
86
|
+
if (!left || !right)
|
|
87
|
+
return null;
|
|
88
|
+
for (let i = 0; i < 3; i += 1) {
|
|
89
|
+
if (left[i] !== right[i])
|
|
90
|
+
return left[i] < right[i] ? -1 : 1;
|
|
91
|
+
}
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Which daemon the approved image carries against the latest daemon release
|
|
96
|
+
* (OSK-12153). `image update: current` used to be the whole answer, while the
|
|
97
|
+
* daemon on the same machine said a newer release existed: "current" means no
|
|
98
|
+
* newer APPROVED image, and a release that no approved image carries yet is not
|
|
99
|
+
* something this machine can move to. Both halves are said, and an unknown
|
|
100
|
+
* half is said to be unknown rather than left out.
|
|
101
|
+
*/
|
|
102
|
+
function describeDedicatedDaemonRelease(image, latest) {
|
|
103
|
+
if (!latest)
|
|
104
|
+
return null;
|
|
105
|
+
const carried = image.approvedImageDaemonVersion || null;
|
|
106
|
+
const latestText = 'version' in latest
|
|
107
|
+
? `latest release skrrd ${latest.version}`
|
|
108
|
+
: `latest release unknown (${latest.error})`;
|
|
109
|
+
if (!carried) {
|
|
110
|
+
return `${approvedImageDaemonUnknownText(image)} · ${latestText}`;
|
|
111
|
+
}
|
|
112
|
+
if (!('version' in latest)) {
|
|
113
|
+
return `approved image carries skrrd ${carried} · ${latestText}`;
|
|
114
|
+
}
|
|
115
|
+
const compared = compareDaemonVersions(carried, latest.version);
|
|
116
|
+
if (compared === 0) {
|
|
117
|
+
return `approved image carries skrrd ${carried}, the latest release`;
|
|
118
|
+
}
|
|
119
|
+
if (compared === -1) {
|
|
120
|
+
return `approved image carries skrrd ${carried} · latest release skrrd ${latest.version} is not in an approved image yet, so there is nothing to update to`;
|
|
121
|
+
}
|
|
122
|
+
return `approved image carries skrrd ${carried} · ${latestText}`;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Why the server could not say which daemon the approved image carries, in
|
|
126
|
+
* words an owner can act on (OSK-12153). A bare "unknown" named no cause and no
|
|
127
|
+
* next step.
|
|
128
|
+
*/
|
|
129
|
+
function approvedImageDaemonUnknownText(image) {
|
|
130
|
+
switch (image.approvedImageDaemonUnknownReason) {
|
|
131
|
+
case 'image_superseded':
|
|
132
|
+
return 'this machine runs an older image than the approved one, so which skrrd the approved image carries is seen once it moves onto it';
|
|
133
|
+
case 'approval_unreadable':
|
|
134
|
+
return "the server cannot compare this machine's image with the approved one, so it cannot say which skrrd the approved image carries";
|
|
135
|
+
case 'daemon_unreported':
|
|
136
|
+
return 'this machine runs the approved image, but its daemon has not reported a version yet';
|
|
137
|
+
default:
|
|
138
|
+
return 'the server did not say which skrrd the approved image carries';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* One line saying where a machine's image stands and what the owner can do.
|
|
143
|
+
*
|
|
144
|
+
* "current" is claimed only when it is KNOWN: no newer approved image AND this
|
|
145
|
+
* machine's own daemon is at the latest release (OSK-12153). A guest never
|
|
146
|
+
* updates its own daemon, so "no newer approved image" beside a daemon that is
|
|
147
|
+
* behind the latest release is a different — and worse — state than "current",
|
|
148
|
+
* and the line says so instead of collapsing toward the reassuring reading.
|
|
149
|
+
* With the release feed unread, or no daemon version, it says only what is
|
|
150
|
+
* known: there is no newer approved image.
|
|
151
|
+
*/
|
|
152
|
+
function describeDedicatedImageStanding(image, leaseId, opts = {}) {
|
|
78
153
|
const last = image.lastUpdate;
|
|
79
154
|
if (last?.status === 'applying') {
|
|
80
155
|
return `moving onto the current image${last.startedAt ? ` since ${last.startedAt}` : ''}; files are kept`;
|
|
@@ -93,7 +168,18 @@ function describeDedicatedImageStanding(image, leaseId) {
|
|
|
93
168
|
parts.push('the last image update did not complete on either image; files are kept');
|
|
94
169
|
}
|
|
95
170
|
else {
|
|
96
|
-
|
|
171
|
+
const here = String(opts.daemonVersion || '').trim();
|
|
172
|
+
const latest = opts.latestDaemonRelease;
|
|
173
|
+
const compared = here && latest && 'version' in latest ? compareDaemonVersions(here, latest.version) : null;
|
|
174
|
+
if (compared === -1 && latest && 'version' in latest) {
|
|
175
|
+
parts.push(`no newer approved image, but this machine's skrrd ${here} is behind the latest release ${latest.version}`);
|
|
176
|
+
}
|
|
177
|
+
else if (compared !== null) {
|
|
178
|
+
parts.push('current');
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
parts.push('no newer approved image');
|
|
182
|
+
}
|
|
97
183
|
}
|
|
98
184
|
if (last?.status === 'succeeded' && last.settledAt)
|
|
99
185
|
parts.push(`updated ${last.settledAt}`);
|
|
@@ -547,7 +633,7 @@ function describeDedicatedDeletion(deletion, leaseId) {
|
|
|
547
633
|
}
|
|
548
634
|
return 'nothing to restore; every copy is deleted when the window closes';
|
|
549
635
|
}
|
|
550
|
-
function describeDedicatedLease(lease, harnesses = []) {
|
|
636
|
+
function describeDedicatedLease(lease, harnesses = [], opts = {}) {
|
|
551
637
|
const lines = [dedicatedLeaseDisplayName(lease)];
|
|
552
638
|
const row = (label, value) => {
|
|
553
639
|
if (value === undefined || value === null || value === '')
|
|
@@ -583,7 +669,11 @@ function describeDedicatedLease(lease, harnesses = []) {
|
|
|
583
669
|
const image = [resources.imageVersion || lease.daemon?.imageVersion].filter(Boolean).join('');
|
|
584
670
|
row('image', image);
|
|
585
671
|
if (lease.health?.image) {
|
|
586
|
-
row('image update', describeDedicatedImageStanding(lease.health.image, dedicatedLeaseId(lease)
|
|
672
|
+
row('image update', describeDedicatedImageStanding(lease.health.image, dedicatedLeaseId(lease), {
|
|
673
|
+
daemonVersion: lease.daemon?.version,
|
|
674
|
+
latestDaemonRelease: opts.latestDaemonRelease,
|
|
675
|
+
}));
|
|
676
|
+
row('image daemon', describeDedicatedDaemonRelease(lease.health.image, opts.latestDaemonRelease));
|
|
587
677
|
}
|
|
588
678
|
if (lease.daemon?.version || lease.daemon?.lastHeartbeatAt) {
|
|
589
679
|
row('daemon', [
|
|
@@ -260,6 +260,22 @@ async function fetchDelegatedCliCredential(env = process.env, options = {}) {
|
|
|
260
260
|
const body = (await res.json().catch(() => null));
|
|
261
261
|
if (res.status === 403 && body?.code === 'DELEGATED_CLI_CAPABILITY_REJECTED')
|
|
262
262
|
return null;
|
|
263
|
+
if ((res.status === 503 || body?.code === 'DELEGATED_CLI_CREDENTIAL_UNAVAILABLE') &&
|
|
264
|
+
(typeof body?.upstreamCode === 'string' || typeof body?.upstreamStatus === 'number')) {
|
|
265
|
+
// The daemon named the server's refusal of its own mint (OSK-12151):
|
|
266
|
+
// say that, with the code, rather than guess at three possible causes.
|
|
267
|
+
const named = [
|
|
268
|
+
typeof body?.upstreamStatus === 'number' ? `HTTP ${body.upstreamStatus}` : '',
|
|
269
|
+
typeof body?.upstreamCode === 'string' ? body.upstreamCode : '',
|
|
270
|
+
]
|
|
271
|
+
.filter(Boolean)
|
|
272
|
+
.join(' ');
|
|
273
|
+
// The daemon's `message` already names the refusal; fall back to the
|
|
274
|
+
// status and code when an older daemon sent none.
|
|
275
|
+
throw new Error(typeof body?.message === 'string' && body.message
|
|
276
|
+
? `the daemon's delegated CLI broker could not mint a credential: ${body.message}`
|
|
277
|
+
: `the skrr server refused the daemon's request for this session's credential (${named}).`);
|
|
278
|
+
}
|
|
263
279
|
if (res.status === 503 || body?.code === 'DELEGATED_CLI_CREDENTIAL_UNAVAILABLE') {
|
|
264
280
|
throw new Error(`the daemon's delegated CLI broker is alive but cannot mint a credential ` +
|
|
265
281
|
`(HTTP ${res.status}). The daemon cannot reach the auth upstream, holds ` +
|
|
@@ -73,6 +73,7 @@ const api_fetch_1 = require("./api-fetch");
|
|
|
73
73
|
const auth_storage_1 = require("./auth-storage");
|
|
74
74
|
const auth_core_init_1 = require("./auth-core-init");
|
|
75
75
|
const config_1 = require("./config");
|
|
76
|
+
const cli_id_1 = require("./cli-id");
|
|
76
77
|
const cred_envelope_1 = require("./cred-envelope");
|
|
77
78
|
const device_id_1 = require("./device-id");
|
|
78
79
|
const first_party_harness_1 = require("./first-party-harness");
|
|
@@ -859,9 +860,10 @@ function hasLocalCredential() {
|
|
|
859
860
|
/**
|
|
860
861
|
* This installation's runtime id.
|
|
861
862
|
*
|
|
862
|
-
*
|
|
863
|
-
*
|
|
864
|
-
*
|
|
863
|
+
* The id login and refresh present, read through `resolveCliId` (a named
|
|
864
|
+
* profile may have its own, OSK-12156); regenerated deterministically as the
|
|
865
|
+
* machine id when nothing is stored (same hostname+user always yields the same
|
|
866
|
+
* value). Normalized to the `cli_` namespace the server requires,
|
|
865
867
|
* because `OVERSKY_CLI_ID` is overlaid verbatim with no format check and a
|
|
866
868
|
* hand-set value would otherwise come back as an opaque
|
|
867
869
|
* `runtime-id-namespace-mismatch`.
|
|
@@ -869,7 +871,7 @@ function hasLocalCredential() {
|
|
|
869
871
|
function localRuntimeId() {
|
|
870
872
|
const configured = (() => {
|
|
871
873
|
try {
|
|
872
|
-
return (0, config_1.loadConfig)()
|
|
874
|
+
return (0, cli_id_1.resolveCliId)((0, config_1.loadConfig)());
|
|
873
875
|
}
|
|
874
876
|
catch {
|
|
875
877
|
return undefined;
|
package/dist/lib/keychain.d.ts
CHANGED
|
@@ -11,6 +11,19 @@ export declare function setActiveProfile(profile: string): void;
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function isProfileResolved(): boolean;
|
|
13
13
|
export declare function getActiveProfile(): string;
|
|
14
|
+
/** The resolved config root this process's credentials belong to. */
|
|
15
|
+
export declare function resolvedConfigRoot(): string;
|
|
16
|
+
/** Is `root` this account's default skrr root, the owner of the legacy slot? */
|
|
17
|
+
export declare function isDefaultConfigRoot(root?: string): boolean;
|
|
18
|
+
/** A slot other than this root's own. */
|
|
19
|
+
export type ForeignSlot = {
|
|
20
|
+
kind: 'legacy';
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'displaced';
|
|
23
|
+
cliId: string;
|
|
24
|
+
};
|
|
25
|
+
/** True when this root's own slot IS the historical profile-only slot. */
|
|
26
|
+
export declare function isLegacySlotCurrent(): boolean;
|
|
14
27
|
/** @internal exposed for tests / diagnostics. */
|
|
15
28
|
export declare function __currentServiceForTest(): string;
|
|
16
29
|
/**
|
|
@@ -47,6 +60,32 @@ export declare function readServerOrigin(): string | null;
|
|
|
47
60
|
export declare function writeServerOrigin(origin: string): boolean;
|
|
48
61
|
/** Remove the issuing-origin companion slot. */
|
|
49
62
|
export declare function deleteServerOrigin(): void;
|
|
63
|
+
/** Raw owner record (`{configRoot, cliId}` JSON) of this root's own slot. */
|
|
64
|
+
export declare function readOwner(): string | null;
|
|
65
|
+
export declare function writeOwner(owner: string): boolean;
|
|
66
|
+
export declare function deleteOwner(): void;
|
|
67
|
+
/** Every value a CLI bundle keeps in the Keychain, read from one service. */
|
|
68
|
+
export interface KeychainBundleSlots {
|
|
69
|
+
token: KeychainEntry;
|
|
70
|
+
refresh: KeychainEntry | null;
|
|
71
|
+
serverOrigin: string | null;
|
|
72
|
+
owner: string | null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Read a whole bundle from a slot this root does not own (the legacy slot, or a
|
|
76
|
+
* displaced one). The caller decides whether it is ours; nothing here rewrites
|
|
77
|
+
* a slot on another config's behalf.
|
|
78
|
+
*/
|
|
79
|
+
export declare function readBundleAt(slot: ForeignSlot): KeychainBundleSlots | null;
|
|
80
|
+
/**
|
|
81
|
+
* Copy every item of one slot to another BYTE FOR BYTE (the displacement
|
|
82
|
+
* rescue). Raw on purpose: the secret items may be sealed under the owning
|
|
83
|
+
* config's envelope key, and re-sealing them under ours would make the copy
|
|
84
|
+
* unreadable to the one config it exists for.
|
|
85
|
+
*/
|
|
86
|
+
export declare function copyBundle(from: ForeignSlot, to: ForeignSlot): boolean;
|
|
87
|
+
/** Remove every CLI item under a slot this root does not own. The caller proves ownership. */
|
|
88
|
+
export declare function deleteAllAt(slot: ForeignSlot): void;
|
|
50
89
|
export declare function deleteToken(): void;
|
|
51
90
|
export declare function deleteRefreshToken(): void;
|
|
52
91
|
/**
|