@skrr-ai/cli 0.1.123 → 0.1.124

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 (85) hide show
  1. package/dist/commands/agents/delete.d.ts +8 -0
  2. package/dist/commands/agents/delete.js +16 -0
  3. package/dist/commands/context.js +7 -0
  4. package/dist/commands/search/workspace.js +1 -1
  5. package/dist/commands/store/gallery-admissions/approve.js +1 -1
  6. package/dist/commands/store/gallery-admissions/preview.js +1 -1
  7. package/dist/commands/store/gallery-admissions/refuse.js +1 -1
  8. package/dist/commands/store/gallery-admissions/show.js +1 -1
  9. package/dist/lib/auth-core-init.d.ts +15 -2
  10. package/dist/lib/auth-core-init.js +47 -6
  11. package/dist/lib/command-hygiene.js +30 -1
  12. package/dist/lib/daemonBrokerRefusal.js +5 -1
  13. package/dist/lib/keychain.js +41 -4
  14. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +5 -1
  15. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +1 -1
  16. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +13 -28
  17. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +133 -4
  18. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +387 -90
  19. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.d.ts +106 -7
  20. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.js +77 -19
  21. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.d.ts +33 -0
  22. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.js +77 -0
  23. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.d.ts +68 -0
  24. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.js +264 -0
  25. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.d.ts +31 -0
  26. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.js +90 -0
  27. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.js +2 -0
  28. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.js +2 -0
  29. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +7 -3
  30. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +102 -0
  31. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.d.ts +1 -1
  32. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.js +6 -0
  33. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.d.ts +18 -0
  34. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.js +8 -0
  35. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.d.ts +22 -0
  36. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.js +11 -0
  37. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.d.ts +43 -3
  38. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.js +33 -3
  39. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.d.ts +154 -0
  40. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.js +281 -0
  41. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +6 -3
  42. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +8 -5
  43. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/profileStateDir.js +7 -27
  44. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +1 -1
  45. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +12 -10
  46. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.d.ts +31 -0
  47. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.js +89 -0
  48. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +5 -1
  49. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +4 -1
  50. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +15 -37
  51. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +133 -4
  52. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +536 -101
  53. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.d.ts +106 -7
  54. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.js +131 -27
  55. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.d.ts +33 -0
  56. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.js +81 -0
  57. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.d.ts +68 -0
  58. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.js +317 -0
  59. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.d.ts +31 -0
  60. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.js +86 -0
  61. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.js +4 -0
  62. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.js +4 -0
  63. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +7 -3
  64. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +13 -3
  65. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.d.ts +1 -1
  66. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.js +1 -1
  67. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.d.ts +18 -0
  68. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.js +15 -0
  69. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.d.ts +22 -0
  70. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.js +20 -0
  71. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.d.ts +43 -3
  72. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.js +104 -8
  73. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.d.ts +154 -0
  74. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.js +386 -0
  75. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +6 -3
  76. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +11 -5
  77. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/profileStateDir.js +6 -35
  78. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +1 -1
  79. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +14 -10
  80. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.d.ts +31 -0
  81. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.js +73 -0
  82. package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
  83. package/dist/node_modules/@skrr-ai/data-provider/index.js +25 -7
  84. package/oclif.manifest.json +1 -1
  85. package/package.json +4 -1
@@ -8,6 +8,14 @@ import { BaseCommand } from '../../base-command';
8
8
  export declare function agentRemovalQuestion(agentId: string, kind: AgentRemovalKind | null): string;
9
9
  /** What happened, read from the server's response rather than predicted. */
10
10
  export declare function agentRemovalReport(agentId: string, response: DeleteAgentResponse | undefined): string;
11
+ /**
12
+ * What became of the agent's Personal Context data, read from the same response
13
+ * (OSK-20112). The agent row is gone as soon as the delete returns, but data
14
+ * derived from its connected sources is erased separately: `Deleted agent` must
15
+ * never stand in for "erased". Nothing is printed when the server reported
16
+ * nothing (an older server, or an uninstall, which erases nothing).
17
+ */
18
+ export declare function agentErasureReport(response: DeleteAgentResponse | undefined): string[];
11
19
  export default class AgentsDelete extends BaseCommand {
12
20
  static description: string;
13
21
  static examples: string[];
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.agentRemovalQuestion = agentRemovalQuestion;
4
4
  exports.agentRemovalReport = agentRemovalReport;
5
+ exports.agentErasureReport = agentErasureReport;
5
6
  const core_1 = require("@oclif/core");
6
7
  const data_provider_1 = require("@skrr-ai/data-provider");
7
8
  const base_command_1 = require("../../base-command");
@@ -38,6 +39,19 @@ function agentRemovalReport(agentId, response) {
38
39
  return `Deleted agent ${agentId}`;
39
40
  }
40
41
  }
