@skrr-ai/cli 0.1.49 → 0.1.50

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 (101) hide show
  1. package/README.md +5 -4
  2. package/dist/base-command.js +16 -4
  3. package/dist/commands/agents/create.js +14 -7
  4. package/dist/commands/agents/update.js +11 -5
  5. package/dist/commands/code/index.d.ts +1 -0
  6. package/dist/commands/code/index.js +49 -5
  7. package/dist/commands/code/run.js +5 -1
  8. package/dist/commands/commitments/cycles.d.ts +19 -0
  9. package/dist/commands/commitments/cycles.js +46 -0
  10. package/dist/commands/commitments/effective-policy.js +26 -1
  11. package/dist/commands/commitments/explain.d.ts +17 -0
  12. package/dist/commands/commitments/explain.js +40 -0
  13. package/dist/commands/goals/create.d.ts +1 -0
  14. package/dist/commands/goals/create.js +22 -0
  15. package/dist/commands/logout.js +12 -1
  16. package/dist/commands/machines/dedicated/index.js +1 -1
  17. package/dist/commands/machines/dedicated/sign-in.js +4 -1
  18. package/dist/commands/machines/dedicated/terminal/kill.d.ts +21 -0
  19. package/dist/commands/machines/dedicated/terminal/kill.js +55 -0
  20. package/dist/commands/machines/dedicated/terminal/ls.d.ts +22 -0
  21. package/dist/commands/machines/dedicated/terminal/ls.js +70 -0
  22. package/dist/commands/machines/dedicated/terminal/rename.d.ts +23 -0
  23. package/dist/commands/machines/dedicated/terminal/rename.js +64 -0
  24. package/dist/commands/machines/dedicated/terminal.d.ts +9 -0
  25. package/dist/commands/machines/dedicated/terminal.js +27 -2
  26. package/dist/commands/machines/hosted/connect.js +2 -2
  27. package/dist/commands/machines/hosted/destroy.js +1 -1
  28. package/dist/commands/machines/hosted/exec.js +1 -1
  29. package/dist/commands/machines/hosted/list.js +1 -1
  30. package/dist/commands/machines/hosted/pause.js +1 -1
  31. package/dist/commands/machines/hosted/pull.js +1 -1
  32. package/dist/commands/machines/hosted/resume.js +1 -1
  33. package/dist/commands/machines/hosted/start.js +2 -2
  34. package/dist/commands/machines/hosted/status.js +2 -1
  35. package/dist/commands/tasks/create.js +1 -0
  36. package/dist/commands/tasks/list.js +1 -0
  37. package/dist/commands/tasks/update.js +1 -0
  38. package/dist/lib/agent-config.d.ts +2 -0
  39. package/dist/lib/agent-config.js +3 -1
  40. package/dist/lib/api-fetch.js +19 -0
  41. package/dist/lib/auth-storage.d.ts +14 -0
  42. package/dist/lib/auth-storage.js +14 -0
  43. package/dist/lib/commitments.d.ts +17 -2
  44. package/dist/lib/commitments.js +114 -0
  45. package/dist/lib/daemonBroker.d.ts +120 -31
  46. package/dist/lib/daemonBroker.js +313 -20
  47. package/dist/lib/dedicated-lease-command.d.ts +14 -0
  48. package/dist/lib/dedicated-lease-command.js +30 -1
  49. package/dist/lib/dedicated-machines.js +8 -25
  50. package/dist/lib/dedicated-service-command.d.ts +11 -3
  51. package/dist/lib/dedicated-service-command.js +20 -3
  52. package/dist/lib/dedicated-service.d.ts +46 -0
  53. package/dist/lib/dedicated-service.js +85 -7
  54. package/dist/lib/dedicated-terminal.d.ts +21 -0
  55. package/dist/lib/dedicated-terminal.js +104 -9
  56. package/dist/lib/first-party-harness-broker.d.ts +10 -0
  57. package/dist/lib/first-party-harness-broker.js +9 -0
  58. package/dist/lib/first-party-harness-doctor.js +41 -1
  59. package/dist/lib/first-party-harness-managed.d.ts +4 -3
  60. package/dist/lib/first-party-harness-project-trust.d.ts +125 -0
  61. package/dist/lib/first-party-harness-project-trust.js +364 -0
  62. package/dist/lib/first-party-harness.d.ts +9 -2
  63. package/dist/lib/first-party-harness.js +6 -5
  64. package/dist/lib/hosted-machines.d.ts +10 -1
  65. package/dist/lib/hosted-machines.js +36 -2
  66. package/dist/lib/login.js +16 -0
  67. package/dist/lib/machine-spend-cap.d.ts +23 -0
  68. package/dist/lib/machine-spend-cap.js +68 -0
  69. package/dist/lib/node-adapter.js +15 -3
  70. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +90 -0
  71. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +113 -0
  72. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.d.ts +47 -0
  73. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.js +69 -0
  74. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +109 -0
  75. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +171 -0
  76. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.d.ts +8 -3
  77. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.js +9 -4
  78. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +8 -9
  79. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.d.ts +6 -0
  80. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.js +32 -9
  81. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.d.ts +81 -0
  82. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.js +87 -0
  83. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/types.d.ts +6 -1
  84. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +90 -0
  85. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +105 -0
  86. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.d.ts +47 -0
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.js +66 -0
  88. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +109 -0
  89. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +164 -0
  90. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.d.ts +8 -3
  91. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.js +9 -4
  92. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +8 -9
  93. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.d.ts +6 -0
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.js +31 -9
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.d.ts +81 -0
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.js +80 -0
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/types.d.ts +6 -1
  98. package/dist/node_modules/@skrr-ai/auth-core/package.json +40 -0
  99. package/dist/node_modules/@skrr-ai/data-provider/index.js +3413 -3343
  100. package/oclif.manifest.json +15769 -15342
  101. package/package.json +4 -1
