@skrr-ai/cli 0.1.122 → 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 (111) hide show
  1. package/dist/commands/agents/chat.d.ts +7 -0
  2. package/dist/commands/agents/chat.js +32 -6
  3. package/dist/commands/agents/delete.d.ts +8 -0
  4. package/dist/commands/agents/delete.js +16 -0
  5. package/dist/commands/context.js +7 -0
  6. package/dist/commands/followups/act.js +3 -0
  7. package/dist/commands/followups/checkin.js +3 -0
  8. package/dist/commands/followups/remind.js +3 -0
  9. package/dist/commands/followups/show.js +1 -1
  10. package/dist/commands/followups/watch.js +3 -0
  11. package/dist/commands/search/workspace.js +1 -1
  12. package/dist/commands/store/gallery-admissions/approve.js +1 -1
  13. package/dist/commands/store/gallery-admissions/preview.js +1 -1
  14. package/dist/commands/store/gallery-admissions/refuse.js +1 -1
  15. package/dist/commands/store/gallery-admissions/show.js +1 -1
  16. package/dist/lib/agentic-stream.d.ts +46 -0
  17. package/dist/lib/agentic-stream.js +135 -14
  18. package/dist/lib/auth-core-init.d.ts +15 -2
  19. package/dist/lib/auth-core-init.js +47 -6
  20. package/dist/lib/auth-failure.js +12 -0
  21. package/dist/lib/command-hygiene.js +30 -1
  22. package/dist/lib/daemonBrokerRefusal.js +5 -1
  23. package/dist/lib/exec-runtime-binary.d.ts +9 -0
  24. package/dist/lib/exec-runtime-binary.js +15 -1
  25. package/dist/lib/followups.d.ts +13 -0
  26. package/dist/lib/followups.js +8 -0
  27. package/dist/lib/keychain.js +41 -4
  28. package/dist/lib/profile-machine-identity-guard.js +2 -1
  29. package/dist/lib/runtime-start-verification.js +6 -1
  30. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +5 -1
  31. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +1 -1
  32. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +69 -27
  33. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +135 -4
  34. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +440 -83
  35. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.d.ts +106 -7
  36. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.js +77 -19
  37. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.d.ts +33 -0
  38. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.js +77 -0
  39. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.d.ts +68 -0
  40. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.js +264 -0
  41. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.d.ts +31 -0
  42. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.js +90 -0
  43. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.js +2 -0
  44. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.js +2 -0
  45. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +7 -3
  46. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +110 -0
  47. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.d.ts +1 -0
  48. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.js +13 -0
  49. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.d.ts +18 -0
  50. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.js +11 -2
  51. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.d.ts +22 -0
  52. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.js +14 -2
  53. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.d.ts +69 -0
  54. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.js +67 -0
  55. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.d.ts +154 -0
  56. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.js +281 -0
  57. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +6 -3
  58. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +8 -5
  59. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.d.ts +1 -1
  60. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.js +1 -0
  61. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/profileStateDir.js +7 -27
  62. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +1 -1
  63. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +24 -16
  64. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.d.ts +31 -0
  65. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.js +89 -0
  66. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.d.ts +3 -0
  67. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.js +41 -3
  68. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/types.d.ts +7 -1
  69. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +5 -1
  70. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +4 -1
  71. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +122 -38
  72. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +135 -4
  73. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +603 -90
  74. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.d.ts +106 -7
  75. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.js +131 -27
  76. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.d.ts +33 -0
  77. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.js +81 -0
  78. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.d.ts +68 -0
  79. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.js +317 -0
  80. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.d.ts +31 -0
  81. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.js +86 -0
  82. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.js +4 -0
  83. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.js +4 -0
  84. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +7 -3
  85. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +14 -4
  86. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.d.ts +1 -0
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.js +1 -0
  88. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.d.ts +18 -0
  89. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.js +18 -2
  90. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.d.ts +22 -0
  91. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.js +23 -2
  92. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.d.ts +69 -0
  93. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.js +134 -0
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.d.ts +154 -0
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.js +386 -0
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +6 -3
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +11 -5
  98. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.d.ts +1 -1
  99. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.js +1 -0
  100. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/profileStateDir.js +6 -35
  101. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +1 -1
  102. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +45 -16
  103. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.d.ts +31 -0
  104. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.js +73 -0
  105. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.d.ts +3 -0
  106. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.js +40 -3
  107. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/types.d.ts +7 -1
  108. package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
  109. package/dist/node_modules/@skrr-ai/data-provider/index.js +7349 -7325
  110. package/oclif.manifest.json +11 -2
  111. package/package.json +4 -1
