@skrr-ai/cli 0.1.49 → 0.1.51

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 (180) hide show
  1. package/README.md +5 -4
  2. package/dist/base-command.js +75 -6
  3. package/dist/commands/agents/chat.d.ts +14 -0
  4. package/dist/commands/agents/chat.js +37 -1
  5. package/dist/commands/agents/create.js +14 -7
  6. package/dist/commands/agents/show.d.ts +9 -0
  7. package/dist/commands/agents/show.js +53 -0
  8. package/dist/commands/agents/update.d.ts +9 -0
  9. package/dist/commands/agents/update.js +38 -5
  10. package/dist/commands/balance/show.d.ts +34 -0
  11. package/dist/commands/balance/show.js +92 -2
  12. package/dist/commands/code/index.d.ts +1 -0
  13. package/dist/commands/code/index.js +49 -5
  14. package/dist/commands/code/run.js +5 -1
  15. package/dist/commands/commitments/cycles.d.ts +19 -0
  16. package/dist/commands/commitments/cycles.js +46 -0
  17. package/dist/commands/commitments/doctor.d.ts +1 -0
  18. package/dist/commands/commitments/doctor.js +19 -4
  19. package/dist/commands/commitments/effective-policy.js +40 -1
  20. package/dist/commands/commitments/explain.d.ts +17 -0
  21. package/dist/commands/commitments/explain.js +40 -0
  22. package/dist/commands/commitments/next-tick.js +3 -1
  23. package/dist/commands/commitments/preflight.js +10 -0
  24. package/dist/commands/commitments/show.js +11 -1
  25. package/dist/commands/daemon/config.d.ts +15 -0
  26. package/dist/commands/daemon/config.js +38 -0
  27. package/dist/commands/daemon/index.js +1 -0
  28. package/dist/commands/daemon/status.js +15 -1
  29. package/dist/commands/goals/create.d.ts +1 -0
  30. package/dist/commands/goals/create.js +22 -0
  31. package/dist/commands/harnesses/models.d.ts +54 -0
  32. package/dist/commands/harnesses/models.js +100 -7
  33. package/dist/commands/harnesses/show.d.ts +11 -0
  34. package/dist/commands/harnesses/show.js +35 -0
  35. package/dist/commands/harnesses/update.d.ts +28 -1
  36. package/dist/commands/harnesses/update.js +73 -10
  37. package/dist/commands/logout.js +12 -1
  38. package/dist/commands/machines/dedicated/index.js +1 -1
  39. package/dist/commands/machines/dedicated/sign-in.js +4 -1
  40. package/dist/commands/machines/dedicated/terminal/kill.d.ts +21 -0
  41. package/dist/commands/machines/dedicated/terminal/kill.js +55 -0
  42. package/dist/commands/machines/dedicated/terminal/ls.d.ts +22 -0
  43. package/dist/commands/machines/dedicated/terminal/ls.js +70 -0
  44. package/dist/commands/machines/dedicated/terminal/rename.d.ts +23 -0
  45. package/dist/commands/machines/dedicated/terminal/rename.js +64 -0
  46. package/dist/commands/machines/dedicated/terminal.d.ts +9 -0
  47. package/dist/commands/machines/dedicated/terminal.js +27 -2
  48. package/dist/commands/machines/hosted/connect.js +2 -2
  49. package/dist/commands/machines/hosted/destroy.js +1 -1
  50. package/dist/commands/machines/hosted/exec.js +1 -1
  51. package/dist/commands/machines/hosted/list.js +1 -1
  52. package/dist/commands/machines/hosted/pause.js +1 -1
  53. package/dist/commands/machines/hosted/pull.js +1 -1
  54. package/dist/commands/machines/hosted/resume.js +1 -1
  55. package/dist/commands/machines/hosted/start.js +2 -2
  56. package/dist/commands/machines/hosted/status.js +2 -1
  57. package/dist/commands/outbox/list.d.ts +12 -1
  58. package/dist/commands/outbox/list.js +31 -2
  59. package/dist/commands/revoke-daemon.js +9 -0
  60. package/dist/commands/spaces/create.d.ts +1 -0
  61. package/dist/commands/spaces/create.js +17 -0
  62. package/dist/commands/spaces/show.js +5 -0
  63. package/dist/commands/spaces/update.d.ts +1 -0
  64. package/dist/commands/spaces/update.js +16 -0
  65. package/dist/commands/tasks/create.d.ts +1 -0
  66. package/dist/commands/tasks/create.js +6 -0
  67. package/dist/commands/tasks/list.js +1 -0
  68. package/dist/commands/tasks/show.js +45 -0
  69. package/dist/commands/tasks/update.d.ts +1 -0
  70. package/dist/commands/tasks/update.js +22 -0
  71. package/dist/commands/tasks/upsert.d.ts +1 -0
  72. package/dist/commands/tasks/upsert.js +16 -0
  73. package/dist/lib/agent-config.d.ts +2 -0
  74. package/dist/lib/agent-config.js +3 -1
  75. package/dist/lib/agentic-stream.d.ts +68 -1
  76. package/dist/lib/agentic-stream.js +294 -7
  77. package/dist/lib/api-fetch.js +19 -0
  78. package/dist/lib/auth-core-init.d.ts +2 -10
  79. package/dist/lib/auth-core-init.js +20 -67
  80. package/dist/lib/auth-failure.d.ts +33 -0
  81. package/dist/lib/auth-failure.js +65 -0
  82. package/dist/lib/auth-storage.d.ts +14 -0
  83. package/dist/lib/auth-storage.js +14 -0
  84. package/dist/lib/commitments.d.ts +37 -2
  85. package/dist/lib/commitments.js +148 -0
  86. package/dist/lib/daemonBroker.d.ts +134 -31
  87. package/dist/lib/daemonBroker.js +376 -31
  88. package/dist/lib/dedicated-lease-command.d.ts +14 -0
  89. package/dist/lib/dedicated-lease-command.js +30 -1
  90. package/dist/lib/dedicated-machines.js +8 -25
  91. package/dist/lib/dedicated-service-command.d.ts +11 -3
  92. package/dist/lib/dedicated-service-command.js +20 -3
  93. package/dist/lib/dedicated-service.d.ts +46 -0
  94. package/dist/lib/dedicated-service.js +85 -7
  95. package/dist/lib/dedicated-terminal.d.ts +21 -0
  96. package/dist/lib/dedicated-terminal.js +104 -9
  97. package/dist/lib/delegated-cli.js +4 -1
  98. package/dist/lib/exec-runtime-binary.d.ts +3 -1
  99. package/dist/lib/exec-runtime-binary.js +4 -2
  100. package/dist/lib/first-party-harness-broker.d.ts +10 -0
  101. package/dist/lib/first-party-harness-broker.js +13 -1
  102. package/dist/lib/first-party-harness-doctor.js +41 -1
  103. package/dist/lib/first-party-harness-managed.d.ts +4 -3
  104. package/dist/lib/first-party-harness-project-trust.d.ts +125 -0
  105. package/dist/lib/first-party-harness-project-trust.js +364 -0
  106. package/dist/lib/first-party-harness.d.ts +9 -2
  107. package/dist/lib/first-party-harness.js +6 -5
  108. package/dist/lib/harnesses.d.ts +10 -0
  109. package/dist/lib/hosted-machines.d.ts +10 -1
  110. package/dist/lib/hosted-machines.js +36 -2
  111. package/dist/lib/live-runtime-binary.d.ts +40 -0
  112. package/dist/lib/live-runtime-binary.js +193 -0
  113. package/dist/lib/login.d.ts +23 -0
  114. package/dist/lib/login.js +60 -10
  115. package/dist/lib/machine-spend-cap.d.ts +23 -0
  116. package/dist/lib/machine-spend-cap.js +68 -0
  117. package/dist/lib/message-intent.js +3 -1
  118. package/dist/lib/node-adapter.js +15 -3
  119. package/dist/lib/tasks.d.ts +20 -0
  120. package/dist/lib/tasks.js +54 -0
  121. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +90 -0
  122. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +113 -0
  123. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +2 -0
  124. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +26 -1
  125. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.d.ts +47 -0
  126. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.js +69 -0
  127. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +133 -0
  128. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +189 -0
  129. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +41 -3
  130. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
  131. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +25 -3
  132. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.d.ts +32 -0
  133. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.js +268 -0
  134. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.d.ts +2 -2
  135. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +11 -10
  136. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +36 -0
  137. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +156 -0
  138. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.d.ts +8 -3
  139. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.js +9 -4
  140. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +8 -0
  141. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +16 -10
  142. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.d.ts +6 -0
  143. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refreshClassification.js +32 -9
  144. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.d.ts +81 -0
  145. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/sessionPermissionAuthority.js +87 -0
  146. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +22 -0
  147. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +61 -2
  148. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/types.d.ts +6 -1
  149. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +90 -0
  150. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +105 -0
  151. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +2 -0
  152. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +25 -1
  153. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.d.ts +47 -0
  154. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.js +66 -0
  155. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +133 -0
  156. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +182 -0
  157. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +41 -3
  158. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
  159. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -1
  160. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.d.ts +32 -0
  161. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.js +257 -0
  162. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.d.ts +2 -2
  163. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +11 -10
  164. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +36 -0
  165. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +147 -0
  166. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.d.ts +8 -3
  167. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.js +9 -4
  168. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +8 -0
  169. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +16 -10
  170. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.d.ts +6 -0
  171. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refreshClassification.js +31 -9
  172. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.d.ts +81 -0
  173. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/sessionPermissionAuthority.js +80 -0
  174. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +22 -0
  175. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +58 -1
  176. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/types.d.ts +6 -1
  177. package/dist/node_modules/@skrr-ai/auth-core/package.json +61 -1
  178. package/dist/node_modules/@skrr-ai/data-provider/index.js +3418 -3344
  179. package/oclif.manifest.json +24813 -24242
  180. package/package.json +4 -1
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ /**
3
+ * auth-failure.ts — say WHY a request was refused on authentication (OSK-12116).
4
+ *
5
+ * A 401 reaches the CLI after its own renewal has already failed: the access
6
+ * token expired, the refresh was refused, and the retry went out with the dead
7
+ * token. The server's answer to that retry is `TOKEN_EXPIRED`, which is true and
8
+ * useless — the expiry is routine, the refusal to RENEW is the fault, and it was
9
+ * visible only as an `[auth-core warn]` line on stderr.
10
+ *
11
+ * auth-core latches every permanent refresh refusal (`needs-reauth.json`, the
12
+ * same record `skrr whoami` prints as "Refresh state"). This module reads that
13
+ * latch and turns it into the sentence the error should have carried.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.describeRefreshRefusal = describeRefreshRefusal;
17
+ /**
18
+ * Explain one refresh refusal in terms of what the reader can do about it.
19
+ * Returns null when there is no latched refusal, so the caller keeps its
20
+ * generic wording rather than inventing a cause.
21
+ */
22
+ function describeRefreshRefusal(refusal, opts) {
23
+ if (!refusal?.reason)
24
+ return null;
25
+ const { bin } = opts;
26
+ const code = refusal.reason;
27
+ const said = refusal.message ? ` (${code}: ${refusal.message})` : ` (${code})`;
28
+ const relogin = `Run \`${bin} login\` to sign in again.`;
29
+ const message = refusal.message ?? '';
30
+ // The row the stored refresh token belongs to was minted for a different CLI
31
+ // identity than the one this config presents. The keychain slot is keyed by
32
+ // profile, while the CLI ID lives in the config directory — so a second skrr
33
+ // configuration on this machine (a different HOME or config dir sharing the
34
+ // same login Keychain) can write this profile's credential under its own ID.
35
+ // Observed: a token minted for `cli_devin_dogfood` sitting in the default
36
+ // profile's slot.
37
+ if (/bound to a different daemon/i.test(message)) {
38
+ return {
39
+ code,
40
+ message: `This profile's stored sign-in could not be renewed${said}. The credential in its ` +
41
+ `Keychain slot was issued to a different CLI identity than this one` +
42
+ `${opts.cliId ? ` (${opts.cliId})` : ''} — usually another skrr configuration on this ` +
43
+ `machine (a different HOME or config directory) signed in and wrote over it. ${relogin}`,
44
+ };
45
+ }
46
+ if (code === 'SESSION_REVOKED') {
47
+ return {
48
+ code,
49
+ message: `This sign-in was revoked${said}. Signing out elsewhere revokes it, and so does ` +
50
+ `revoking a daemon that brokered it (\`${bin} login\` without a browser signs in ` +
51
+ `through this machine's daemon). ${relogin}`,
52
+ };
53
+ }
54
+ if (code === 'REFRESH_REUSED') {
55
+ return {
56
+ code,
57
+ message: `This sign-in was ended because its refresh token was presented twice${said}; the ` +
58
+ `server treats that as possible theft and revokes the whole family. ${relogin}`,
59
+ };
60
+ }
61
+ return {
62
+ code,
63
+ message: `This sign-in could not be renewed${said}. ${relogin}`,
64
+ };
65
+ }
@@ -21,6 +21,20 @@ export interface WriteOptions {
21
21
  /** Clear a stale needs-reauth latch after a server has issued fresh credentials. */
22
22
  clearReauth?: boolean;
23
23
  }