42
+ /**
43
+ * What became of the agent's Personal Context data, read from the same response
44
+ * (OSK-20112). The agent row is gone as soon as the delete returns, but data
45
+ * derived from its connected sources is erased separately: `Deleted agent` must
46
+ * never stand in for "erased". Nothing is printed when the server reported
47
+ * nothing (an older server, or an uninstall, which erases nothing).
48
+ */
49
+ function agentErasureReport(response) {
50
+ if ((0, data_provider_1.agentRemovalKind)(response) !== 'deleted')
51
+ return [];
52
+ const erasure = response && 'erasure' in response ? response.erasure : undefined;
53
+ return (0, data_provider_1.describePersonalContextErasure)(erasure)?.lines ?? [];
54
+ }
41
55
  class AgentsDelete extends base_command_1.BaseCommand {
42
56
  static description = 'Delete an agent you created (irreversible), or uninstall one installed from the store (reinstalling restores it)';
43
57
  static examples = [
@@ -89,6 +103,8 @@ class AgentsDelete extends base_command_1.BaseCommand {
89
103
  this.handleApiError(err);
90
104
  }
91
105
  this.log(agentRemovalReport(args.id, response));
106
+ for (const line of agentErasureReport(response))
107
+ this.log(line);
92
108
  }
93
109
  }
94
110
  exports.default = AgentsDelete;
@@ -64,6 +64,13 @@ class Context extends base_command_1.BaseCommand {
64
64
  path: (0, config_1.getConfigPath)(),
65
65
  cliId: (0, cli_id_1.resolveCliId)(this.cliConfig) ?? null,
66
66
  },
67
+ // Silent about the DAEMON's device key on purpose (OSK-20507). This is the CLI's
68
+ // own sign-in, and the CLI never requires a device key (OSK-12063), so an
69
+ // unreadable one changes nothing an agent reading this can do. The daemon's
70
+ // account of its key is `skrr daemon status` (`problems`, from `/health`
71
+ // `deviceIdentity`), and the one line a `skrr` process prints about it is the
72
+ // notice in `lib/auth-core-init.ts`. Adding it here would put a machine fact
73
+ // beside a per-process one and read as this credential's state.
67
74
  auth: {
68
75
  signedIn: Boolean(this.resolvedCredential?.token),
69
76
  tokenSource: this.credentialSourceLabel(),
@@ -104,7 +104,7 @@ class SearchWorkspace extends base_command_1.BaseCommand {
104
104
  url: `${(0, web_url_1.webOrigin)(this.cliConfig.baseURL)}${item.href}`,
105
105
  }));
106
106
  if (flags.json) {
107
- this.log(JSON.stringify({ ...response, results }, null, 2));
107
+ this.printJsonResult({ ...response, results });
108
108
  return;
109
109
  }
110
110
  for (const diagnostic of new Set(response.diagnostics || []))
@@ -24,7 +24,7 @@ class StoreGalleryAdmissionsApprove extends base_command_1.BaseCommand {
24
24
  revision: flags.revision, digest: flags.digest, imagesReviewed: true, ...(flags.note ? { note: flags.note } : {}),
25
25
  });
26
26
  if (flags.json)
27
- this.log(JSON.stringify(result, null, 2));
27
+ this.printJsonResult(result);
28
28
  else
29
29
  this.log(`Approved gallery revision ${flags.revision}.`);
30
30
  }
@@ -36,7 +36,7 @@ class StoreGalleryAdmissionsPreview extends base_command_1.BaseCommand {
36
36
  await (0, promises_1.writeFile)(flags.output, bytes, { flag: 'wx', mode: 0o600 });
37
37
  const receipt = { requestId: args.id, kind, revision: review.revision, digest: review.digest, imageId: args.imageId, sha256: digest, path: flags.output, bytes: bytes.length };
38
38
  if (flags.json)
39
- this.log(JSON.stringify(receipt));
39
+ this.printJsonResult(receipt);
40
40
  else
41
41
  this.log(`Wrote ${bytes.length} bytes to ${flags.output} (sha256:${digest}). Review revision ${review.revision}, digest ${review.digest}.`);
42
42
  }