@@ -45,6 +45,7 @@ var import_node_crypto = require("node:crypto");
45
45
  var import_node_fs = __toESM(require("node:fs"), 1);
46
46
  var import_node_path = __toESM(require("node:path"), 1);
47
47
  var import_runtime = require("./runtime.js");
48
+ var import_stateFiles = require("./stateFiles.js");
48
49
  const PROFILES_SEGMENT = "profiles";
49
50
  const SUPERSEDED_MARKER_SUFFIX = ".superseded.json";
50
51
  const SUPERSEDED_COPY_SUFFIX = ".superseded";
@@ -190,28 +191,6 @@ function sha256OfFile(target) {
190
191
  return null;
191
192
  }
192
193
  }
193
- function atomicWrite0600(target, value) {
194
- const dir = import_node_path.default.dirname(target);
195
- import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
196
- const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
197
- const fd = import_node_fs.default.openSync(tmp, "w", 384);
198
- try {
199
- import_node_fs.default.writeSync(fd, value);
200
- import_node_fs.default.fsyncSync(fd);
201
- } finally {
202
- import_node_fs.default.closeSync(fd);
203
- }
204
- import_node_fs.default.renameSync(tmp, target);
205
- try {
206
- const dirFd = import_node_fs.default.openSync(dir, "r");
207
- try {
208
- import_node_fs.default.fsyncSync(dirFd);
209
- } finally {
210
- import_node_fs.default.closeSync(dirFd);
211
- }
212
- } catch {
213
- }
214
- }
215
194
  function adopt(fromDir, toDir, files, dryRun) {
216
195
  const written = [];
217
196
  for (const name of files) {
@@ -219,8 +198,9 @@ function adopt(fromDir, toDir, files, dryRun) {
219
198
  if (!existsFile(source))
220
199
  continue;
221
200
  const target = import_node_path.default.join(toDir, name);
222
- if (!dryRun)
223
- atomicWrite0600(target, import_node_fs.default.readFileSync(source));
201
+ if (!dryRun) {
202
+ (0, import_stateFiles.atomicWrite0600)(target, import_node_fs.default.readFileSync(source), `adopt ${name} into ${toDir}`);
203
+ }
224
204
  written.push(target);
225
205
  }
226
206
  return { written, supersededAside: [] };
@@ -232,7 +212,7 @@ function moveAside(dir, files, suffix = SUPERSEDED_COPY_SUFFIX) {
232
212
  const target = import_node_path.default.join(dir, name);
233
213
  if (!existsFile(target))
234
214
  continue;
235
- import_node_fs.default.renameSync(target, `${target}${suffix}.${stamp}`);
215
+ (0, import_stateFiles.renameStateFile)(target, `${target}${suffix}.${stamp}`, `set aside ${target}`);
236
216
  aside.push(target);
237
217
  }
238
218
  return aside;
@@ -248,8 +228,8 @@ function writeMarker(markerPath, supersededBy, reason) {
248
228
  at: (/* @__PURE__ */ new Date()).toISOString(),
249
229
  note: "Kept deliberately. This file is the pre-consolidation copy (OSK-10314); older binaries read it and it is the only rollback that needs no operator step. Remove it only through logout."
250
230
  };
251
- atomicWrite0600(markerPath, Buffer.from(`${JSON.stringify(payload, null, 2)}
252
- `, "utf-8"));
231
+ (0, import_stateFiles.atomicWrite0600)(markerPath, Buffer.from(`${JSON.stringify(payload, null, 2)}
232
+ `, "utf-8"), `write the consolidation marker ${markerPath}`);
253
233
  }
254
234
  function emitOutcome(spec, status, meta) {
255
235
  spec.onEvent?.(`path_consolidation.${status}`, status !== "conflict", {
@@ -231,7 +231,7 @@ export type DeviceProofSigner = (params: {
231
231
  method: string;
232
232
  url: string;
233
233
  refreshToken: string;
234
- }) => string | null | undefined;
234
+ }) => string | null | undefined | Promise<string | null | undefined>;
235
235
  export declare function refreshDaemonToken(serverUrl: string, currentRefreshToken: string, daemonId: string, options?: {
236
236
  machineUuid?: string;
237
237
  origin?: string;
@@ -49,7 +49,9 @@ var import_node_fs = __toESM(require("node:fs"), 1);
49
49
  var import_node_path = __toESM(require("node:path"), 1);
50
50
  var import_proper_lockfile = __toESM(require("proper-lockfile"), 1);
51
51
  var import_runtime = require("./runtime.js");
52
+ var import_liveState = require("./liveState.js");
52
53
  var import_refreshClassification = require("./refreshClassification.js");
54
+ var import_stateFiles = require("./stateFiles.js");
53
55
  var import_types = require("./types.js");
54
56
  const LOCK_STALE_MS = 5e3;
55
57
  const LOCK_RETRY_MS = 100;
@@ -125,9 +127,9 @@ async function withAuthLock(body, daemonId) {
125
127
  const lockPath = authLockPath();
126
128
  const dir = import_node_path.default.dirname(lockPath);
127
129
  if (!import_node_fs.default.existsSync(dir))
128
- import_node_fs.default.mkdirSync(dir, { recursive: true });
130
+ (0, import_stateFiles.ensureStateDir)(dir, "create the auth lock directory", null);
129
131
  if (!import_node_fs.default.existsSync(lockPath)) {
130
- import_node_fs.default.writeFileSync(lockPath, "", { mode: 384 });
132
+ (0, import_stateFiles.writeStateFile)(lockPath, "", { mode: 384 }, "create the auth lock");
131
133
  }
132
134
  const acquireStart = Date.now();
133
135
  (0, import_runtime.emitAuthTelemetry)({
@@ -282,7 +284,9 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
282
284
  if (options.machineUuid) {
283
285
  body.machineUuid = options.machineUuid;
284
286
  }
285
- function buildRequestInit() {
287
+ let proofAttached = false;
288
+ async function buildRequestInit() {
289
+ proofAttached = false;
286
290
  const headers = { "Content-Type": "application/json" };
287
291
  if (options.origin) {
288
292
  headers["X-Oversky-Origin"] = options.origin;
@@ -290,7 +294,7 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
290
294
  __refreshDiagnostics.requestAttemptCount += 1;
291
295
  if (options.signRequestProof) {
292
296
  try {
293
- const proof = options.signRequestProof({
297
+ const proof = await options.signRequestProof({
294
298
  method: "POST",
295
299
  url,
296
300
  refreshToken: currentRefreshToken
@@ -298,6 +302,7 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
298
302
  __refreshDiagnostics.proofAttemptCount += 1;
299
303
  if (typeof proof === "string" && proof.length > 0) {
300
304
  headers["OverSky-DPoP"] = proof;
305
+ proofAttached = true;
301
306
  }
302
307
  } catch (err) {
303
308
  __refreshDiagnostics.proofAttemptCount += 1;
@@ -312,7 +317,7 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
312
317
  }
313
318
  let response;
314
319
  try {
315
- response = await fetch(url, buildRequestInit());
320
+ response = await fetch(url, await buildRequestInit());
316
321
  (0, import_runtime.notifyResponseObserver)(response);
317
322
  } catch (err) {
318
323
  log.warn(`[auth/refresh] network error: ${err?.message}`);
@@ -426,10 +431,13 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
426
431
  });
427
432
  throw new import_types.PermanentAuthFailure(code, parsedBody.message || code, revocationReason);
428
433
  }
434
+ const noLocalProof = parsedBody.code === "DEVICE_PROOF_MISSING" && !proofAttached;
435
+ const latchReason = noLocalProof ? "DEVICE_KEY_UNAVAILABLE" : "UNKNOWN_401";
429
436
  const message = parsedBody.message || "Unknown auth failure";
437
+ const latchMessage = noLocalProof ? `This computer has no usable device key, so the refresh carried no device proof (server: ${message})` : message;
430
438
  const { exhausted, count } = recordUnknown401(daemonId, Date.now());
431
439
  if (!exhausted) {
432
- log.warn(`[auth/refresh] unknown-shape 401 (${count}/${UNKNOWN_401_BUDGET}) \u2014 treating as transient, will retry: ${message}`);
440
+ log.warn(`[auth/refresh] unknown-shape 401 (${count}/${UNKNOWN_401_BUDGET}) \u2014 treating as transient, will retry: ${message}` + (noLocalProof ? " (no device proof was sent: this machine has no usable device key)" : ""));
433
441
  (0, import_runtime.emitAuthTelemetry)({
434
442
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
435
443
  eventType: "auth",
@@ -448,7 +456,7 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
448
456
  });
449
457
  throw new import_types.TransientAuthFailure(`Unknown auth failure (transient, retrying): ${message}`);
450
458
  }
451
- log.warn(`[auth/refresh] unknown-shape 401 budget exhausted (${count}/${UNKNOWN_401_BUDGET}) \u2014 latching permanent: ${message}`);
459
+ log.warn(`[auth/refresh] unknown-shape 401 budget exhausted (${count}/${UNKNOWN_401_BUDGET}) \u2014 latching permanent ${latchReason}: ${message}`);
452
460
  (0, import_runtime.emitAuthTelemetry)({
453
461
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
454
462
  eventType: "auth",
@@ -457,18 +465,18 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
457
465
  event: "refresh_failure",
458
466
  daemonId,
459
467
  durationMs: Date.now() - startMs,
460
- errorCode: "UNKNOWN_401",
468
+ errorCode: latchReason,
461
469
  statusCode: 401,
462
470
  transient: false,
463
471
  unknown401Streak: count
464
472
  }
465
473
  });
466
474
  persistReauthState({
467
- code: "UNKNOWN_401",
468
- message,
475
+ code: latchReason,
476
+ message: latchMessage,
469
477
  at: Date.now()
470
478
  });
471
- throw new import_types.PermanentAuthFailure("UNKNOWN_401", message);
479
+ throw new import_types.PermanentAuthFailure(latchReason, latchMessage);
472
480
  }
473
481
  let bodyText = "";
474
482
  try {
@@ -495,11 +503,10 @@ function persistReauthState(state) {
495
503
  try {
496
504
  const dir = import_node_path.default.dirname(flagPath);
497
505
  if (!import_node_fs.default.existsSync(dir))
498
- import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
499
- import_node_fs.default.writeFileSync(flagPath, JSON.stringify(state, null, 2) + "\n", {
500
- mode: 384
501
- });
506
+ (0, import_stateFiles.ensureStateDir)(dir, "create the re-auth latch directory");
507
+ (0, import_stateFiles.writeStateFile)(flagPath, JSON.stringify(state, null, 2) + "\n", { mode: 384 }, "write the re-auth latch");
502
508
  } catch (err) {
509
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
503
510
  log.debug(`[auth/refresh] failed to persist reauth flag: ${err?.message}`);
504
511
  }
505
512
  }
@@ -526,8 +533,9 @@ function clearReauthState() {
526
533
  try {
527
534
  const flagPath = reauthFlagPath();
528
535
  if (import_node_fs.default.existsSync(flagPath))
529
- import_node_fs.default.unlinkSync(flagPath);
536
+ (0, import_stateFiles.removeStateFile)(flagPath, "clear the re-auth latch");
530
537
  } catch (err) {
538
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
531
539
  log.debug(`[auth/refresh] clearReauthState: ${err?.message}`);
532
540
  }
533
541
  }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * stateFiles.ts — the ONE place auth-core mutates credential state on disk.
3
+ *
4
+ * Every write, delete, rename and mkdir of a wrapped DEK, a device key, an enrollment marker, a
5
+ * lock sentinel, a re-auth latch or a machine identity goes through a function here, and each
6
+ * asks `assertMayMutateLiveState` first: a test process aimed at the account's real home is
7
+ * refused with a named error before a byte changes (`liveState.ts` says why that cannot be left
8
+ * to each runner).
9
+ *
10
+ * This is the "own a control surface, or do not touch it" rule applied to the real home. The
11
+ * atomic writer used to exist three times (device identity, credential envelope, profile
12
+ * consolidation), so a guard added to one left two doors open. `stateFileBoundary.spec.ts`
13
+ * fails when a file other than this one starts calling a raw `fs` mutator, so a fourth copy
14
+ * cannot appear unguarded.
15
+ *
16
+ * Outside a test process every function here is exactly the `fs` call it wraps.
17
+ */
18
+ import fs from 'node:fs';
19
+ /** `mkdir -p` for a state directory, 0700 unless `mode` says otherwise. */
20
+ export declare function ensureStateDir(dir: string, what: string, mode?: number | null): void;
21
+ /**
22
+ * Atomic 0600 write — tmp+fsync+rename+dir-fsync. The same shape the daemon and CLI always used,
23
+ * so a partial-write race between `skrr` and `skrrd` produces the same on-disk transitions.
24
+ */
25
+ export declare function atomicWrite0600(target: string, value: Buffer | string, what: string): void;
26
+ /** `fs.writeFileSync` for a state file: lock sentinels, a re-auth latch, a machine identity. */
27
+ export declare function writeStateFile(target: string, data: string | NodeJS.ArrayBufferView, options: fs.WriteFileOptions, what: string): void;
28
+ /** `fs.unlinkSync` for a state file. Throws what `unlinkSync` throws, ENOENT included. */
29
+ export declare function removeStateFile(target: string, what: string): void;
30
+ /** `fs.renameSync` between state paths; both ends are checked. */
31
+ export declare function renameStateFile(from: string, to: string, what: string): void;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var stateFiles_exports = {};
30
+ __export(stateFiles_exports, {
31
+ atomicWrite0600: () => atomicWrite0600,
32
+ ensureStateDir: () => ensureStateDir,
33
+ removeStateFile: () => removeStateFile,
34
+ renameStateFile: () => renameStateFile,
35
+ writeStateFile: () => writeStateFile
36
+ });
37
+ module.exports = __toCommonJS(stateFiles_exports);
38
+ var import_node_fs = __toESM(require("node:fs"), 1);
39
+ var import_node_path = __toESM(require("node:path"), 1);
40
+ var import_liveState = require("./liveState.js");
41
+ function ensureStateDir(dir, what, mode = 448) {
42
+ (0, import_liveState.assertMayMutateLiveState)(dir, what);
43
+ import_node_fs.default.mkdirSync(dir, mode === null ? { recursive: true } : { recursive: true, mode });
44
+ }
45
+ function atomicWrite0600(target, value, what) {
46
+ (0, import_liveState.assertMayMutateLiveState)(target, what);
47
+ const dir = import_node_path.default.dirname(target);
48
+ import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
49
+ const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
50
+ const buf = typeof value === "string" ? Buffer.from(value, "utf-8") : value;
51
+ const fd = import_node_fs.default.openSync(tmp, "w", 384);
52
+ try {
53
+ import_node_fs.default.writeSync(fd, buf);
54
+ import_node_fs.default.fsyncSync(fd);
55
+ } finally {
56
+ import_node_fs.default.closeSync(fd);
57
+ }
58
+ import_node_fs.default.renameSync(tmp, target);
59
+ try {
60
+ const dirFd = import_node_fs.default.openSync(dir, "r");
61
+ try {
62
+ import_node_fs.default.fsyncSync(dirFd);
63
+ } finally {
64
+ import_node_fs.default.closeSync(dirFd);
65
+ }
66
+ } catch {
67
+ }
68
+ }
69
+ function writeStateFile(target, data, options, what) {
70
+ (0, import_liveState.assertMayMutateLiveState)(target, what);
71
+ import_node_fs.default.writeFileSync(target, data, options);
72
+ }
73
+ function removeStateFile(target, what) {
74
+ (0, import_liveState.assertMayMutateLiveState)(target, what);
75
+ import_node_fs.default.unlinkSync(target);
76
+ }
77
+ function renameStateFile(from, to, what) {
78
+ (0, import_liveState.assertMayMutateLiveState)(from, what);
79
+ (0, import_liveState.assertMayMutateLiveState)(to, what);
80
+ import_node_fs.default.renameSync(from, to);
81
+ }
82
+ // Annotate the CommonJS export names for ESM import in node:
83
+ 0 && (module.exports = {
84
+ atomicWrite0600,
85
+ ensureStateDir,
86
+ removeStateFile,
87
+ renameStateFile,
88
+ writeStateFile
89
+ });
@@ -61,6 +61,9 @@ export declare const PREVIEW_TOKEN_REDACTION: "pvw_[redacted]";
61
61
  export declare const QR_SIGN_IN_REDACTION: "[redacted]";
62
62
  /** Fields are protocol-specific; preserve ordinary request ids, error codes and metrics. */
63
63
  export declare function isQrSignInSecretField(key: string): boolean;
64
+ export declare function isOAuthQuerySecretField(key: string): boolean;
65
+ export declare function isOAuthQueryContainerField(key: string): boolean;
66
+ export declare function isGraphHandoffCookieField(key: string): boolean;
64
67
  /**
65
68
  * Replace every credential that travels in a URL with a marker: a Hosted Machine preview token with
66
69
  * {@link PREVIEW_TOKEN_REDACTION}, and a workspace invitation, platform invitation, member link or operator code with
@@ -20,6 +20,9 @@ var telemetryRedaction_exports = {};
20
20
  __export(telemetryRedaction_exports, {
21
21
  PREVIEW_TOKEN_REDACTION: () => PREVIEW_TOKEN_REDACTION,
22
22
  QR_SIGN_IN_REDACTION: () => QR_SIGN_IN_REDACTION,
23
+ isGraphHandoffCookieField: () => isGraphHandoffCookieField,
24
+ isOAuthQueryContainerField: () => isOAuthQueryContainerField,
25
+ isOAuthQuerySecretField: () => isOAuthQuerySecretField,
23
26
  isQrSignInSecretField: () => isQrSignInSecretField,
24
27
  redactPreviewTokens: () => redactPreviewTokens,
25
28
  redactPreviewTokensDeep: () => redactPreviewTokensDeep,
@@ -53,6 +56,18 @@ function isQrSignInSecretField(key) {
53
56
  return QR_SECRET_FIELDS.has(leaf.replace(/[_-]/g, "").toLowerCase());
54
57
  }
55
58
  const QR_SECRET_ASSIGNMENT = /((?:["']?)(?:statusToken|claimToken|codeVerifier|codeChallenge|comparisonCode|status_token|claim_token|code_verifier|code_challenge|comparison_code)(?:["']?)\s*[:=]\s*["']?)([A-Za-z0-9._~%+-]+)/gi;
59
+ const OAUTH_QUERY_SECRET = /(^|[?&]|%(?:25)*26)(code|state|error_description|handoff)(=|%(?:25)*3[dD])([^&#\s"'<>]*)/gi;
60
+ function isOAuthQuerySecretField(key) {
61
+ return ["code", "state", "error_description", "handoff"].includes(key.toLowerCase());
62
+ }
63
+ function isOAuthQueryContainerField(key) {
64
+ const leaf = (key.split(".").pop() || "").replace(/[_-]/g, "").toLowerCase();
65
+ return leaf === "query" || leaf === "querystring";
66
+ }
67
+ function isGraphHandoffCookieField(key) {
68
+ return key.toLowerCase() === "__secure-pc-graph-handoff";
69
+ }
70
+ const GRAPH_HANDOFF_COOKIE = /((?:_|%(?:25)*5[fF]){2}secure(?:-|%(?:25)*2[dD])pc(?:-|%(?:25)*2[dD])graph(?:-|%(?:25)*2[dD])handoff(?:=|%(?:25)*3[dD]))([^;,&\s"'<>]+)/gi;
56
71
  function redactQrSignInText(value) {
57
72
  const standaloneQuery = /(?:^|[?&])direction=(?:mobile_approves_web|web_approves_mobile)(?:&|$)/.test(value) ? value.replace(QR_REQUEST_QUERY, `$1$2${QR_SIGN_IN_REDACTION}`) : value;
58
73
  return standaloneQuery.replace(QR_REQUEST_PATH, `$1${QR_SIGN_IN_REDACTION}`).replace(QR_LINK_QUERY, (_match, prefix, query) => prefix + query.replace(QR_REQUEST_QUERY, `$1$2${QR_SIGN_IN_REDACTION}`)).replace(QR_SECRET_ASSIGNMENT, `$1${QR_SIGN_IN_REDACTION}`);
@@ -62,7 +77,7 @@ const credentialMarker = (_match, prefix) => `${prefix}_[redacted]`;
62
77
  function redactPreviewTokens(value) {
63
78
  if (typeof value !== "string")
64
79
  return value;
65
- const qrSafe = redactQrSignInText(value);
80
+ const qrSafe = redactQrSignInText(value).replace(OAUTH_QUERY_SECRET, "$1$2$3[redacted]").replace(GRAPH_HANDOFF_COOKIE, "$1[redacted]");
66
81
  if (!CREDENTIAL_HINTS.some((hint) => qrSafe.indexOf(hint) !== -1))
67
82
  return qrSafe;
68
83
  return qrSafe.replace(PREVIEW_TOKEN, PREVIEW_TOKEN_REDACTION).replace(SIGNED_CREDENTIAL, credentialMarker).replace(LEGACY_OPERATOR_CODE, credentialMarker);
@@ -81,7 +96,7 @@ function redactPreviewTokensDeep(root, limits = {}) {
81
96
  let visited = 0;
82
97
  let complete = true;
83
98
  const rewrite = (holder, key, current) => {
84
- const next = isQrSignInSecretField(key) ? QR_SIGN_IN_REDACTION : redactPreviewTokens(current);
99
+ const next = isQrSignInSecretField(key) || isGraphHandoffCookieField(key) ? QR_SIGN_IN_REDACTION : redactPreviewTokens(current);
85
100
  if (next === current) {
86
101
  return;
87
102
  }
@@ -119,7 +134,27 @@ function redactPreviewTokensDeep(root, limits = {}) {
119
134
  complete = false;
120
135
  continue;
121
136
  }
122
- if (isQrSignInSecretField(key) || typeof child === "string") {
137
+ if (isOAuthQueryContainerField(key) && Array.isArray(child)) {
138
+ try {
139
+ holder[key] = "[redacted]";
140
+ } catch {
141
+ complete = false;
142
+ }
143
+ if (holder[key] !== "[redacted]")
144
+ complete = false;
145
+ continue;
146
+ }
147
+ if (isOAuthQueryContainerField(key) && child && typeof child === "object" && !Array.isArray(child)) {
148
+ const query = child;
149
+ for (const queryKey of Object.keys(query)) {
150
+ if (isOAuthQuerySecretField(queryKey)) {
151
+ query[queryKey] = "[redacted]";
152
+ if (query[queryKey] !== "[redacted]")
153
+ complete = false;
154
+ }
155
+ }
156
+ }
157
+ if (isQrSignInSecretField(key) || isGraphHandoffCookieField(key) || typeof child === "string") {
123
158
  rewrite(holder, key, child);
124
159
  } else if (child !== null && typeof child === "object") {
125
160
  if (depth + 1 > maxDepth) {
@@ -166,6 +201,9 @@ function withPreviewTokenRedaction(options) {
166
201
  0 && (module.exports = {
167
202
  PREVIEW_TOKEN_REDACTION,
168
203
  QR_SIGN_IN_REDACTION,
204
+ isGraphHandoffCookieField,
205
+ isOAuthQueryContainerField,
206
+ isOAuthQuerySecretField,
169
207
  isQrSignInSecretField,
170
208
  redactPreviewTokens,
171
209
  redactPreviewTokensDeep,
@@ -67,6 +67,12 @@ export interface TokenPair {
67
67
  * (server code `DEVICE_PROOF_THUMBPRINT_MISMATCH`).
68
68
  * The daemon signs every retry with the same key, so
69
69
  * retrying can never succeed.
70
+ * - DEVICE_KEY_UNAVAILABLE — the server demanded a device proof
71
+ * (`DEVICE_PROOF_MISSING`) and this process had none
72
+ * to send: the key is unreadable, never created, or no
73
+ * signer is wired. Unlike `DEVICE_KEY_MISMATCH` there is
74
+ * no second key to reconcile; the fix is an interactive
75
+ * daemon sign-in, which mints and re-pins a new one.
70
76
  * - UNKNOWN_401 — server returned 401 with a code we don't
71
77
  * recognize. Treated as permanent (better to prompt
72
78
  * re-auth than to loop forever) but tagged
@@ -79,7 +85,7 @@ export interface TokenPair {
79
85
  * Without this, the client `describeAuthFailure` exhaustiveness check
80
86
  * was silently broken for the non-REFRESH branches.
81
87
  */
82
- export type PermanentAuthReason = 'REFRESH_INVALID' | 'REFRESH_REUSED' | 'REFRESH_EXPIRED' | 'SESSION_REVOKED' | 'DEVICE_KEY_MISMATCH' | 'TOKEN_INVALID' | 'TOKEN_EXPIRED' | 'UNKNOWN_401';
88
+ export type PermanentAuthReason = 'REFRESH_INVALID' | 'REFRESH_REUSED' | 'REFRESH_EXPIRED' | 'SESSION_REVOKED' | 'DEVICE_KEY_MISMATCH' | 'DEVICE_KEY_UNAVAILABLE' | 'TOKEN_INVALID' | 'TOKEN_EXPIRED' | 'UNKNOWN_401';
83
89
  export declare class PermanentAuthFailure extends Error {
84
90
  readonly code: PermanentAuthReason;
85
91
  /**
@@ -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;
@@ -167,7 +167,10 @@ export function daemonSignInRemedy(opts = {}) {
167
167
  const login = opts.loginCommand ? `\`${opts.loginCommand}\`` : '`skrr daemon login`';
168
168
  return `run ${login}${repin ? ' — an interactive login re-pins the device key' : ''}`;
169
169
  }
170
+ // A caller that cannot tell who runs the daemon may still know which profile it
171
+ // is on: `skrr daemon login` with no `--profile` signs the DEFAULT daemon in
172
+ // (OSK-16435), so the command it names is the one it was given.
170
173
  return ('sign the daemon in again on the machine running it: if the Skrr desktop app runs it, quit ' +
171
- 'and reopen the app; otherwise run `skrr daemon login`' +
174
+ `and reopen the app; otherwise run \`${opts.loginCommand ?? 'skrr daemon login'}\`` +
172
175
  (repin ? ' (either one re-pins the device key)' : ''));
173
176
  }