24
+ /**
25
+ * File-backend-only read. Exported for the brokered-hand-off migration
26
+ * cleanup (`daemonBroker.maybeRetireStoredCliAuthFile`), which must inspect
27
+ * `~/.skrr/cli-auth.json` specifically — not the keychain-preferred
28
+ * `readFromBackend()` — to prove the stored family's provenance before
29
+ * retiring it. Everything else should keep using `readFromBackend()`.
30
+ */
31
+ export declare function readFromFile(): AuthBundle | null;
32
+ /**
33
+ * Delete `~/.skrr/cli-auth.json` only (the keychain is untouched). Exported
34
+ * for the brokered-hand-off migration cleanup; `skrr logout` continues to
35
+ * use `clearAllBackends()`.
36
+ */
37
+ export declare function deleteFileBackend(): void;
24
38
  /**
25
39
  * Read the CLI's auth bundle from the preferred backend.
26
40
  *
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.backends = void 0;
37
+ exports.readFromFile = readFromFile;
38
+ exports.deleteFileBackend = deleteFileBackend;
37
39
  exports.readFromBackend = readFromBackend;
38
40
  exports.writeToBackend = writeToBackend;
39
41
  exports.clearAllBackends = clearAllBackends;
@@ -96,6 +98,13 @@ function authFilePath() {
96
98
  // ---------------------------------------------------------------------
97
99
  // File backend
98
100
  // ---------------------------------------------------------------------
101
+ /**
102
+ * File-backend-only read. Exported for the brokered-hand-off migration
103
+ * cleanup (`daemonBroker.maybeRetireStoredCliAuthFile`), which must inspect
104
+ * `~/.skrr/cli-auth.json` specifically — not the keychain-preferred
105
+ * `readFromBackend()` — to prove the stored family's provenance before
106
+ * retiring it. Everything else should keep using `readFromBackend()`.
107
+ */
99
108
  function readFromFile() {
100
109
  const p = authFilePath();
101
110
  if (!fs.existsSync(p))
@@ -235,6 +244,11 @@ function writeToFile(bundle) {
235
244
  throw err;
236
245
  }
237
246
  }
