@skrr-ai/cli 0.1.123 → 0.1.125

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 (89) hide show
  1. package/dist/commands/agents/chat.d.ts +26 -0
  2. package/dist/commands/agents/chat.js +48 -10
  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/search/workspace.js +1 -1
  7. package/dist/commands/store/gallery-admissions/approve.js +1 -1
  8. package/dist/commands/store/gallery-admissions/preview.js +1 -1
  9. package/dist/commands/store/gallery-admissions/refuse.js +1 -1
  10. package/dist/commands/store/gallery-admissions/show.js +1 -1
  11. package/dist/generated/agent-guides.json +19 -19
  12. package/dist/lib/agentic-stream.js +15 -1
  13. package/dist/lib/auth-core-init.d.ts +15 -2
  14. package/dist/lib/auth-core-init.js +47 -6
  15. package/dist/lib/command-hygiene.js +30 -1
  16. package/dist/lib/daemonBrokerRefusal.js +5 -1
  17. package/dist/lib/keychain.js +41 -4
  18. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +5 -1
  19. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +1 -1
  20. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +13 -28
  21. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +133 -4
  22. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +387 -90
  23. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.d.ts +106 -7
  24. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.js +77 -19
  25. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.d.ts +33 -0
  26. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.js +77 -0
  27. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.d.ts +68 -0
  28. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.js +264 -0
  29. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.d.ts +31 -0
  30. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.js +90 -0
  31. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.js +2 -0
  32. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.js +2 -0
  33. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +7 -3
  34. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +102 -0
  35. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.d.ts +1 -1
  36. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.js +6 -0
  37. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.d.ts +18 -0
  38. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.js +8 -0
  39. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.d.ts +22 -0
  40. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.js +11 -0
  41. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.d.ts +43 -3
  42. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.js +33 -3
  43. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.d.ts +154 -0
  44. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.js +281 -0
  45. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +6 -3
  46. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +8 -5
  47. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/profileStateDir.js +7 -27
  48. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +1 -1
  49. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +12 -10
  50. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.d.ts +31 -0
  51. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.js +89 -0
  52. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +5 -1
  53. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +4 -1
  54. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +15 -37
  55. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +133 -4
  56. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +536 -101
  57. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.d.ts +106 -7
  58. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.js +131 -27
  59. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.d.ts +33 -0
  60. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.js +81 -0
  61. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.d.ts +68 -0
  62. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.js +317 -0
  63. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.d.ts +31 -0
  64. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.js +86 -0
  65. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.js +4 -0
  66. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.js +4 -0
  67. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +7 -3
  68. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +13 -3
  69. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.d.ts +1 -1
  70. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.js +1 -1
  71. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.d.ts +18 -0
  72. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.js +15 -0
  73. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.d.ts +22 -0
  74. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.js +20 -0
  75. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.d.ts +43 -3
  76. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.js +104 -8
  77. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.d.ts +154 -0
  78. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.js +386 -0
  79. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +6 -3
  80. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +11 -5
  81. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/profileStateDir.js +6 -35
  82. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +1 -1
  83. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +14 -10
  84. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.d.ts +31 -0
  85. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.js +73 -0
  86. package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
  87. package/dist/node_modules/@skrr-ai/data-provider/index.js +1094 -1067
  88. package/oclif.manifest.json +16 -3
  89. package/package.json +4 -1
@@ -20,18 +20,48 @@ var masterKekService_exports = {};
20
20
  __export(masterKekService_exports, {
21
21
  MASTER_KEK_KEYCHAIN_ACCOUNT: () => MASTER_KEK_KEYCHAIN_ACCOUNT,
22
22
  MASTER_KEK_KEYCHAIN_SERVICE: () => MASTER_KEK_KEYCHAIN_SERVICE,
23
- isMasterKekKeychainService: () => isMasterKekKeychainService
23
+ classifyDaemonKeychainService: () => classifyDaemonKeychainService,
24
+ isMasterKekKeychainService: () => isMasterKekKeychainService,
25
+ isSessionKeychainService: () => isSessionKeychainService,
26
+ isUserOwnedSecretKeychainService: () => isUserOwnedSecretKeychainService
24
27
  });
25
28
  module.exports = __toCommonJS(masterKekService_exports);
26
29
  const MASTER_KEK_KEYCHAIN_SERVICE = "ai.skrr.daemon.kek";
27
30
  const MASTER_KEK_KEYCHAIN_ACCOUNT = "master-kek-v1";