@@ -26,6 +26,8 @@ exports.describeWakeDecision = describeWakeDecision;
26
26
  exports.renderCheckList = renderCheckList;
27
27
  exports.renderCommitmentLedger = renderCommitmentLedger;
28
28
  exports.renderCommitmentTrace = renderCommitmentTrace;
29
+ exports.formatCommitmentCycles = formatCommitmentCycles;
30
+ exports.formatCommitmentExplanation = formatCommitmentExplanation;
29
31
  exports.renderListPageHint = renderListPageHint;
30
32
  exports.renderCommitmentObservations = renderCommitmentObservations;
31
33
  exports.renderCommitmentHypotheses = renderCommitmentHypotheses;
@@ -236,6 +238,10 @@ exports.commitmentApi = {
236
238
  /** Un-delete. Returns the Commitment PAUSED, never active. */
237
239
  restore: (id) => data_provider_1.request.post(`${base(id)}/restore`, {}),
238
240
  trace: (id, query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/trace`, query)),
241
+ /** One Cycle per Check, newest first — the unit a person reads (D8). */
242
+ cycles: (id, query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/cycles`, query)),
243
+ /** The latest Cycle walked as six questions; the first "no" is where it stopped. */
244
+ explain: (id) => data_provider_1.request.get(`${base(id)}/explain`),
239
245
  ledger: (id, query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/ledger`, query)),
240
246
  wakeCapabilities: (id) => data_provider_1.request.get(`${base(id)}/wake-capabilities`),
241
247
  dailyMetrics: (id, query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/daily-metrics`, query)),
@@ -1160,23 +1166,131 @@ function renderCommitmentLedger(ledger, log) {
1160
1166
  }
1161
1167
  }
1162
1168
  /** Human view for the ordered commitment trace. */