247
+ /**
248
+ * Delete `~/.skrr/cli-auth.json` only (the keychain is untouched). Exported
249
+ * for the brokered-hand-off migration cleanup; `skrr logout` continues to
250
+ * use `clearAllBackends()`.
251
+ */
238
252
  function deleteFileBackend() {
239
253
  const p = authFilePath();
240
254
  try {
@@ -17,7 +17,7 @@
17
17
  * "hold this state", derived from a Compass invariant — not authored here).
18
18
  */
19
19
  import { COMMITMENT_WATCHED_SOURCES, COMMITMENT_TEMPLATE_IDS, COMMITMENT_CADENCE_MODES, COMMITMENT_CADENCE_TIME_PATTERN, COMMITMENT_CADENCE_MAX_TIMES, COMMITMENT_AUTONOMY_MODES, COMMITMENT_EXECUTION_ISOLATION_MODES, COMMITMENT_STATUSES, COMMITMENT_KIND_VALUES } from '@skrr-ai/data-provider';
20
- import type { CommitmentCheck, CommitmentChecksResponse, Commitment, CommitmentActionProposal, CommitmentListResponse, CommitmentPlanResponse, CommitmentStatusResponse, CommitmentWorkLink, CommitmentExecutionReceiptListResponse, CommitmentExecutionReceiptQuarterlyMetricsResponse, CommitmentCapabilitySource, CommitmentDeliveryPatchResponse, CommitmentDeliveryBundleResponse } from '@skrr-ai/data-provider';
20
+ import type { CommitmentCheck, CommitmentChecksResponse, Commitment, CommitmentActionProposal, CommitmentListResponse, CommitmentPlanResponse, CommitmentStatusResponse, CommitmentWorkLink, CommitmentExecutionReceiptListResponse, CommitmentExecutionReceiptQuarterlyMetricsResponse, CommitmentCapabilitySource, CommitmentDeliveryPatchResponse, CommitmentDeliveryBundleResponse, CommitmentCyclesResponse, CommitmentExplanationResponse, CommitmentRunPermissions } from '@skrr-ai/data-provider';
21
21
  import { withQuery } from './triggers';
22
22
  export { COMMITMENT_WATCHED_SOURCES, COMMITMENT_TEMPLATE_IDS, COMMITMENT_CADENCE_MODES, COMMITMENT_CADENCE_TIME_PATTERN, COMMITMENT_CADENCE_MAX_TIMES, COMMITMENT_STATUSES, COMMITMENT_KIND_VALUES, };
23
23
  export type { CommitmentCheck, CommitmentChecksResponse, Commitment, CommitmentActionProposal, CommitmentListResponse, CommitmentPlanResponse, CommitmentStatusResponse, CommitmentWorkLink, };
@@ -99,7 +99,27 @@ export type CommitmentPreflightReport = {
99
99
  suppressedBy?: string | null;
100
100
  [key: string]: unknown;
101
101
  };
102
+ /** Whose permissions a run uses (OSK-11975). Absent from older servers. */
103
+ permissions?: CommitmentRunPermissions | null;
104
+ /** `retired` names the fence fields OSK-11975 made inert (OSK-12065). */
105
+ execution?: {
106
+ retired?: string[];
107
+ retiredReason?: string;
108
+ } & Record<string, unknown>;
102
109
  };