28
- const MASTER_KEK_SERVICE_PATTERN = /^ai\.(?:skrr|oversky)\.daemon\.kek(?:\..*)?$/;
31
+ const MASTER_KEK_SERVICE_PATTERN = /^(?:ai\.(?:skrr|oversky)|com\.oversky)\.daemon\.kek(?:\..*)?$/;
29
32
  function isMasterKekKeychainService(service) {
30
33
  return typeof service === "string" && MASTER_KEK_SERVICE_PATTERN.test(service);
31
34
  }
35
+ const USER_OWNED_SECRET_SERVICE_PATTERN = /^ai\.(?:skrr|oversky)\.daemon\.byok(?:\..*)?$/;
36
+ function isUserOwnedSecretKeychainService(service) {
37
+ return typeof service === "string" && USER_OWNED_SECRET_SERVICE_PATTERN.test(service);
38
+ }
39
+ const PROFILE_NAME = "[A-Za-z0-9][A-Za-z0-9._-]{0,63}";
40
+ const CONFIG_DIR_HASH = "-[a-f0-9]{8}";
41
+ const SESSION_SERVICE_PATTERNS = [
42
+ new RegExp(`^ai\\.skrr\\.daemon\\.${PROFILE_NAME}${CONFIG_DIR_HASH}$`),
43
+ new RegExp(`^ai\\.oversky\\.daemon(?:\\.${PROFILE_NAME}(?:${CONFIG_DIR_HASH})?)?$`)
44
+ ];
45
+ function classifyDaemonKeychainService(service) {
46
+ if (typeof service !== "string")
47
+ return "unclassified";
48
+ if (isMasterKekKeychainService(service))
49
+ return "master_kek";
50
+ if (isUserOwnedSecretKeychainService(service))
51
+ return "user_secret";
52
+ if (SESSION_SERVICE_PATTERNS.some((pattern) => pattern.test(service)))
53
+ return "session";
54
+ return "unclassified";
55
+ }
56
+ function isSessionKeychainService(service) {
57
+ return classifyDaemonKeychainService(service) === "session";
58
+ }
32
59
  // Annotate the CommonJS export names for ESM import in node:
33
60
  0 && (module.exports = {
34
61
  MASTER_KEK_KEYCHAIN_ACCOUNT,
35
62
  MASTER_KEK_KEYCHAIN_SERVICE,
36
- isMasterKekKeychainService
63
+ classifyDaemonKeychainService,
64
+ isMasterKekKeychainService,
65
+ isSessionKeychainService,
66
+ isUserOwnedSecretKeychainService
37
67
  });