1169
+ /**
1170
+ * `question:reason` for a trace event's server-derived stop — which of the six
1171
+ * questions (wake, fresh, turn, think, act, reach) the wake stopped at. `none`
1172
+ * means an Agent turn was dispatched. Blank when the event carries no stop.
1173
+ */
1174
+ function stopCell(item) {
1175
+ const detail = item.detail;
1176
+ const stop = detail && typeof detail === 'object' ? detail.stop : undefined;
1177
+ if (!stop || typeof stop !== 'object')
1178
+ return '';
1179
+ const { question, gate, reason } = stop;
1180
+ if (typeof question !== 'string')
1181
+ return '';
1182
+ let why = '';
1183
+ if (typeof reason === 'string' && reason)
1184
+ why = reason;
1185
+ else if (typeof gate === 'string')
1186
+ why = gate;
1187
+ return why ? `${question}: ${why}` : question;
1188
+ }
1163
1189
  function renderCommitmentTrace(trace, log) {
1164
1190
  const events = records(trace.events);
1165
1191
  renderRows(events.map((item) => ({
1166
1192
  at: text(firstValue(item, ['occurredAt', 'createdAt', 'startedAt'])),
1167
1193
  type: text(firstValue(item, ['type', 'kind'])),
1168
1194
  status: text(item.status),
1195
+ stop: stopCell(item),
1169
1196
  summary: text(firstValue(item, ['summary', 'reason', 'title']), ''),
1170
1197
  })), [
1171
1198
  { key: 'at', header: 'AT', maxWidth: 24 },
1172
1199
  { key: 'type', header: 'TYPE', maxWidth: 28 },
1173
1200
  { key: 'status', header: 'STATUS', maxWidth: 28 },
1201
+ { key: 'stop', header: 'STOPPED AT', maxWidth: 48 },
1174
1202
  { key: 'summary', header: 'SUMMARY', maxWidth: 72 },
1175
1203
  ], 'No trace events recorded.', log);
1176
1204
  if (trace.has_more === true && trace.next_cursor) {
1177
1205
  log(`More events are available; continue with --cursor ${text(trace.next_cursor)}.`);
1178
1206
  }
1179
1207
  }