110
+ /**
111
+ * The lines every Commitment read surface prints for "whose permissions does a
112
+ * run use?" — `show`, `effective-policy` and `preflight` share it so the three
113
+ * cannot phrase it three ways (OSK-12065). The sentence and the warnings are
114
+ * the server's; this only lays them out. `null` (an older server that does not
115
+ * report it) prints nothing rather than a guess.
116
+ */
117
+ export declare function permissionLines(permissions: CommitmentRunPermissions | null | undefined): string[];
118
+ /**
119
+ * One line naming fields that are still in the JSON but decide nothing any
120
+ * more. Deprecation, not removal: `--json` keeps them for scripts.
121
+ */
122
+ export declare function retiredFieldsLine(retired: string[] | undefined, reason: string | undefined): string | null;
103
123
  /**
104
124
  * A Commitment Pack's readiness receipt.
105
125
  *
@@ -351,6 +371,13 @@ export declare const commitmentApi: {
351
371
  limit?: number;
352
372
  cursor?: string;
353
373
  }) => Promise<Record<string, unknown>>;
374
+ /** One Cycle per Check, newest first — the unit a person reads (D8). */
375
+ cycles: (id: string, query?: {
376
+ limit?: number;
377
+ cursor?: string;
378
+ }) => Promise<CommitmentCyclesResponse>;
379
+ /** The latest Cycle walked as six questions; the first "no" is where it stopped. */
380
+ explain: (id: string) => Promise<CommitmentExplanationResponse>;
354
381
  ledger: (id: string, query?: Record<string, string | number | undefined>) => Promise<Record<string, unknown>>;
355
382
  wakeCapabilities: (id: string) => Promise<CommitmentWakeCapabilities>;