@@ -23,7 +23,7 @@ class StoreGalleryAdmissionsRefuse extends base_command_1.BaseCommand {
23
23
  revision: flags.revision, digest: flags.digest, note: flags.note.trim(),
24
24
  });
25
25
  if (flags.json)
26
- this.log(JSON.stringify(result, null, 2));
26
+ this.printJsonResult(result);
27
27
  else
28
28
  this.log(`Refused gallery revision ${flags.revision}.`);
29
29
  }
@@ -16,7 +16,7 @@ class StoreGalleryAdmissionsShow extends base_command_1.BaseCommand {
16
16
  try {
17
17
  const result = await data_provider_1.dataService.getStoreGalleryAdmission(flags.kind, args.id);
18
18
  if (flags.json) {
19
- this.log(JSON.stringify(result, null, 2));
19
+ this.printJsonResult(result);
20
20
  return;
21
21
  }
22
22
  const r = result.review;
@@ -38,9 +38,22 @@ export declare function plainCredentialEnvelopeWarning(args: readonly unknown[])
38
38
  export declare const CREDENTIAL_KEY_CHANGED_NOTICE: string;
39
39
  /**
40
40
  * The one line an unreadable device key prints when no credential-key change
41
- * explains it. The CLI never enrolls or requires a device key (OSK-12063), so
42
- * this degrades a proof, not access — say exactly that.
41
+ * explains it (OSK-20506).
42
+ *
43
+ * The CLI never enrolls or requires a device key (OSK-12063), so this degrades a
44
+ * proof, not a sign-in: say exactly that. What it must not do is point at
45
+ * `skrr login`. That signs this CLI in on its own and never creates or replaces a
46
+ * device key, so a person who followed it would be told the same thing on the next
47
+ * command. Only an interactive daemon sign-in mints a key and re-pins it, and which
48
+ * one depends on who runs the daemon, so this uses the sentence the hand-off
49
+ * refusals use for the same repair (`daemonSignInRemedy`) rather than a second
50
+ * spelling.
51
+ *
52
+ * `profile` names the command for a non-default profile: a bare `skrr daemon login`
53
+ * signs the default daemon in and leaves this one as it was (OSK-16435).
43
54
  */
55
+ export declare function deviceKeyRecoveryRequiredNotice(profile?: string): string;
56
+ /** The notice for the default profile. */
44
57
  export declare const DEVICE_KEY_RECOVERY_REQUIRED_NOTICE: string;
45
58
  export declare function createCredentialEnvelopeWarningPresenter(): (args: readonly unknown[]) => string | null;
46
59
  type DedicatedGenerationPreparationOptions = {
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.CliCredentialInitializationError = exports.DEVICE_KEY_RECOVERY_REQUIRED_NOTICE = exports.CREDENTIAL_KEY_CHANGED_NOTICE = void 0;
7
7
  exports.plainCredentialEnvelopeWarning = plainCredentialEnvelopeWarning;
8
+ exports.deviceKeyRecoveryRequiredNotice = deviceKeyRecoveryRequiredNotice;
8
9
  exports.createCredentialEnvelopeWarningPresenter = createCredentialEnvelopeWarningPresenter;
9
10
  exports.prepareDedicatedRuntimeAuthGeneration = prepareDedicatedRuntimeAuthGeneration;
10
11
  exports.ensureAuthCoreConfigured = ensureAuthCoreConfigured;
@@ -38,10 +39,12 @@ const node_fs_1 = __importDefault(require("node:fs"));
38
39
  const node_path_1 = __importDefault(require("node:path"));
39
40
  const proper_lockfile_1 = __importDefault(require("proper-lockfile"));
40
41
  const auth_core_1 = require("@skrr-ai/auth-core");
42
+ const cli_handoff_wire_1 = require("@skrr-ai/auth-core/cli-handoff-wire");
41
43
  const cred_envelope_1 = require("./cred-envelope");
42
44
  const device_identity_1 = require("./device-identity");
43
45
  const auth_storage_1 = require("./auth-storage");
44
46
  const keychain_1 = require("./keychain");
47
+ const daemon_command_hint_1 = require("./daemon-command-hint");
45
48
  function envFlag(name) {
46
49
  const v = process.env[name];
47
50
  return v === '1' || v === 'true' || v === 'TRUE';
@@ -117,13 +120,33 @@ const DEVICE_KEY_DECRYPT_FAILED = '[deviceIdentity] private_key_decrypt_failed';
117
120
  const DEVICE_KEY_RECOVERY_REQUIRED = '[deviceIdentity] recovery_required';
118
121
  /**
119
122
  * The one line an unreadable device key prints when no credential-key change
120
- * explains it. The CLI never enrolls or requires a device key (OSK-12063), so
121
- * this degrades a proof, not access — say exactly that.
123
+ * explains it (OSK-20506).
124
+ *
125
+ * The CLI never enrolls or requires a device key (OSK-12063), so this degrades a
126
+ * proof, not a sign-in: say exactly that. What it must not do is point at
127
+ * `skrr login`. That signs this CLI in on its own and never creates or replaces a
128
+ * device key, so a person who followed it would be told the same thing on the next
129
+ * command. Only an interactive daemon sign-in mints a key and re-pins it, and which
130
+ * one depends on who runs the daemon, so this uses the sentence the hand-off
131
+ * refusals use for the same repair (`daemonSignInRemedy`) rather than a second
132
+ * spelling.
133
+ *
134
+ * `profile` names the command for a non-default profile: a bare `skrr daemon login`
135
+ * signs the default daemon in and leaves this one as it was (OSK-16435).
122
136
  */
123
- exports.DEVICE_KEY_RECOVERY_REQUIRED_NOTICE = "warning: this machine's stored device key could not be read, so skrr sends requests " +
124
- 'without a device proof until the local daemon signs in again. (DEBUG=1 shows the underlying error.)';
137
+ function deviceKeyRecoveryRequiredNotice(profile = keychain_1.DEFAULT_PROFILE) {
138
+ const loginCommand = (0, daemon_command_hint_1.daemonCommandHint)('login', { profile });
139
+ return ("warning: this machine's stored device key could not be read, so skrr sends requests " +
140
+ `without a device proof. To replace it, ${(0, cli_handoff_wire_1.daemonSignInRemedy)({ repinsDeviceKey: true, loginCommand })}. ` +
141
+ '(DEBUG=1 shows the underlying error.)');
142
+ }
143
+ /** The notice for the default profile. */
144
+ exports.DEVICE_KEY_RECOVERY_REQUIRED_NOTICE = deviceKeyRecoveryRequiredNotice(keychain_1.DEFAULT_PROFILE);
125
145
  function createCredentialEnvelopeWarningPresenter() {
126
146
  let keyChanged = false;
147
+ // auth-core named a failure as the DEVICE key's (see below): the person has one
148
+ // line to read for it, and it is not the credential line.
149
+ let deviceKeyUnreadable = false;
127
150
  return (args) => {
128
151
  const [message, metadata] = args;
129
152
  const event = typeof message === 'string' && message.startsWith('[credEnvelope] ')
@@ -157,10 +180,28 @@ function createCredentialEnvelopeWarningPresenter() {
157
180
  (message === DEVICE_KEY_DECRYPT_FAILED || message === DEVICE_KEY_RECOVERY_REQUIRED)) {
158
181
  return '';
159
182
  }
183
+ // No key change in THIS process, yet the device key will not open (the
184
+ // credential key changed in an earlier one, or the file is damaged).
185
+ // auth-core's device identity labels that read with its own hint, so the
186
+ // producer says WHAT failed and this decides what a person reads. The
187
+ // generic line would say "signing in again rebuilds it", and `skrr login`
188
+ // does not rebuild a device key. Print nothing for it, and nothing for the
189
+ // device identity's own echo of the same failure: `recovery_required`
190
+ // follows and carries the one line (OSK-20506). The flag is set by that
191
+ // hint alone, so another credential that will not open still gets its own
192
+ // line below.
193
+ if (event === 'cred_envelope.read.decrypt_failed' &&
194
+ meta.hint === auth_core_1.DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT) {
195
+ deviceKeyUnreadable = true;
196
+ return '';
197
+ }
198
+ if (deviceKeyUnreadable && message === DEVICE_KEY_DECRYPT_FAILED)
199
+ return '';
160
200
  // Without a key change an unreadable device key is a real fault: one
161
201
  // plain line instead of an object dump.
162
- if (message === DEVICE_KEY_RECOVERY_REQUIRED)
163
- return exports.DEVICE_KEY_RECOVERY_REQUIRED_NOTICE;
202
+ if (message === DEVICE_KEY_RECOVERY_REQUIRED) {
203
+ return deviceKeyRecoveryRequiredNotice((0, keychain_1.getActiveProfile)());
204
+ }
164
205
  return plainCredentialEnvelopeWarning(args);
165
206
  };
166
207
  }
@@ -453,6 +453,35 @@ const AGENT_CRITICAL_COMMANDS = [
453
453
  'agent:message:send',
454
454
  'tasks:complete',
455
455
  ];
456
+ /**
457
+ * The text of a command file, with a pure re-export alias read as its target.
458
+ *
459
+ * `export { default } from '../search/tasks';` is a second spelling of another
460
+ * command: oclif loads the same class, so the alias has the target's flags,
461
+ * description and args. Reading the alias's own few lines reports a missing
462
+ * `--json` and a missing description that the command in fact has, and the only
463
+ * way to silence that would be to copy the description into the alias, where
464
+ * the two spellings could drift apart.
465
+ *
466
+ * One hop, and only when the re-export is the whole module (comments aside), so
467
+ * a file that adds its own code is still audited as itself.
468
+ */
469
+ function readCommandSource(file) {
470
+ const source = fs.readFileSync(file, 'utf8');
471
+ const code = source
472
+ .replace(/\/\*[\s\S]*?\*\//g, '')
473
+ .replace(/\/\/.*$/gm, '')
474
+ .trim();
475
+ const alias = code.match(/^export\s*\{\s*default\s*\}\s*from\s*['"](\.{1,2}\/[^'"]+)['"]\s*;?$/);
476
+ if (!alias)
477
+ return source;
478
+ const target = path.resolve(path.dirname(file), alias[1]);
479
+ for (const candidate of [`${target}.ts`, path.join(target, 'index.ts')]) {
480
+ if (fs.existsSync(candidate))
481
+ return fs.readFileSync(candidate, 'utf8');
482
+ }
483
+ return source;
484
+ }
456
485
  function auditCommands({ rootDir, manifest, }) {
457
486
  const issues = [];
458
487
  const commands = manifest?.commands ?? {};
@@ -512,7 +541,7 @@ function auditCommands({ rootDir, manifest, }) {
512
541
  sourceCommandFiles = files.length;
513
542
  for (const file of files) {
514
543
  const rel = path.relative(sourceRoot, file);
515
- const source = fs.readFileSync(file, 'utf8');
544
+ const source = readCommandSource(file);
516
545
  const command = commandIdFromRelativePath(rel);
517
546
  sourceCommandIds.add(command);
518
547
  const hasFromJson = /['"]from-json['"]/.test(source);
@@ -30,6 +30,10 @@ function isAnsweredBrokerRefusal(outcome) {
30
30
  /** Codes whose repair is signing the DAEMON in again (laptop only). */
31
31
  const DAEMON_RELOGIN_CODES = new Set([
32
32
  'DEVICE_PROOF_PIN_MISMATCH',
33
+ // The daemon's device key could not be read, so it sent no proof (OSK-20506). Only an
34
+ // interactive daemon login creates and pins a key; waiting never does, which is why
35
+ // the daemon's own mint hold treats it as credential-bound (`CREDENTIAL_BOUND_CODES`).
36
+ 'DEVICE_PROOF_MISSING',
33
37
  'SESSION_REVOKED',
34
38
  'PRINCIPAL_UNRESOLVED',
35
39
  'DAEMON_NOT_AUTHED',
@@ -88,7 +92,7 @@ function describeBrokerRefusal(failure, bin = 'skrr') {
88
92
  `\`${bin} machines dedicated health-check <lease-id>\` (\`${bin} machines dedicated list\` shows the lease id). ` +
89
93
  guestReissueRemedy(bin, 'If it stays refused');
90
94
  }
91
- else if (code === 'DEVICE_PROOF_PIN_MISMATCH') {
95
+ else if (code === 'DEVICE_PROOF_PIN_MISMATCH' || code === 'DEVICE_PROOF_MISSING') {
92
96
  remedy =
93
97
  `Run \`${(0, daemon_command_hint_1.daemonCommandHint)('login', { bin })}\` to sign the background service in again — an interactive daemon login re-pins its device key — then retry. ` +
94
98
  `\`${bin} login\` signs this CLI in on its own instead.`;
@@ -609,7 +609,9 @@ function deleteRefreshToken() {
609
609
  * prints service names and does not prompt for or reveal any secret.
610
610
  *
611
611
  * Filtered to `LEGACY_NATIVE_ID_PREFIXES` followed by a dot, so a third-party
612
- * item that merely starts with the same letters is never in scope.
612
+ * item that merely starts with the same letters is never in scope. That is the
613
+ * SCOPE of the sweep and nothing more: which of these services it deletes is
614
+ * decided by `isRetiredBrandSweepTarget`, by name.
613
615
  */
614
616
  function discoverLegacyBrandServices() {
615
617
  let dump;
@@ -639,6 +641,12 @@ function discoverLegacyBrandServices() {
639
641
  * because a delete that never reports "not found" would otherwise spin.
640
642
  */
641
643
  function deleteEntireService(service) {
644
+ // The sweep can name key material (`--all-devices` selects the retired KEKs), so it is the one
645
+ // CLI mutator behind the guard. The CLI's own login slots are not: `ai.skrr.cli.<profile>`
646
+ // carries no hash of the home, so a sandbox and the account give the SAME name and the guard
647
+ // could not tell a faked `security` from the real one. A test's protection there is its
648
+ // sandbox home, under which macOS finds no default keychain to write to.
649
+ (0, auth_core_1.assertMayMutateLiveKeychain)(service, `delete every Keychain item in ${service}`);
642
650
  let removedAny = false;
643
651
  for (let i = 0; i < 32; i += 1) {
644
652
  try {
@@ -680,9 +688,38 @@ function deleteEntireService(service) {
680
688
  function deleteLegacyBrandEntries(options = {}) {
681
689
  if (!isAvailable())
682
690
  return [];
683
- return discoverLegacyBrandServices()
684
- .filter((service) => options.includeMachineCredentials === true || /^(?:ai|com)\.oversky\.cli\./.test(service))
685
- .filter((service) => deleteEntireService(service));
691
+ const includeMachineCredentials = options.includeMachineCredentials === true;
692
+ const inScope = discoverLegacyBrandServices();
693
+ const targets = inScope.filter((service) => isRetiredBrandSweepTarget(service, includeMachineCredentials));
694
+ if (process.env.DEBUG) {
695
+ for (const service of inScope) {
696
+ if (!targets.includes(service)) {
697
+ console.error(`[cli/keychain] legacy sweep left unclassified service ${service}`);
698
+ }
699
+ }
700
+ }
701
+ return targets.filter((service) => deleteEntireService(service));
702
+ }
703
+ /**
704
+ * A CLI slot the previous brand wrote: `<previous prefix>.cli.<profile>`, under either of the
705
+ * two previous prefixes. Positive on the whole name, so the bare `.cli` word alone, or a
706
+ * service that merely begins with these words, is not one.
707
+ */
708
+ const RETIRED_CLI_SERVICE_PATTERN = new RegExp(`^(?:${auth_core_1.LEGACY_NATIVE_ID_PREFIXES.map((prefix) => prefix.replace(/\./g, '\\.')).join('|')})` +
709
+ '\\.cli\\.[A-Za-z0-9][A-Za-z0-9._-]*$');
710
+ /**
711
+ * Whether the legacy sweep deletes this previous-brand service. Named, not matched by prefix:
712
+ * a retired CLI slot always; and, only for `--all-devices` (`includeMachineCredentials`), the
713
+ * daemon services the shared grammar in `@skrr-ai/auth-core` classifies (sessions, the person's
714
+ * own provider keys, and the retired KEKs, which the stolen-laptop case is for). A previous-brand
715
+ * service nobody classified is left in place. The current brand is out of scope entirely.
716
+ */
717
+ function isRetiredBrandSweepTarget(service, includeMachineCredentials) {
718
+ if (!auth_core_1.LEGACY_NATIVE_ID_PREFIXES.some((prefix) => service.startsWith(`${prefix}.`)))
719
+ return false;
720
+ if (RETIRED_CLI_SERVICE_PATTERN.test(service))
721
+ return true;
722
+ return includeMachineCredentials && (0, auth_core_1.classifyDaemonKeychainService)(service) !== 'unclassified';
686
723
  }
687
724
  /**
688
725
  * Remove every CLI keychain slot for the active profile. Used by `skrr logout`.
@@ -161,7 +161,11 @@ export type DaemonSupervisor = 'desktop' | 'service';
161
161
  export declare function daemonSignInRemedy(opts?: {
162
162
  /** How the daemon is run; absent when the caller cannot tell. */
163
163
  supervisor?: DaemonSupervisor;
164
- /** The exact `skrrd … login` for this profile, when the daemon knows it. */
164
+ /**
165
+ * The exact login command for this profile (`skrrd … login`, or
166
+ * `skrr daemon login --profile …`), when the caller knows it. Used when the
167
+ * supervisor is `service` or unknown; a desktop-hosted daemon is never told one.
168
+ */
165
169
  loginCommand?: string;
166
170
  /** The refusal was DEVICE_PROOF_PIN_MISMATCH: say that a sign-in re-pins. */
167
171
  repinsDeviceKey?: boolean;
@@ -132,7 +132,7 @@ function daemonSignInRemedy(opts = {}) {
132
132
  const login = opts.loginCommand ? `\`${opts.loginCommand}\`` : "`skrr daemon login`";
133
133
  return `run ${login}${repin ? " \u2014 an interactive login re-pins the device key" : ""}`;
134
134
  }
135
- return "sign the daemon in again on the machine running it: if the Skrr desktop app runs it, quit and reopen the app; otherwise run `skrr daemon login`" + (repin ? " (either one re-pins the device key)" : "");
135
+ return `sign the daemon in again on the machine running it: if the Skrr desktop app runs it, quit and reopen the app; otherwise run \`${opts.loginCommand ?? "skrr daemon login"}\`` + (repin ? " (either one re-pins the device key)" : "");
136
136
  }
137
137
  // Annotate the CommonJS export names for ESM import in node:
138
138
  0 && (module.exports = {
@@ -55,8 +55,10 @@ var import_proper_lockfile = __toESM(require("proper-lockfile"), 1);
55
55
  var import_credentialEnvelope = require("./credentialEnvelope.js");
56
56
  var import_kek = require("./kek/index.js");
57
57
  var import_profileStateDir = require("./profileStateDir.js");
58
+ var import_liveState = require("./liveState.js");
58
59
  var import_refresh = require("./refresh.js");
59
60
  var import_runtime = require("./runtime.js");
61
+ var import_stateFiles = require("./stateFiles.js");
60
62
  const ACCOUNT_CRED_DEK_WRAPPED = "cred-dek-wrapped";
61
63
  let _state = { kind: "uninit" };
62
64
  let _kekRotationDetected = false;
@@ -71,28 +73,6 @@ function __getStateForTest() {
71
73
  function wrappedDekFilePath(profile) {
72
74
  return (0, import_profileStateDir.resolveProfileStateFile)(profile, ACCOUNT_CRED_DEK_WRAPPED).path;
73
75
  }
74
- function atomicWrite0600(target, value) {
75
- const dir = import_node_path.default.dirname(target);
76
- import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
77
- const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
78
- const fd = import_node_fs.default.openSync(tmp, "w", 384);
79
- try {
80
- import_node_fs.default.writeSync(fd, value);
81
- import_node_fs.default.fsyncSync(fd);
82
- } finally {
83
- import_node_fs.default.closeSync(fd);
84
- }
85
- import_node_fs.default.renameSync(tmp, target);
86
- try {
87
- const dirFd = import_node_fs.default.openSync(dir, "r");
88
- try {
89
- import_node_fs.default.fsyncSync(dirFd);
90
- } finally {
91
- import_node_fs.default.closeSync(dirFd);
92
- }
93
- } catch {
94
- }
95
- }
96
76
  const warnedSignatures = /* @__PURE__ */ new Set();
97
77
  const MAX_WARNED_SIGNATURES = 64;
98
78
  function __clearWarnDedupeForTest() {
@@ -124,9 +104,9 @@ function credEnvelopeLockPath(profile) {
124
104
  async function withCredEnvelopeLock(profile, body) {
125
105
  const lockPath = credEnvelopeLockPath(profile);
126
106
  const dir = import_node_path.default.dirname(lockPath);
127
- import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
107
+ (0, import_stateFiles.ensureStateDir)(dir, `take the credential envelope lock for profile ${profile}`);
128
108
  if (!import_node_fs.default.existsSync(lockPath)) {
129
- import_node_fs.default.writeFileSync(lockPath, "", { mode: 384 });
109
+ (0, import_stateFiles.writeStateFile)(lockPath, "", { mode: 384 }, "create the credential envelope lock");
130
110
  }
131
111
  const acquireStart = Date.now();
132
112
  const release = await import_proper_lockfile.default.lock(lockPath, {
@@ -202,6 +182,7 @@ async function consolidateWrappedDek(profile, kek, aad) {
202
182
  onEvent: (name, success, meta) => emit(`cred_envelope.${name}`, success, meta)
203
183
  }));
204
184
  } catch (err) {
185
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
205
186
  emit("cred_envelope.path_consolidation.failed", false, {
206
187
  profile,
207
188
  message: err?.message ?? "unknown"
@@ -268,16 +249,17 @@ function keepUnreadableWrappedDek(profile, wrapped) {
268
249
  }
269
250
  }
270
251
  const name = `${UNREADABLE_DEK_COPY_PREFIX}${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
271
- atomicWrite0600(import_node_path.default.join(dir, name), wrapped);
252
+ (0, import_stateFiles.atomicWrite0600)(import_node_path.default.join(dir, name), wrapped, "keep the unreadable wrapped DEK");
272
253
  const kept = [...existing, name].sort();
273
254
  for (const stale of kept.slice(0, Math.max(0, kept.length - MAX_UNREADABLE_DEK_COPIES))) {
274
255
  try {
275
- import_node_fs.default.unlinkSync(import_node_path.default.join(dir, stale));
256
+ (0, import_stateFiles.removeStateFile)(import_node_path.default.join(dir, stale), "prune a kept wrapped DEK");
276
257
  } catch {
277
258
  }
278
259
  }
279
260
  return name;
280
261
  } catch (err) {
262
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
281
263
  emit("cred_envelope.unreadable_dek_copy_failed", false, {
282
264
  profile,
283
265
  message: err?.message ?? "unknown"
@@ -395,6 +377,7 @@ async function initNewIndependentCredEnvelope(profile) {
395
377
  const dek = (0, import_credentialEnvelope.generateDek)();
396
378
  let temp;
397
379
  try {
380
+ (0, import_liveState.assertMayMutateLiveState)(dir, `enroll the wrapped DEK for background profile ${profile}`);
398
381
  const wrapped = await kek.wrap(dek, (0, import_credentialEnvelope.buildCredentialAad)(profile));
399
382
  if (paths.some(present))
400
383
  return await initExistingCredEnvelope(profile);
@@ -572,7 +555,7 @@ async function initCredEnvelope(profile) {
572
555
  try {
573
556
  dek = (0, import_credentialEnvelope.generateDek)();
574
557
  const wrapped = await kek.wrap(dek, aad);
575
- atomicWrite0600(targetPath, wrapped);
558
+ (0, import_stateFiles.atomicWrite0600)(targetPath, wrapped, `write the wrapped DEK for profile ${profile}`);
576
559
  emit("cred_envelope.dek_enrolled", true, {
577
560
  kekId: kek.id,
578
561
  kekKind: captureKind(),
@@ -580,6 +563,7 @@ async function initCredEnvelope(profile) {
580
563
  ...unwrapAttemptedAndFailed ? { reEnrolledAfterRotation: true } : {}
581
564
  });
582
565
  } catch (err) {
566
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
583
567
  _state = { kind: "disabled", reason: "init_failed" };
584
568
  emit("cred_envelope.init_failed", false, {
585
569
  reason: "enrollment_failed",
@@ -613,8 +597,9 @@ async function resetCredEnvelope(opts) {
613
597
  ...retiredDekFiles(opts.profile)
614
598
  ]) {
615
599
  try {
616
- import_node_fs.default.unlinkSync(target);
600
+ (0, import_stateFiles.removeStateFile)(target, `delete the wrapped DEK file ${import_node_path.default.basename(target)}`);
617
601
  } catch (err) {
602
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
618
603
  if (err.code !== "ENOENT") {
619
604
  emit("cred_envelope.clear_failed", false, {
620
605
  profile: opts.profile,