@@ -0,0 +1,154 @@
1
+ import { CONFIG_DIR_NAME } from './localIdentity.js';
2
+ /** Directory name of the state root inside a home directory. */
3
+ export { CONFIG_DIR_NAME };
4
+ /** Opt-in for a deliberate check against the machine's real state. */
5
+ export declare const ALLOW_LIVE_STATE_ENV = "SKRR_TEST_ALLOW_LIVE_STATE";
6
+ /** Set by every hermetic runner to the throwaway home it gave the process. */
7
+ export declare const HERMETIC_HOME_ENV = "SKRR_TEST_HERMETIC_HOME";
8
+ /** `LiveStateRefusedError.code`. Kept at its pre-consolidation value: scripts match on it. */
9
+ export declare const LIVE_STATE_REFUSED_CODE = "SKRR_TEST_LIVE_STATE";
10
+ /**
11
+ * Which signal says this is a test process, or `null`. The text names the signal so a false
12
+ * positive (a shell that exports `NODE_ENV=test`) diagnoses itself.
13
+ *
14
+ * `NODE_TEST_CONTEXT` is set by `node --test` in every file's child process and inherited by
15
+ * anything a test spawns. `--test` in `execArgv` covers `--test-isolation=none`, where tests run
16
+ * in the runner itself. A test file run DIRECTLY (`node file.test.ts`) has neither: node:test
17
+ * sets no marker for it, so the entry script's name is the only evidence. `JEST_WORKER_ID` is
18
+ * set in the real environment of every jest worker (and by `--runInBand`), so a child a spec
19
+ * spawns without `env` still carries it. `NODE_ENV=test` is what jest, vitest and `bun test`
20
+ * default to, and what custom scripts set by hand.
21
+ *
22
+ * `argv` is the process's own only when `env` is: a caller describing a hypothetical process
23
+ * (`{}`) describes all of it.
24
+ */
25
+ export declare function testProcessSignal(env?: NodeJS.ProcessEnv, execArgv?: readonly string[], argv?: readonly string[]): string | null;
26
+ /** Is this a test process? See `testProcessSignal` for what counts. */
27
+ export declare function isTestProcess(env?: NodeJS.ProcessEnv, execArgv?: readonly string[], argv?: readonly string[]): boolean;
28
+ /** Should a test process be refused access to the machine's real state? */
29
+ export declare function refusesLiveStateInTest(env?: NodeJS.ProcessEnv, execArgv?: readonly string[], argv?: readonly string[]): boolean;
30
+ /**
31
+ * The account's home directory from the user database. A test that points `HOME` somewhere else
32
+ * cannot move it, which is the point: it names where the INSTALLED product keeps its state,
33
+ * whatever this process believes.
34
+ *
35
+ * Node's `os.userInfo().homedir` reads the user database. Bun's returns `$HOME`, which is
36
+ * exactly what a test moves, so the compiled daemon asks the database itself — without that, a
37
+ * compiled binary spawned by a test with its own `HOME` saw that home as the live one and
38
+ * refused to start. `null` when it cannot be determined.
39
+ */
40
+ export declare function accountHomeDir(): string | null;
41
+ /**
42
+ * @internal Test seam — make `accountHomeDir()` answer `home` (`null`: "cannot be determined"),
43
+ * or restore the user-database lookup with `undefined`. It lets a spec stand a THROWAWAY
44
+ * directory in for the account's real home, so the guard can be exercised end to end — and its
45
+ * refusal removed, to prove the spec notices — without any path ever touching the real one.
46
+ * Never called from production code; `stateFileBoundary.spec.ts` fails if it is.
47
+ */
48
+ export declare function __setAccountHomeForTest(home: string | null | undefined): void;
49
+ /** The state root of the product installed for this account, when it can be known. */
50
+ export declare function liveStateRoot(home?: string | null): string | null;
51
+ /**
52
+ * What a guard decision is made against. Everything defaults to the running process; a spec
53
+ * injects a throwaway directory as `realHome` to prove the guard fires WITHOUT going near the
54
+ * machine's actual home.
55
+ */
56
+ export interface LiveStateProbe {
57
+ env?: NodeJS.ProcessEnv;
58
+ execArgv?: readonly string[];
59
+ argv?: readonly string[];
60
+ /** The account's real home. `null` means "cannot be determined". Default: the user database. */
61
+ realHome?: string | null;
62
+ }
63
+ export declare class LiveStateRefusedError extends Error {
64
+ readonly code = "SKRR_TEST_LIVE_STATE";
65
+ /** What the mutation was aimed at: a path, or a Keychain service. */
66
+ readonly target: string;
67
+ /** Why this process was taken for a test. */
68
+ readonly signal: string;
69
+ constructor(what: string, target: string, signal: string, why: string, hint?: string);
70
+ }
71
+ /**
72
+ * Is `err` a live-state refusal? By name and code as well as by class: auth-core ships as both
73
+ * ESM and CJS, and two copies of it in one process (a farmed worktree) have two classes.
74
+ */
75
+ export declare function isLiveStateRefusal(err: unknown): err is LiveStateRefusedError;
76
+ /**
77
+ * First line of any `catch` that wraps a guarded mutation. A refusal swallowed into a telemetry
78
+ * event is a write that silently did not happen under a test that reports green, which is the
79
+ * "everything reads healthy" shape this repo has paid for before. A no-op outside a test
80
+ * process, where no refusal is ever constructed.
81
+ */
82
+ export declare function rethrowIfLiveStateRefusal(err: unknown): void;
83
+ /**
84
+ * Throw `LiveStateRefusedError` when a test process is about to mutate `target`, a path under
85
+ * the account's real home. Returns without reading anything else outside a test process.
86
+ *
87
+ * `what` completes "Refusing to …" (`replace the wrapped DEK`). `hint` replaces the default
88
+ * advice about how to run the test safely.
89
+ */
90
+ export declare function assertMayMutateLiveState(target: string, what: string, probe?: LiveStateProbe, hint?: string): void;
91
+ /** Service name prefix of the daemon's credential slots; always followed by `.<profile>`. */
92
+ export declare const DAEMON_KEYCHAIN_SERVICE_BASE = "ai.skrr.daemon";
93
+ /**
94
+ * Separate Keychain service for local BYOK secrets.
95
+ *
96
+ * Keeping provider keys out of the daemon-auth service prevents a broad "export daemon
97
+ * credentials" operation from accidentally including them and lets the user remove a provider
98
+ * key without disturbing their login.
99
+ */
100
+ export declare const DAEMON_BYOK_KEYCHAIN_SERVICE_BASE = "ai.skrr.daemon.byok";
101
+ /**
102
+ * The PRE-RENAME, pre-Phase-K service name. Read ONLY by the daemon's migration helper.
103
+ *
104
+ * It must NOT equal `DAEMON_KEYCHAIN_SERVICE_BASE`, and it held that value until a fix: the
105
+ * skrr rename swept the string like any other, and the two became identical. Nothing failed,
106
+ * because the failure is an absence — `migrateLegacyServiceToProfile` read a service nothing had
107
+ * ever written, found no slots, and reported success. Pre-rename credentials stayed where they
108
+ * were and the user silently re-authenticated. Written as a literal on purpose.
109
+ */
110
+ export declare const LEGACY_DAEMON_KEYCHAIN_SERVICE = "ai.oversky.daemon";
111
+ /** The profile the installed daemon runs when nothing selects another. */
112
+ export declare const LIVE_DEFAULT_PROFILE = "default";
113
+ /**
114
+ * `-<sha256(dir)[0:8]>`, the suffix of every per-profile Keychain service name. The input is the
115
+ * absolute, trailing-slash-stripped config directory, so two OS users running the daemon under
116
+ * their own `~/.skrr/` get distinct names and cannot read each other's tokens.
117
+ */
118
+ export declare function keychainSuffixForDir(dir: string): string;
119
+ /**
120
+ * Keychain services that belong to the product installed on this machine: the master KEK
121
+ * (current brand and the ones before it), the default profile's credential and BYOK slots under
122
+ * the account's real home, and the legacy names the daemon migrates from on start — a write to
123
+ * one of those is adopted by the next restart instead of the next read.
124
+ *
125
+ * The per-profile names are derived from the ACCOUNT's home, which no `HOME` override can reach.
126
+ * When the home is unknown only the names that need no home are returned; see
127
+ * `isLiveKeychainService` for what is done about the rest.
128
+ */
129
+ export declare function liveKeychainServices(probe?: Pick<LiveStateProbe, 'realHome'>): ReadonlySet<string>;
130
+ /**
131
+ * Does `service` name an item of the installed product? When the real home is unknown, a
132
+ * default-profile slot with a hash suffix is assumed live: its hash cannot be checked, and the
133
+ * guard fails closed.
134
+ */
135
+ export declare function isLiveKeychainService(service: string, probe?: Pick<LiveStateProbe, 'realHome'>): boolean;
136
+ /** Would a mutation of Keychain `service` be refused in a process with this shape? */
137
+ export declare function refusesLiveKeychainMutation(service: string, probe?: LiveStateProbe): boolean;
138
+ /** What a Keychain guard decision is made against. */
139
+ export interface LiveKeychainProbe extends LiveStateProbe {
140
+ /**
141
+ * The keychain FILE the write is aimed at, when the caller knows it (macOS passes the login
142
+ * keychain it resolved). A keychain outside the real home is not the account's, so nothing
143
+ * the installed product reads can be in it, whatever the service is called: a throwaway
144
+ * keychain file, or a fake `security` that answers with a path of its own. Without it, the
145
+ * service name alone decides.
146
+ */
147
+ keychainFile?: string;
148
+ }
149
+ /**
150
+ * Throw `LiveStateRefusedError` when a test process is about to write or delete an item in a
151
+ * Keychain service the installed product reads. Callers that can be handed a fake `security`
152
+ * binary call this only when the real one would run: a mocked write touches nothing.
153
+ */
154
+ export declare function assertMayMutateLiveKeychain(service: string, what: string, probe?: LiveKeychainProbe, hint?: string): void;
@@ -0,0 +1,281 @@
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 liveState_exports = {};
30
+ __export(liveState_exports, {
31
+ ALLOW_LIVE_STATE_ENV: () => ALLOW_LIVE_STATE_ENV,
32
+ CONFIG_DIR_NAME: () => import_localIdentity.CONFIG_DIR_NAME,
33
+ DAEMON_BYOK_KEYCHAIN_SERVICE_BASE: () => DAEMON_BYOK_KEYCHAIN_SERVICE_BASE,
34
+ DAEMON_KEYCHAIN_SERVICE_BASE: () => DAEMON_KEYCHAIN_SERVICE_BASE,
35
+ HERMETIC_HOME_ENV: () => HERMETIC_HOME_ENV,
36
+ LEGACY_DAEMON_KEYCHAIN_SERVICE: () => LEGACY_DAEMON_KEYCHAIN_SERVICE,
37
+ LIVE_DEFAULT_PROFILE: () => LIVE_DEFAULT_PROFILE,
38
+ LIVE_STATE_REFUSED_CODE: () => LIVE_STATE_REFUSED_CODE,
39
+ LiveStateRefusedError: () => LiveStateRefusedError,
40
+ __setAccountHomeForTest: () => __setAccountHomeForTest,
41
+ accountHomeDir: () => accountHomeDir,
42
+ assertMayMutateLiveKeychain: () => assertMayMutateLiveKeychain,
43
+ assertMayMutateLiveState: () => assertMayMutateLiveState,
44
+ isLiveKeychainService: () => isLiveKeychainService,
45
+ isLiveStateRefusal: () => isLiveStateRefusal,
46
+ isTestProcess: () => isTestProcess,
47
+ keychainSuffixForDir: () => keychainSuffixForDir,
48
+ liveKeychainServices: () => liveKeychainServices,
49
+ liveStateRoot: () => liveStateRoot,
50
+ refusesLiveKeychainMutation: () => refusesLiveKeychainMutation,
51
+ refusesLiveStateInTest: () => refusesLiveStateInTest,
52
+ rethrowIfLiveStateRefusal: () => rethrowIfLiveStateRefusal,
53
+ testProcessSignal: () => testProcessSignal
54
+ });
55
+ module.exports = __toCommonJS(liveState_exports);
56
+ var import_node_child_process = require("node:child_process");
57
+ var import_node_crypto = require("node:crypto");
58
+ var import_node_fs = __toESM(require("node:fs"), 1);
59
+ var import_node_os = __toESM(require("node:os"), 1);
60
+ var import_node_path = __toESM(require("node:path"), 1);
61
+ var import_masterKekService = require("./kek/masterKekService.js");
62
+ var import_localIdentity = require("./localIdentity.js");
63
+ const ALLOW_LIVE_STATE_ENV = "SKRR_TEST_ALLOW_LIVE_STATE";
64
+ const HERMETIC_HOME_ENV = "SKRR_TEST_HERMETIC_HOME";
65
+ const LIVE_STATE_REFUSED_CODE = "SKRR_TEST_LIVE_STATE";
66
+ const TEST_FILE_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
67
+ function testProcessSignal(env = process.env, execArgv = process.execArgv, argv = env === process.env ? process.argv : []) {
68
+ if (env.NODE_TEST_CONTEXT)
69
+ return "NODE_TEST_CONTEXT is set (node --test)";
70
+ if (env.JEST_WORKER_ID)
71
+ return "JEST_WORKER_ID is set (jest)";
72
+ if (env.VITEST || env.VITEST_WORKER_ID)
73
+ return "VITEST is set (vitest)";
74
+ if (env[HERMETIC_HOME_ENV])
75
+ return `${HERMETIC_HOME_ENV} is set (a hermetic test runner)`;
76
+ if (env.NODE_ENV === "test")
77
+ return "NODE_ENV=test";
78
+ if (execArgv.includes("--test"))
79
+ return "node was started with --test";
80
+ const entry = argv[1];
81
+ if (typeof entry === "string" && TEST_FILE_RE.test(entry)) {
82
+ return `the entry script ${import_node_path.default.basename(entry)} is a test file`;
83
+ }
84
+ return null;
85
+ }
86
+ function isTestProcess(env = process.env, execArgv = process.execArgv, argv) {
87
+ return testProcessSignal(env, execArgv, argv) !== null;
88
+ }
89
+ function refusesLiveStateInTest(env = process.env, execArgv = process.execArgv, argv) {
90
+ return isTestProcess(env, execArgv, argv) && env[ALLOW_LIVE_STATE_ENV] !== "1";
91
+ }
92
+ let cachedAccountHome;
93
+ function userDatabaseHome() {
94
+ const probe = (command, args) => (0, import_node_child_process.execFileSync)(command, args, {
95
+ encoding: "utf-8",
96
+ stdio: ["ignore", "pipe", "ignore"],
97
+ timeout: 5e3
98
+ });
99
+ try {
100
+ if (process.platform === "darwin") {
101
+ const out = probe("dscl", [
102
+ ".",
103
+ "-read",
104
+ `/Users/${import_node_os.default.userInfo().username}`,
105
+ "NFSHomeDirectory"
106
+ ]);
107
+ const home = out.replace(/^NFSHomeDirectory:\s*/, "").trim();
108
+ return home || null;
109
+ }
110
+ if (process.platform === "linux" && typeof process.getuid === "function") {
111
+ const home = probe("getent", ["passwd", String(process.getuid())]).trim().split(":")[5];
112
+ return home || null;
113
+ }
114
+ } catch {
115
+ return null;
116
+ }
117
+ return null;
118
+ }
119
+ function accountHomeDir() {
120
+ if (cachedAccountHome !== void 0)
121
+ return cachedAccountHome;
122
+ if (typeof process.versions.bun === "string") {
123
+ cachedAccountHome = userDatabaseHome();
124
+ } else {
125
+ try {
126
+ cachedAccountHome = import_node_os.default.userInfo().homedir || null;
127
+ } catch {
128
+ cachedAccountHome = null;
129
+ }
130
+ }
131
+ return cachedAccountHome;
132
+ }
133
+ function __setAccountHomeForTest(home) {
134
+ cachedAccountHome = home;
135
+ }
136
+ function liveStateRoot(home = accountHomeDir()) {
137
+ return home ? import_node_path.default.resolve(home, import_localIdentity.CONFIG_DIR_NAME) : null;
138
+ }
139
+ function probeHome(probe) {
140
+ const home = probe.realHome === void 0 ? accountHomeDir() : probe.realHome;
141
+ if (!home || import_node_path.default.parse(import_node_path.default.resolve(home)).root === import_node_path.default.resolve(home))
142
+ return null;
143
+ return home;
144
+ }
145
+ function canonicalise(target) {
146
+ const absolute = import_node_path.default.resolve(target);
147
+ let head = absolute;
148
+ const tail = [];
149
+ for (; ; ) {
150
+ try {
151
+ const real = import_node_fs.default.realpathSync.native(head);
152
+ return tail.length === 0 ? real : import_node_path.default.join(real, ...tail.reverse());
153
+ } catch {
154
+ const parent = import_node_path.default.dirname(head);
155
+ if (parent === head)
156
+ return absolute;
157
+ tail.push(import_node_path.default.basename(head));
158
+ head = parent;
159
+ }
160
+ }
161
+ }
162
+ function isWithin(child, parent) {
163
+ const rel = import_node_path.default.relative(parent, child);
164
+ return rel !== ".." && !rel.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(rel);
165
+ }
166
+ class LiveStateRefusedError extends Error {
167
+ code = LIVE_STATE_REFUSED_CODE;
168
+ /** What the mutation was aimed at: a path, or a Keychain service. */
169
+ target;
170
+ /** Why this process was taken for a test. */
171
+ signal;
172
+ constructor(what, target, signal, why, hint) {
173
+ super(`Refusing to ${what}: ${why}. This process looks like a test (${signal}), and the target belongs to the Skrr daemon, CLI and desktop app installed on this machine. ` + (hint ?? "Run the suite through its own test script (`npm test`, or `npm run test:file -- <path>` in daemon/), which gives every test process a home of its own, or point HOME at a throwaway directory for the run (HOME=$(mktemp -d) \u2026). ") + `Set ${ALLOW_LIVE_STATE_ENV}=1 only for a deliberate check against the real state.`);
174
+ this.name = "LiveStateRefusedError";
175
+ this.target = target;
176
+ this.signal = signal;
177
+ }
178
+ }
179
+ function isLiveStateRefusal(err) {
180
+ return err instanceof LiveStateRefusedError || err?.name === "LiveStateRefusedError" && err.code === LIVE_STATE_REFUSED_CODE;
181
+ }
182
+ function rethrowIfLiveStateRefusal(err) {
183
+ if (isLiveStateRefusal(err))
184
+ throw err;
185
+ }
186
+ function pathRefusal(target, probe, env) {
187
+ const resolved = canonicalise(target);
188
+ const home = probeHome(probe);
189
+ if (home === null) {
190
+ const sandbox = env[HERMETIC_HOME_ENV];
191
+ if (sandbox && isWithin(resolved, canonicalise(sandbox)))
192
+ return null;
193
+ return `${target} cannot be shown to be outside the real home, because the account's home directory could not be determined`;
194
+ }
195
+ return isWithin(resolved, canonicalise(home)) ? `${target} is inside the real home ${home}` : null;
196
+ }
197
+ function assertMayMutateLiveState(target, what, probe = {}, hint) {
198
+ const env = probe.env ?? process.env;
199
+ const signal = testProcessSignal(env, probe.execArgv ?? process.execArgv, probe.argv);
200
+ if (signal === null || env[ALLOW_LIVE_STATE_ENV] === "1")
201
+ return;
202
+ const why = pathRefusal(target, probe, env);
203
+ if (why !== null)
204
+ throw new LiveStateRefusedError(what, target, signal, why, hint);
205
+ }
206
+ const DAEMON_KEYCHAIN_SERVICE_BASE = `${import_localIdentity.NATIVE_ID_PREFIX}.daemon`;
207
+ const DAEMON_BYOK_KEYCHAIN_SERVICE_BASE = `${import_localIdentity.NATIVE_ID_PREFIX}.daemon.byok`;
208
+ const LEGACY_DAEMON_KEYCHAIN_SERVICE = "ai.oversky.daemon";
209
+ const LIVE_DEFAULT_PROFILE = "default";
210
+ function keychainSuffixForDir(dir) {
211
+ const resolved = import_node_path.default.resolve(dir.replace(/\/+$/, ""));
212
+ return `-${(0, import_node_crypto.createHash)("sha256").update(resolved).digest("hex").toLowerCase().slice(0, 8)}`;
213
+ }
214
+ const UNVERIFIABLE_DEFAULT_SLOT_RE = new RegExp(`^(?:${DAEMON_KEYCHAIN_SERVICE_BASE.replace(/\./g, "\\.")}|${DAEMON_BYOK_KEYCHAIN_SERVICE_BASE.replace(/\./g, "\\.")})\\.${LIVE_DEFAULT_PROFILE}-[0-9a-f]{8}$`);
215
+ function liveKeychainServices(probe = {}) {
216
+ const services = /* @__PURE__ */ new Set([
217
+ import_masterKekService.MASTER_KEK_KEYCHAIN_SERVICE,
218
+ ...import_localIdentity.LEGACY_NATIVE_ID_PREFIXES.map((prefix) => `${prefix}.daemon.kek`),
219
+ `${DAEMON_KEYCHAIN_SERVICE_BASE}.${LIVE_DEFAULT_PROFILE}`,
220
+ DAEMON_KEYCHAIN_SERVICE_BASE,
221
+ LEGACY_DAEMON_KEYCHAIN_SERVICE
222
+ ]);
223
+ const root = liveStateRoot(probeHome(probe));
224
+ if (root) {
225
+ const suffix = keychainSuffixForDir(import_node_path.default.resolve(root, "profiles", LIVE_DEFAULT_PROFILE));
226
+ services.add(`${DAEMON_KEYCHAIN_SERVICE_BASE}.${LIVE_DEFAULT_PROFILE}${suffix}`);
227
+ services.add(`${DAEMON_BYOK_KEYCHAIN_SERVICE_BASE}.${LIVE_DEFAULT_PROFILE}${suffix}`);
228
+ }
229
+ return services;
230
+ }
231
+ function isLiveKeychainService(service, probe = {}) {
232
+ if (liveKeychainServices(probe).has(service))
233
+ return true;
234
+ return probeHome(probe) === null && UNVERIFIABLE_DEFAULT_SLOT_RE.test(service);
235
+ }
236
+ function refusesLiveKeychainMutation(service, probe = {}) {
237
+ const env = probe.env ?? process.env;
238
+ return refusesLiveStateInTest(env, probe.execArgv ?? process.execArgv, probe.argv) && isLiveKeychainService(service, probe);
239
+ }
240
+ function assertMayMutateLiveKeychain(service, what, probe = {}, hint) {
241
+ const env = probe.env ?? process.env;
242
+ const signal = testProcessSignal(env, probe.execArgv ?? process.execArgv, probe.argv);
243
+ if (signal === null || env[ALLOW_LIVE_STATE_ENV] === "1")
244
+ return;
245
+ if (!isLiveKeychainService(service, probe))
246
+ return;
247
+ let where = "";
248
+ if (probe.keychainFile !== void 0) {
249
+ const why = pathRefusal(probe.keychainFile, probe, env);
250
+ if (why === null)
251
+ return;
252
+ where = `, in a keychain that is not shown to be anyone else's (${why})`;
253
+ }
254
+ throw new LiveStateRefusedError(what, service, signal, `the Keychain service ${service} is one the installed product reads${where}`, hint);
255
+ }
256
+ // Annotate the CommonJS export names for ESM import in node:
257
+ 0 && (module.exports = {
258
+ ALLOW_LIVE_STATE_ENV,
259
+ CONFIG_DIR_NAME,
260
+ DAEMON_BYOK_KEYCHAIN_SERVICE_BASE,
261
+ DAEMON_KEYCHAIN_SERVICE_BASE,
262
+ HERMETIC_HOME_ENV,
263
+ LEGACY_DAEMON_KEYCHAIN_SERVICE,
264
+ LIVE_DEFAULT_PROFILE,
265
+ LIVE_STATE_REFUSED_CODE,
266
+ LiveStateRefusedError,
267
+ __setAccountHomeForTest,
268
+ accountHomeDir,
269
+ assertMayMutateLiveKeychain,
270
+ assertMayMutateLiveState,
271
+ isLiveKeychainService,
272
+ isLiveStateRefusal,
273
+ isTestProcess,
274
+ keychainSuffixForDir,
275
+ liveKeychainServices,
276
+ liveStateRoot,
277
+ refusesLiveKeychainMutation,
278
+ refusesLiveStateInTest,
279
+ rethrowIfLiveStateRefusal,
280
+ testProcessSignal
281
+ });
@@ -37,7 +37,9 @@ var import_node_crypto = __toESM(require("node:crypto"), 1);
37
37
  var import_node_fs = __toESM(require("node:fs"), 1);