1208
+ const CYCLE_GLYPH = {
1209
+ advanced: '✓',
1210
+ stopped: '●',
1211
+ quiet: '○',
1212
+ in_flight: '◌',
1213
+ };
1214
+ /** `MM-DD HH:MM` in the reader's local time; `-` when the time is unknown. */
1215
+ function cycleTime(value) {
1216
+ if (typeof value !== 'string')
1217
+ return '-';
1218
+ const date = new Date(value);
1219
+ if (Number.isNaN(date.getTime()))
1220
+ return '-';
1221
+ const pad = (n) => String(n).padStart(2, '0');
1222
+ return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
1223
+ }
1224
+ function cycleWake(cycle) {
1225
+ const wake = cycle.wake || {};
1226
+ if (wake.kind === 'fire')
1227
+ return `${wake.source || 'trigger'} wake`;
1228
+ return wake.source ? `${wake.kind} (${wake.source})` : String(wake.kind || '-');
1229
+ }
1230
+ /** What one Cycle came to, in a single line. */
1231
+ function cycleSummary(cycle) {
1232
+ if (cycle.outcome === 'stopped' && cycle.stop) {
1233
+ const { question, gate, reason, fix, detail } = cycle.stop;
1234
+ return `${question} ${reason || gate} — ${fix || detail || 'stopped'}`;
1235
+ }
1236
+ if (cycle.outcome === 'advanced') {
1237
+ return cycle.advancementKinds?.length
1238
+ ? cycle.advancementKinds.join(', ')
1239
+ : 'applied an agent result';
1240
+ }
1241
+ if (cycle.outcome === 'in_flight') {
1242
+ const runs = cycle.runs?.length || 0;
1243
+ return runs ? `${runs} run(s) still going` : 'check still running';
1244
+ }
1245
+ return cycle.note || cycle.decision?.summary || cycle.decision?.posture || 'nothing to act on';
1246
+ }
1247
+ /**
1248
+ * One line per Cycle:
1249
+ * `09-22 12:04 ● stopped act commitment_execution_budget_exhausted — Raise maxRunsPerDay…`
1250
+ * A run that failed is named under its Cycle with who has to move (machine
1251
+ * or model), because that is the next question every reader asks.
1252
+ */
1253
+ function formatCommitmentCycles(page) {
1254
+ const cycles = Array.isArray(page.cycles) ? page.cycles : [];
1255
+ if (!cycles.length)
1256
+ return ['No cycles recorded yet.'];
1257
+ const lines = [];
1258
+ for (const cycle of cycles) {
1259
+ const glyph = CYCLE_GLYPH[cycle.outcome] || '?';
1260
+ lines.push(`${cycleTime(cycle.startedAt)} ${glyph} ${String(cycle.outcome).padEnd(9)} ${cycleWake(cycle).padEnd(18)} ${cycleSummary(cycle)}`);
1261
+ for (const run of cycle.runs || []) {
1262
+ if (!['machine', 'contract', 'work'].includes(run.failureClass))
1263
+ continue;
1264
+ const owner = run.failureOwner === 'model' ? 'model' : run.failureOwner || 'work';
1265
+ lines.push(` run ${run.id} ${run.status} — ${owner}: ${run.failureCode || '-'}`);
1266
+ }
1267
+ }
1268
+ if (page.has_more && page.next_cursor) {
1269
+ lines.push(`More cycles are available; continue with --cursor ${page.next_cursor}.`);
1270
+ }
1271
+ return lines;
1272
+ }
1273
+ const ANSWER_GLYPH = { yes: '✓', no: '✗', 'n/a': '–' };
1274
+ /** The six questions for the latest Cycle, the first ✗ carrying its fix. */
1275
+ function formatCommitmentExplanation(explanation) {
1276
+ const cycle = explanation.cycle;
1277
+ const lines = [
1278
+ cycle
1279
+ ? `Latest cycle: ${cycleTime(cycle.startedAt)} ${cycle.outcome} (${cycle.id})`
1280
+ : `No cycle yet (status: ${explanation.status || '-'})`,
1281
+ ];
1282
+ for (const answer of explanation.answers || []) {
1283
+ lines.push(` ${ANSWER_GLYPH[answer.answer] || '?'} ${answer.question} ${answer.because}`);
1284
+ if (answer.fix)
1285
+ lines.push(` Fix: ${answer.fix}`);
1286
+ }
1287
+ lines.push(!explanation.stoppedAt
1288
+ ? 'Nothing stopped this cycle.'
1289
+ : explanation.verdict === 'quiet'
1290
+ ? `Chose to stay quiet at: ${explanation.stoppedAt}`
1291
+ : `Stopped at: ${explanation.stoppedAt}`);
1292
+ return lines;
1293
+ }
1180
1294
  /**
1181
1295
  * Print the continuation hint for a paged `object:'list'` response — the line
1182
1296
  * that makes history beyond the newest page reachable instead of invisible.
@@ -36,31 +36,25 @@
36
36
  * liveness with `process.kill(pid, 0)` to filter stale files left behind
37
37
  * by a force-killed daemon.
38
38
  */
39
+ import { type CliHandoffDescriptor, type CliHandoffTokenResponse } from '@skrr-ai/auth-core/cli-handoff-wire';
39
40
  import { type AuthBackend } from './auth-storage';
40
41
  import { type CliConfig } from './config';
