@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.
Files changed (67) hide show
  1. package/dist/base-command.d.ts +7 -0
  2. package/dist/base-command.js +38 -8
  3. package/dist/commands/agents/chat.js +2 -1
  4. package/dist/commands/agents/create.js +21 -8
  5. package/dist/commands/agents/update.d.ts +55 -0
  6. package/dist/commands/agents/update.js +119 -23
  7. package/dist/commands/commitments/next-tick.js +9 -1
  8. package/dist/commands/context.js +2 -1
  9. package/dist/commands/daemon/config.js +16 -1
  10. package/dist/commands/doctor.js +8 -1
  11. package/dist/commands/login.d.ts +2 -1
  12. package/dist/commands/login.js +26 -7
  13. package/dist/commands/logout.js +3 -0
  14. package/dist/commands/machines/dedicated/show.js +25 -2
  15. package/dist/commands/tasks/approvals/list.d.ts +22 -0
  16. package/dist/commands/tasks/approvals/list.js +80 -3
  17. package/dist/commands/tasks/create.d.ts +5 -0
  18. package/dist/commands/tasks/create.js +28 -7
  19. package/dist/commands/tasks/start.d.ts +14 -0
  20. package/dist/commands/tasks/start.js +31 -1
  21. package/dist/commands/tasks/update.js +30 -3
  22. package/dist/commands/whoami.js +5 -3
  23. package/dist/lib/agent-config.d.ts +15 -0
  24. package/dist/lib/agent-config.js +46 -10
  25. package/dist/lib/agentic-stream.d.ts +21 -1
  26. package/dist/lib/agentic-stream.js +40 -6
  27. package/dist/lib/auth-failure.js +4 -1
  28. package/dist/lib/auth-storage.d.ts +24 -0
  29. package/dist/lib/auth-storage.js +348 -13
  30. package/dist/lib/cli-id.d.ts +22 -0
  31. package/dist/lib/cli-id.js +56 -0
  32. package/dist/lib/cli-identity.d.ts +49 -0
  33. package/dist/lib/cli-identity.js +159 -0
  34. package/dist/lib/commitments.d.ts +4 -0
  35. package/dist/lib/commitments.js +12 -1
  36. package/dist/lib/config.d.ts +17 -0
  37. package/dist/lib/config.js +15 -3
  38. package/dist/lib/cred-envelope.d.ts +12 -1
  39. package/dist/lib/cred-envelope.js +20 -3
  40. package/dist/lib/daemonBroker.d.ts +26 -4
  41. package/dist/lib/daemonBroker.js +82 -10
  42. package/dist/lib/daemonBrokerRefusal.d.ts +51 -0
  43. package/dist/lib/daemonBrokerRefusal.js +127 -0
  44. package/dist/lib/dedicated-machines.d.ts +53 -4
  45. package/dist/lib/dedicated-machines.js +95 -5
  46. package/dist/lib/delegated-cli.js +16 -0
  47. package/dist/lib/first-party-harness-broker.js +6 -4
  48. package/dist/lib/keychain.d.ts +39 -0
  49. package/dist/lib/keychain.js +254 -40
  50. package/dist/lib/label-ref.d.ts +34 -0
  51. package/dist/lib/label-ref.js +72 -1
  52. package/dist/lib/login.js +24 -12
  53. package/dist/lib/oauthLogin.js +2 -8
  54. package/dist/lib/refresh.js +6 -2
  55. package/dist/lib/tasks.js +33 -8
  56. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +13 -1
  57. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +9 -3
  58. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  59. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -3
  60. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +13 -1
  61. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +8 -3
  62. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  63. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
  64. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  65. package/dist/node_modules/@skrr-ai/data-provider/index.js +3138 -3100
  66. package/oclif.manifest.json +26470 -26449
  67. 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
- /** A newer image exists that has not already failed on this machine. */
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
- /** One line saying where a machine's image stands and what the owner can do. */
125
- export declare function describeDedicatedImageStanding(image: DedicatedImageStanding, leaseId?: string): string;
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[]): string[];
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
- /** One line saying where a machine's image stands and what the owner can do. */
77
- function describeDedicatedImageStanding(image, leaseId) {
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
- parts.push('current');
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
- * Persisted at first login as `cliId`; regenerated deterministically when absent
863
- * (same hostname+user always yields the same value), matching `daemonBroker.ts`
864
- * and `refresh.ts`. Normalized to the `cli_` namespace the server requires,
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)().cliId?.trim();
874
+ return (0, cli_id_1.resolveCliId)((0, config_1.loadConfig)());
873
875
  }
874
876
  catch {
875
877
  return undefined;
@@ -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
  /**