38
38
  var import_node_os = __toESM(require("node:os"), 1);
39
39
  var import_node_path = __toESM(require("node:path"), 1);
40
+ var import_liveState = require("./liveState.js");
40
41
  var import_runtime = require("./runtime.js");
42
+ var import_stateFiles = require("./stateFiles.js");
41
43
  const DAEMON_ID_FILENAME = "daemon.id";
42
44
  const PROFILES_DIRNAME = "profiles";
43
45
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -67,14 +69,14 @@ function isUuidLike(raw) {
67
69
  }
68
70
  function writeDaemonIdFile(target, id) {
69
71
  const dir = import_node_path.default.dirname(target);
70
- import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
72
+ (0, import_stateFiles.ensureStateDir)(dir, "create the machine id directory");
71
73
  const tmp = `${target}.tmp.${process.pid}.${Math.random().toString(36).slice(2, 10)}`;
72
- import_node_fs.default.writeFileSync(tmp, id + "\n", { encoding: "utf-8", mode: 384 });
74
+ (0, import_stateFiles.writeStateFile)(tmp, id + "\n", { encoding: "utf-8", mode: 384 }, "write the machine id");
73
75
  try {
74
76
  import_node_fs.default.chmodSync(tmp, 384);
75
77
  } catch {
76
78
  }
77
- import_node_fs.default.renameSync(tmp, target);
79
+ (0, import_stateFiles.renameStateFile)(tmp, target, "write the machine id");
78
80
  }