356
383
  dailyMetrics: (id: string, query?: {
@@ -759,8 +786,16 @@ export declare function renderCheckList(checks: CommitmentCheckView[], log: (lin
759
786
  type ReadModelRecord = Record<string, unknown>;
760
787
  /** Human view for `commitments ledger`; JSON remains the automation surface. */
761
788
  export declare function renderCommitmentLedger(ledger: ReadModelRecord, log: (line: string) => void): void;
762
- /** Human view for the ordered commitment trace. */
763
789
  export declare function renderCommitmentTrace(trace: ReadModelRecord, log: (line: string) => void): void;
790
+ /**
791
+ * One line per Cycle:
792
+ * `09-22 12:04 ● stopped act commitment_execution_budget_exhausted — Raise maxRunsPerDay…`
793
+ * A run that failed is named under its Cycle with who has to move (machine
794
+ * or model), because that is the next question every reader asks.
795
+ */
796
+ export declare function formatCommitmentCycles(page: Partial<CommitmentCyclesResponse>): string[];
797
+ /** The six questions for the latest Cycle, the first ✗ carrying its fix. */
798
+ export declare function formatCommitmentExplanation(explanation: Partial<CommitmentExplanationResponse>): string[];
764
799
  /**
765
800
  * Print the continuation hint for a paged `object:'list'` response — the line
766
801
  * that makes history beyond the newest page reachable instead of invisible.
@@ -4,6 +4,8 @@ exports.COMMITMENT_CLI_EVALUATORS = exports.COMMITMENT_CREATE_KEYS = exports.COM
4
4
  exports.storedAutonomyModeForAuthoring = storedAutonomyModeForAuthoring;
5
5
  exports.authoringAutonomyModeForStored = authoringAutonomyModeForStored;
6
6
  exports.commitmentAutonomyLabel = commitmentAutonomyLabel;
7
+ exports.permissionLines = permissionLines;
8
+ exports.retiredFieldsLine = retiredFieldsLine;
7
9
  exports.readCommitmentAgentDirective = readCommitmentAgentDirective;
8
10
  exports.normalizeCreateBody = normalizeCreateBody;
9
11
  exports.parseCommitmentConcurrencyLimit = parseCommitmentConcurrencyLimit;
@@ -26,6 +28,8 @@ exports.describeWakeDecision = describeWakeDecision;
26
28
  exports.renderCheckList = renderCheckList;
27
29
  exports.renderCommitmentLedger = renderCommitmentLedger;
28
30
  exports.renderCommitmentTrace = renderCommitmentTrace;
31
+ exports.formatCommitmentCycles = formatCommitmentCycles;
32
+ exports.formatCommitmentExplanation = formatCommitmentExplanation;
29
33
  exports.renderListPageHint = renderListPageHint;
30
34
  exports.renderCommitmentObservations = renderCommitmentObservations;
31
35
  exports.renderCommitmentHypotheses = renderCommitmentHypotheses;
@@ -107,6 +111,33 @@ function commitmentAutonomyLabel(mode) {
107
111
  */
108
112
  exports.MIN_COMMITMENT_INTERVAL_MS = 60_000;
109
113
  exports.MAX_COMMITMENT_INTERVAL_MS = 365 * 24 * 60 * 60 * 1000;
114
+ /**
115
+ * The lines every Commitment read surface prints for "whose permissions does a
116
+ * run use?" — `show`, `effective-policy` and `preflight` share it so the three
117
+ * cannot phrase it three ways (OSK-12065). The sentence and the warnings are
118
+ * the server's; this only lays them out. `null` (an older server that does not
119
+ * report it) prints nothing rather than a guess.
120
+ */
121
+ function permissionLines(permissions) {
122
+ if (!permissions?.summary)
123
+ return [];
124
+ const lines = [`Permissions: ${permissions.summary}`];
125
+ for (const warning of permissions.warnings || []) {
126
+ lines.push(`Warning: ${warning.message}`);
127
+ if (warning.fix)
128
+ lines.push(` Fix: ${warning.fix}`);
129
+ }
130
+ return lines;
131
+ }
132
+ /**
133
+ * One line naming fields that are still in the JSON but decide nothing any
134
+ * more. Deprecation, not removal: `--json` keeps them for scripts.
135
+ */
136
+ function retiredFieldsLine(retired, reason) {
137
+ if (!Array.isArray(retired) || !retired.length)
138
+ return null;
139
+ return `Retired fields (no effect): ${retired.join(', ')}${reason ? ` — ${reason}` : ''}`;
140
+ }
110
141
  function base(id) {
111
142
  return `/api/commitments/${encodeURIComponent(id)}`;
112
143
  }
@@ -236,6 +267,10 @@ exports.commitmentApi = {
236
267
  /** Un-delete. Returns the Commitment PAUSED, never active. */
237
268
  restore: (id) => data_provider_1.request.post(`${base(id)}/restore`, {}),
238
269
  trace: (id, query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/trace`, query)),
270
+ /** One Cycle per Check, newest first — the unit a person reads (D8). */
271
+ cycles: (id, query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/cycles`, query)),
272
+ /** The latest Cycle walked as six questions; the first "no" is where it stopped. */
273
+ explain: (id) => data_provider_1.request.get(`${base(id)}/explain`),
239
274
  ledger: (id, query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/ledger`, query)),
240
275
  wakeCapabilities: (id) => data_provider_1.request.get(`${base(id)}/wake-capabilities`),
241
276
  dailyMetrics: (id, query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/daily-metrics`, query)),
@@ -1007,6 +1042,11 @@ function describeWakeDecision(check) {
1007
1042
  if (verdict === 'skipped') {
1008
1043
  const reason = typeof entry.reason === 'string' ? entry.reason : '';
1009
1044
  if (reason === 'posture_does_not_admit_agent_run') {
1045
+ // The server names the commonest cause on the check's `stop`: nothing
1046
+ // was open to act on. "record_only" alone read as a setting to change
1047
+ // when the remedy was to give the Commitment some work (OSK-12090).
1048
+ if (check.stop?.reason === 'no_open_loop')
1049
+ return 'no — nothing open';
1010
1050
  const posture = typeof entry.posture === 'string' ? entry.posture : '';
1011
1051
  return posture ? `no — ${posture}` : 'no — posture';
1012
1052
  }
@@ -1160,23 +1200,131 @@ function renderCommitmentLedger(ledger, log) {
1160
1200
  }
1161
1201
  }
1162
1202
  /** Human view for the ordered commitment trace. */
1203
+ /**
1204
+ * `question:reason` for a trace event's server-derived stop — which of the six
1205
+ * questions (wake, fresh, turn, think, act, reach) the wake stopped at. `none`
1206
+ * means an Agent turn was dispatched. Blank when the event carries no stop.
1207
+ */
1208
+ function stopCell(item) {
1209
+ const detail = item.detail;
1210
+ const stop = detail && typeof detail === 'object' ? detail.stop : undefined;
1211
+ if (!stop || typeof stop !== 'object')
1212
+ return '';
1213
+ const { question, gate, reason } = stop;
1214
+ if (typeof question !== 'string')
1215
+ return '';
1216
+ let why = '';
1217
+ if (typeof reason === 'string' && reason)
1218
+ why = reason;
1219
+ else if (typeof gate === 'string')
1220
+ why = gate;
1221
+ return why ? `${question}: ${why}` : question;
1222
+ }
1163
1223
  function renderCommitmentTrace(trace, log) {
1164
1224
  const events = records(trace.events);
1165
1225
  renderRows(events.map((item) => ({
1166
1226
  at: text(firstValue(item, ['occurredAt', 'createdAt', 'startedAt'])),
1167
1227
  type: text(firstValue(item, ['type', 'kind'])),
1168
1228
  status: text(item.status),
1229
+ stop: stopCell(item),
1169
1230
  summary: text(firstValue(item, ['summary', 'reason', 'title']), ''),
1170
1231
  })), [
1171
1232
  { key: 'at', header: 'AT', maxWidth: 24 },
1172
1233
  { key: 'type', header: 'TYPE', maxWidth: 28 },
1173
1234
  { key: 'status', header: 'STATUS', maxWidth: 28 },
1235
+ { key: 'stop', header: 'STOPPED AT', maxWidth: 48 },
1174
1236
  { key: 'summary', header: 'SUMMARY', maxWidth: 72 },
1175
1237
  ], 'No trace events recorded.', log);
1176
1238
  if (trace.has_more === true && trace.next_cursor) {
1177
1239
  log(`More events are available; continue with --cursor ${text(trace.next_cursor)}.`);
1178
1240
  }
1179
1241
  }
1242
+ const CYCLE_GLYPH = {
1243
+ advanced: '✓',
1244
+ stopped: '●',
1245
+ quiet: '○',
1246
+ in_flight: '◌',
1247
+ };
1248
+ /** `MM-DD HH:MM` in the reader's local time; `-` when the time is unknown. */
1249
+ function cycleTime(value) {
1250
+ if (typeof value !== 'string')
1251
+ return '-';
1252
+ const date = new Date(value);
1253
+ if (Number.isNaN(date.getTime()))
1254
+ return '-';
1255
+ const pad = (n) => String(n).padStart(2, '0');
1256
+ return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
1257
+ }
1258
+ function cycleWake(cycle) {
1259
+ const wake = cycle.wake || {};
1260
+ if (wake.kind === 'fire')
1261
+ return `${wake.source || 'trigger'} wake`;
1262
+ return wake.source ? `${wake.kind} (${wake.source})` : String(wake.kind || '-');
1263
+ }
1264
+ /** What one Cycle came to, in a single line. */
1265
+ function cycleSummary(cycle) {
1266
+ if (cycle.outcome === 'stopped' && cycle.stop) {
1267
+ const { question, gate, reason, fix, detail } = cycle.stop;
1268
+ return `${question} ${reason || gate} — ${fix || detail || 'stopped'}`;
1269
+ }
1270
+ if (cycle.outcome === 'advanced') {
1271
+ return cycle.advancementKinds?.length
1272
+ ? cycle.advancementKinds.join(', ')
1273
+ : 'applied an agent result';
1274
+ }
1275
+ if (cycle.outcome === 'in_flight') {
1276
+ const runs = cycle.runs?.length || 0;
1277
+ return runs ? `${runs} run(s) still going` : 'check still running';
1278
+ }
1279
+ return cycle.note || cycle.decision?.summary || cycle.decision?.posture || 'nothing to act on';
1280
+ }
1281
+ /**
1282
+ * One line per Cycle:
1283
+ * `09-22 12:04 ● stopped act commitment_execution_budget_exhausted — Raise maxRunsPerDay…`
1284
+ * A run that failed is named under its Cycle with who has to move (machine
1285
+ * or model), because that is the next question every reader asks.
1286
+ */
1287
+ function formatCommitmentCycles(page) {
1288
+ const cycles = Array.isArray(page.cycles) ? page.cycles : [];
1289
+ if (!cycles.length)
1290
+ return ['No cycles recorded yet.'];
1291
+ const lines = [];
1292
+ for (const cycle of cycles) {
1293
+ const glyph = CYCLE_GLYPH[cycle.outcome] || '?';
1294
+ lines.push(`${cycleTime(cycle.startedAt)} ${glyph} ${String(cycle.outcome).padEnd(9)} ${cycleWake(cycle).padEnd(18)} ${cycleSummary(cycle)}`);
1295
+ for (const run of cycle.runs || []) {
1296
+ if (!['machine', 'contract', 'work'].includes(run.failureClass))
1297
+ continue;
1298
+ const owner = run.failureOwner === 'model' ? 'model' : run.failureOwner || 'work';
1299
+ lines.push(` run ${run.id} ${run.status} — ${owner}: ${run.failureCode || '-'}`);
1300
+ }
1301
+ }
1302
+ if (page.has_more && page.next_cursor) {
1303
+ lines.push(`More cycles are available; continue with --cursor ${page.next_cursor}.`);
1304
+ }
1305
+ return lines;
1306
+ }
1307
+ const ANSWER_GLYPH = { yes: '✓', no: '✗', 'n/a': '–' };
1308
+ /** The six questions for the latest Cycle, the first ✗ carrying its fix. */
1309
+ function formatCommitmentExplanation(explanation) {
1310
+ const cycle = explanation.cycle;
1311
+ const lines = [
1312
+ cycle
1313
+ ? `Latest cycle: ${cycleTime(cycle.startedAt)} ${cycle.outcome} (${cycle.id})`
1314
+ : `No cycle yet (status: ${explanation.status || '-'})`,
1315
+ ];
1316
+ for (const answer of explanation.answers || []) {
1317
+ lines.push(` ${ANSWER_GLYPH[answer.answer] || '?'} ${answer.question} ${answer.because}`);
1318
+ if (answer.fix)
1319
+ lines.push(` Fix: ${answer.fix}`);
1320
+ }
1321
+ lines.push(!explanation.stoppedAt
1322
+ ? 'Nothing stopped this cycle.'
1323
+ : explanation.verdict === 'quiet'
1324
+ ? `Chose to stay quiet at: ${explanation.stoppedAt}`
1325
+ : `Stopped at: ${explanation.stoppedAt}`);
1326
+ return lines;
1327
+ }
1180
1328
  /**
1181
1329
  * Print the continuation hint for a paged `object:'list'` response — the line
1182
1330
  * 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;
@@ -117,10 +138,16 @@ export interface DaemonBrokerFailure {
117
138
  * the daemon".
118
139
  */
119
140
  daemonServerUrl?: string;
141
+ /**
142
+ * For `base_url_mismatch` found locally: the descriptor file that named
143
+ * `daemonServerUrl`, so a message can say what was read rather than assert
144
+ * where "the" daemon is logged in (OSK-12117).
145
+ */
146
+ descriptorPath?: string;
120
147
  }
121
148
  export type DaemonBrokerOutcome = (DaemonBrokerResult & {
122
149
  ok: true;
123
- }) | DaemonBrokerFailure;
150
+ }) | DaemonBrokeredOutcome | DaemonBrokerFailure;
124
151
  export interface DaemonBrokerOptions {
125
152
  /** Stable per-machine cliId the CLI wants the new refresh row bound to. */
126
153
  cliId: string;
@@ -151,6 +178,22 @@ export interface DaemonBrokerOptions {
151
178
  candidatePathsOverride?: readonly string[];
152
179
  /** Override total request timeout. Tests use a short value. */
153
180
  timeoutMs?: number;
181
+ /**
182
+ * Which advertised mode to prefer when a descriptor offers both
183
+ * (`handoffModes: ['access_token', 'refresh_family']`, the laptop island
184
+ * shape during transition):
185
+ *
186
+ * - `'access_token'` — broker mode: redeem a short-lived access token
187
+ * and hold it in memory only. Used by `maybeAutoBroker`: nothing
188
+ * credential-shaped lands on disk and each `skrr` process re-redeems
189
+ * over loopback.
190
+ * - `'refresh_family'` (default) — the legacy durable mint. Used by
191
+ * explicit `skrr login`: a human login still writes a real,
192
+ * user-owned refresh family. A descriptor that advertises ONLY
193
+ * `access_token` (the Dedicated guest shape) redeems brokered
194
+ * regardless — its secret cannot mint a family at all.
195
+ */
196
+ preferHandoffMode?: 'access_token' | 'refresh_family';
154
197
  }
155
198
  /**
156
199
  * Compute the bootstrap file path. Exported for tests so they can assert
@@ -187,6 +230,14 @@ export declare function resolveBootstrapCandidatePaths(profile?: string): string
187
230
  export declare const DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR = "/run/skrr-dedicated-runtime/cli-handoff/descriptor.json";
188
231
  /** Where the cli-handoff broker looks, in order. */
189
232
  export declare function resolveCliHandoffCandidatePaths(profile?: string): string[];
233
+ /**
234
+ * The island files that belong to exactly `profile` — its canonical path and
235
+ * the pre-rename darwin location — and never another profile's. The broker's
236
+ * candidate list for `default` deliberately falls through to any running
237
+ * profile, which is right for borrowing a credential and wrong for naming
238
+ * which daemon a profile's status describes (OSK-12102).
239
+ */
240
+ export declare function resolveExactProfileBootstrapPaths(profile?: string): string[];
190
241
  /**
191
242
  * Resolve the first live daemon bootstrap for a profile. Message-intent
192
243
  * submission reuses the same authenticated loopback discovery as the login
@@ -214,10 +265,60 @@ export declare function readBootstrap(filePath: string): LocalBootstrap | null;
214
265
  */
215
266
  export declare function attemptDaemonBrokerLogin(opts: DaemonBrokerOptions): Promise<DaemonBrokerOutcome>;
216
267
  /**
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`.
268
+ * True when `token` is the access token this process redeemed through the
269
+ * daemon's broker-mode hand-off. `api-fetch` uses this to pick the re-redeem
270
+ * recovery branch instead of the stored-family refresh (which would have
271
+ * nothing to rotate — brokered credentials are never persisted).
272
+ */
273
+ export declare function isBrokeredHandoffToken(token: string | null | undefined): boolean;
274
+ /**
275
+ * Re-redeem the brokered hand-off token — the 401-recovery path. The
276
+ * descriptor is re-read from disk when its source path is known, so a secret
277
+ * rotated by a daemon restart resolves to the fresh one. Returns null when
278
+ * this process holds no brokered credential or the redeem fails (the caller
279
+ * then surfaces the original 401).
280
+ */
281
+ export declare function redeemBrokeredHandoffToken(forceRefresh?: boolean): Promise<CliHandoffTokenResponse | null>;
282
+ /** @internal test seam — clears the module-scoped brokered credential. */
283
+ export declare function __resetBrokeredHandoffForTest(): void;
284
+ /**
285
+ * True when a live local descriptor advertises broker-mode access — i.e.
286
+ * this machine's CLI authority is platform-managed and `skrr logout` has no
287
+ * local credential to clear. Read-only: parses candidates, never redeems.
288
+ */
289
+ export declare function findBrokeredHandoffDescriptor(profile?: string): LocalBootstrap | null;
290
+ /**
291
+ * Migration cleanup: retire the refresh family a pre-broker-mode CLI
292
+ * generation persisted, now that this machine redeems access tokens from
293
+ * the daemon's in-memory broker.
294
+ *
295
+ * Provenance guard — locally we cannot tell a hand-off-minted family from a
296
+ * user's explicit `skrr login` family by shape (the distinguishing
297
+ * `daemonRefOverride` lives server-side). The safe rule, applied here:
298
+ * revoke + delete ONLY when all of these hold —
299
+ *
300
+ * 1. the descriptor that just redeemed IS the Dedicated Runtime guest
301
+ * hand-off file (`/run/skrr-dedicated-runtime/...`),
302
+ * 2. `~/.skrr/cli-auth.json` reads cleanly and carries a refresh token,
303
+ * 3. its stored `serverOrigin` matches the descriptor's `serverUrl`.
304
+ *
305
+ * When provenance can't be proven the file is KEPT: an explicit `skrr login`
306
+ * on a guest is rare but real, and a stale extra family is bounded by the
307
+ * server's session cap and expires on its own. Best-effort throughout — a
308
+ * failed revoke still deletes the file (the whole point is that nothing
309
+ * credential-shaped should remain), and any failure leaves the working
310
+ * brokered credential untouched.
311
+ */
312
+ export declare function maybeRetireStoredCliAuthFile(opts: {
313
+ descriptorPath?: string;
314
+ descriptorServerUrl?: string;
315
+ }): Promise<void>;
316
+ /**
317
+ * Convenience wrapper: run the broker call and, on a durable-mint success,
318
+ * persist the returned bundle through the canonical Sky CLI auth backend
319
+ * (Keychain on macOS, `cli-auth.json` elsewhere). A `brokered` outcome is
320
+ * returned WITHOUT persisting — that is the entire point of broker mode.
321
+ * Returns the same outcome shape so the caller can branch on `ok`/`brokered`.
221
322
  */
222
323
  export declare function attemptDaemonBrokerLoginAndPersist(opts: DaemonBrokerOptions): Promise<DaemonBrokerOutcome>;
223
324
  export interface MaybeAutoBrokerOptions {
@@ -275,10 +376,12 @@ export interface MaybeAutoBrokerResult {
275
376
  * - `commandId` is in `AUTH_SELF_MANAGED_COMMANDS`
276
377
  *
277
378
  * 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).
379
+ * - Calls `attemptDaemonBrokerLoginAndPersist` with `preferHandoffMode:
380
+ * 'access_token'`. On a descriptor that advertises broker mode the
381
+ * outcome is `brokered` — an in-memory access token, nothing written;
382
+ * the caller holds it for this process and the next `skrr` re-redeems.
383
+ * On a legacy/v1 descriptor the durable mint is persisted to the
384
+ * keychain / cli-auth.json exactly as before.
282
385
  * - May generate a new `cliId` if the config doesn't have one. The
283
386
  * caller MUST persist `updatedConfig` so the next invocation
284
387
  * presents the same id to the server's refresh rotator.