41
42
  /**
42
- * Wire-shape of the bootstrap file. Mirrors
43
- * `daemon/src/local-bootstrap.ts::LocalBootstrap`. Kept loose — fields
44
- * the CLI doesn't use are ignored, and a future addition (e.g. version
45
- * bump, new metadata) doesn't break this reader.
43
+ * Wire-shape of the bootstrap file — the shared declaration in
44
+ * `@skrr-ai/auth-core/cli-handoff-wire` (`CliHandoffDescriptor`), which the
45
+ * daemon's writers and this reader both parse against so the format cannot
46
+ * drift between two mirrored type declarations. `LocalBootstrap` stays the
47
+ * exported name so existing callers (`daemon-target`, `message-intent`,
48
+ * `dedicated-lease-command`) don't churn.
49
+ *
50
+ * Fields the CLI doesn't use are ignored by the parser, and a future
51
+ * addition (e.g. version bump, new metadata) doesn't break this reader.
52
+ * `handoffModes` (descriptor v2) advertises what the secret can redeem:
53
+ * absent reads as `['refresh_family']` — today's durable mint; a guest
54
+ * descriptor advertising `['access_token']` can only redeem short-lived
55
+ * access tokens from the daemon's in-memory broker.
46
56
  */
47
- export interface LocalBootstrap {
48
- version?: number;
49
- pid?: number;
50
- daemonId?: string;
51
- host?: string;
52
- port?: number;
53
- /** Base64 32-byte handshake secret. */
54
- secret?: string;
55
- /**
56
- * Server URL the daemon is authenticated against (e.g.
57
- * `https://oversky.ai` or `http://localhost:3080`). Daemons that
58
- * pre-date the baseURL-match check do not write this field — the
59
- * broker pre-flight then refuses to call so we never mint against
60
- * an unknown server.
61
- */
62
- serverUrl?: string;
63
- }
57
+ export type LocalBootstrap = CliHandoffDescriptor;
64
58
  /**
65
59
  * Normalize a base URL for equality comparison. Mirrors the daemon-side
66
60
  * `normalizeBaseUrl` in `daemon/src/auth/cliHandoff.ts` so both ends
@@ -94,6 +88,33 @@ export interface DaemonBrokerResult {
94
88
  * the file.
95
89
  */
96
90
  storedIn?: Exclude<AuthBackend, 'none'>;
91
+ /**
92
+ * False on the legacy durable-mint shape so `outcome.brokered === true`
93
+ * narrows the outcome union. A durable mint is never brokered.
94
+ */
95
+ brokered?: false;
96
+ }
97
+ /**
98
+ * Broker-mode success: the descriptor advertised `access_token` and the
99
+ * daemon's loopback token route (`POST /v1/auth/cli-handoff-token`) answered
100
+ * a short-lived cli-scope access token minted from a refresh family the
101
+ * DAEMON holds in memory.
102
+ *
103
+ * This outcome carries no `refreshToken`, no `cliId`, no `userId` — nothing
104
+ * credential-shaped may be persisted for it. `brokered: true` is the tag the
105
+ * persist path keys on to skip `writeToBackend` entirely; the token is used
106
+ * in-memory for this process only, and the next `skrr` invocation re-redeems
107
+ * over loopback.
108
+ */
109
+ export interface DaemonBrokeredOutcome {
110
+ ok: true;
111
+ brokered: true;
112
+ accessToken: string;
113
+ /** Absolute expiry in epoch ms, when the daemon reports it. */
114
+ accessExpiresAt?: number;
115
+ /** Server the minted credential belongs to (daemon-reported, else the
116
+ * descriptor's own `serverUrl`). */
117
+ serverUrl?: string;
97
118
  }
98
119
  /**
99
120
  * Explicit reasons the broker path could fail. These exist to keep the
@@ -101,7 +122,7 @@ export interface DaemonBrokerResult {
101
122
  * fall through to PKCE; `ci_token_refused` is the one we surface
102
123
  * because re-driving it via PKCE would not change the outcome.
103
124
  */