79
81
  function defaultMachineUuidFallback() {
80
82
  const host = import_node_os.default.hostname() || "unknown-host";
@@ -118,6 +120,7 @@ function ensureMachineDaemonId(machineUuidFallback) {
118
120
  cachedStableDaemonId = fresh;
119
121
  return fresh;
120
122
  } catch (err) {
123
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
121
124
  log.warn(`[machineIdentity] persist ${target} failed: ${err.message} \u2014 using fallback`);
122
125
  const fallback = `fallback-${fallbackFn()}`;
123
126
  cachedStableDaemonId = fallback;
@@ -47,6 +47,8 @@ var import_node_os = __toESM(require("node:os"), 1);
47
47
  var import_node_path = __toESM(require("node:path"), 1);
48
48
  var import_node_child_process = require("node:child_process");
49
49
  var import_configRoot = require("./configRoot.js");
50
+ var import_liveState = require("./liveState.js");
51
+ var import_stateFiles = require("./stateFiles.js");
50
52
  const HOSTED_RUNTIME_MACHINE_SOURCES = /* @__PURE__ */ new Set([
51
53
  "hosted-machine",
52
54
  "cloud-coding",
@@ -152,7 +154,7 @@ function readPersistedChoice(file, hardware, profile, requireProfile = false) {
152
154
  }
153
155
  function persistChoice(dir, file, hardware, machineUuid, reason, profile, evidence) {
154
156
  try {
155
- import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
157
+ (0, import_stateFiles.ensureStateDir)(dir, "create the machine identity directory");
156
158
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
157
159
  const body = {
158
160
  version: 1,
@@ -168,10 +170,11 @@ function persistChoice(dir, file, hardware, machineUuid, reason, profile, eviden
168
170
  ...evidence && evidence.length > 0 ? { evidence: [...evidence] } : {},
169
171
  decidedAt: (/* @__PURE__ */ new Date()).toISOString()
170
172
  };
171
- import_node_fs.default.writeFileSync(tmp, `${JSON.stringify(body)}
172
- `, { mode: 384 });
173
- import_node_fs.default.renameSync(tmp, file);
174
- } catch {
173
+ (0, import_stateFiles.writeStateFile)(tmp, `${JSON.stringify(body)}
174
+ `, { mode: 384 }, "write the machine identity");
175
+ (0, import_stateFiles.renameStateFile)(tmp, file, "write the machine identity");
176
+ } catch (err) {
177
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
175
178
  }
176
179
  }
177
180
  function recoveryChoicePath(profile) {
@@ -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;