104
- export type DaemonBrokerErrorReason = 'no_bootstrap' | 'stale_bootstrap' | 'bootstrap_parse' | 'base_url_mismatch' | 'daemon_not_authed' | 'ci_token_refused' | 'rate_limited' | 'server_error' | 'network' | 'timeout' | 'unknown';
125
+ export type DaemonBrokerErrorReason = 'no_bootstrap' | 'stale_bootstrap' | 'bootstrap_parse' | 'base_url_mismatch' | 'daemon_not_authed' | 'ci_token_refused' | 'handoff_broker_only' | 'rate_limited' | 'server_error' | 'network' | 'timeout' | 'unknown';
105
126
  export interface DaemonBrokerFailure {
106
127
  ok: false;
107
128
  reason: DaemonBrokerErrorReason;
@@ -120,7 +141,7 @@ export interface DaemonBrokerFailure {
120
141
  }
121
142
  export type DaemonBrokerOutcome = (DaemonBrokerResult & {
122
143
  ok: true;
123
- }) | DaemonBrokerFailure;
144
+ }) | DaemonBrokeredOutcome | DaemonBrokerFailure;
124
145
  export interface DaemonBrokerOptions {
125
146
  /** Stable per-machine cliId the CLI wants the new refresh row bound to. */
126
147
  cliId: string;
@@ -151,6 +172,22 @@ export interface DaemonBrokerOptions {
151
172
  candidatePathsOverride?: readonly string[];
152
173
  /** Override total request timeout. Tests use a short value. */
153
174
  timeoutMs?: number;
175
+ /**
176
+ * Which advertised mode to prefer when a descriptor offers both
177
+ * (`handoffModes: ['access_token', 'refresh_family']`, the laptop island
178
+ * shape during transition):
179
+ *
180
+ * - `'access_token'` — broker mode: redeem a short-lived access token
181
+ * and hold it in memory only. Used by `maybeAutoBroker`: nothing
182
+ * credential-shaped lands on disk and each `skrr` process re-redeems
183
+ * over loopback.
184
+ * - `'refresh_family'` (default) — the legacy durable mint. Used by
185
+ * explicit `skrr login`: a human login still writes a real,
186
+ * user-owned refresh family. A descriptor that advertises ONLY
187
+ * `access_token` (the Dedicated guest shape) redeems brokered
188
+ * regardless — its secret cannot mint a family at all.
189
+ */
190
+ preferHandoffMode?: 'access_token' | 'refresh_family';
154
191
  }
155
192
  /**
156
193
  * Compute the bootstrap file path. Exported for tests so they can assert
@@ -214,10 +251,60 @@ export declare function readBootstrap(filePath: string): LocalBootstrap | null;
214
251
  */
215
252
  export declare function attemptDaemonBrokerLogin(opts: DaemonBrokerOptions): Promise<DaemonBrokerOutcome>;
216
253
  /**
217
- * Convenience wrapper: run the broker call and, on success, persist the
218
- * returned bundle through the canonical Sky CLI auth backend (Keychain
219
- * on macOS, `cli-auth.json` elsewhere). Returns the same outcome shape
220
- * so the caller can branch on `ok`.
254
+ * True when `token` is the access token this process redeemed through the
255
+ * daemon's broker-mode hand-off. `api-fetch` uses this to pick the re-redeem
256
+ * recovery branch instead of the stored-family refresh (which would have
257
+ * nothing to rotate — brokered credentials are never persisted).
258
+ */
259
+ export declare function isBrokeredHandoffToken(token: string | null | undefined): boolean;
260
+ /**
261
+ * Re-redeem the brokered hand-off token — the 401-recovery path. The
262
+ * descriptor is re-read from disk when its source path is known, so a secret
263
+ * rotated by a daemon restart resolves to the fresh one. Returns null when
264
+ * this process holds no brokered credential or the redeem fails (the caller
265
+ * then surfaces the original 401).
266
+ */
267
+ export declare function redeemBrokeredHandoffToken(forceRefresh?: boolean): Promise<CliHandoffTokenResponse | null>;
268
+ /** @internal test seam — clears the module-scoped brokered credential. */
269
+ export declare function __resetBrokeredHandoffForTest(): void;
270
+ /**
271
+ * True when a live local descriptor advertises broker-mode access — i.e.
272
+ * this machine's CLI authority is platform-managed and `skrr logout` has no
273
+ * local credential to clear. Read-only: parses candidates, never redeems.
274
+ */
275
+ export declare function findBrokeredHandoffDescriptor(profile?: string): LocalBootstrap | null;
276
+ /**
277
+ * Migration cleanup: retire the refresh family a pre-broker-mode CLI
278
+ * generation persisted, now that this machine redeems access tokens from
279
+ * the daemon's in-memory broker.
280
+ *
281
+ * Provenance guard — locally we cannot tell a hand-off-minted family from a
282
+ * user's explicit `skrr login` family by shape (the distinguishing
283
+ * `daemonRefOverride` lives server-side). The safe rule, applied here:
284
+ * revoke + delete ONLY when all of these hold —
285
+ *
286
+ * 1. the descriptor that just redeemed IS the Dedicated Runtime guest
287
+ * hand-off file (`/run/skrr-dedicated-runtime/...`),
288
+ * 2. `~/.skrr/cli-auth.json` reads cleanly and carries a refresh token,
289
+ * 3. its stored `serverOrigin` matches the descriptor's `serverUrl`.
290
+ *
291
+ * When provenance can't be proven the file is KEPT: an explicit `skrr login`
292
+ * on a guest is rare but real, and a stale extra family is bounded by the
293
+ * server's session cap and expires on its own. Best-effort throughout — a
294
+ * failed revoke still deletes the file (the whole point is that nothing
295
+ * credential-shaped should remain), and any failure leaves the working
296
+ * brokered credential untouched.
297
+ */
298
+ export declare function maybeRetireStoredCliAuthFile(opts: {
299
+ descriptorPath?: string;
300
+ descriptorServerUrl?: string;
301
+ }): Promise<void>;
302
+ /**
303
+ * Convenience wrapper: run the broker call and, on a durable-mint success,
304
+ * persist the returned bundle through the canonical Sky CLI auth backend
305
+ * (Keychain on macOS, `cli-auth.json` elsewhere). A `brokered` outcome is
306
+ * returned WITHOUT persisting — that is the entire point of broker mode.
307
+ * Returns the same outcome shape so the caller can branch on `ok`/`brokered`.
221
308
  */
222
309
  export declare function attemptDaemonBrokerLoginAndPersist(opts: DaemonBrokerOptions): Promise<DaemonBrokerOutcome>;
223
310
  export interface MaybeAutoBrokerOptions {
@@ -275,10 +362,12 @@ export interface MaybeAutoBrokerResult {
275
362
  * - `commandId` is in `AUTH_SELF_MANAGED_COMMANDS`
276
363
  *
277
364
  * Side effects:
278
- * - Calls `attemptDaemonBrokerLoginAndPersist` which writes to the
279
- * keychain / cli-auth.json on success (this is the whole point —
280
- * subsequent commands read the persisted token via the normal
281
- * resolver chain, no broker round-trip per command).
365
+ * - Calls `attemptDaemonBrokerLoginAndPersist` with `preferHandoffMode:
366
+ * 'access_token'`. On a descriptor that advertises broker mode the
367
+ * outcome is `brokered` — an in-memory access token, nothing written;
368
+ * the caller holds it for this process and the next `skrr` re-redeems.
369
+ * On a legacy/v1 descriptor the durable mint is persisted to the
370
+ * keychain / cli-auth.json exactly as before.
282
371
  * - May generate a new `cliId` if the config doesn't have one. The
283
372
  * caller MUST persist `updatedConfig` so the next invocation
284
373
  * presents the same id to the server's refresh rotator.