@kanadego/dsh-heartbeat 1.9.2 → 2.0.0

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 (57) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +344 -396
  3. package/client.js +108 -20
  4. package/config/policy.json +74 -73
  5. package/config/profile-schema.json +306 -306
  6. package/dist/{bindings-225SI7KC.js → bindings-U2M3CO2D.js} +3 -3
  7. package/dist/{chunk-TY6UP74X.js → chunk-3DNGY4DS.js} +2 -2
  8. package/dist/{chunk-CUOSICYJ.js → chunk-544QOJN5.js} +397 -23
  9. package/dist/chunk-544QOJN5.js.map +1 -0
  10. package/dist/{chunk-AJ5HLAL7.js → chunk-6MVKWICX.js} +708 -154
  11. package/dist/chunk-6MVKWICX.js.map +1 -0
  12. package/dist/{chunk-IG6YMUHW.js → chunk-6XM6KY7M.js} +43 -12
  13. package/dist/chunk-6XM6KY7M.js.map +1 -0
  14. package/dist/chunk-AKIJ4LS2.js +680 -0
  15. package/dist/chunk-AKIJ4LS2.js.map +1 -0
  16. package/dist/{chunk-KB5SMG3F.js → chunk-DC5HMCJC.js} +3 -3
  17. package/dist/{chunk-3MDBU6WY.js → chunk-DZ7YCFGX.js} +37 -7
  18. package/dist/chunk-DZ7YCFGX.js.map +1 -0
  19. package/dist/{chunk-WRUTATW4.js → chunk-IBBAISSW.js} +13 -2
  20. package/dist/chunk-IBBAISSW.js.map +1 -0
  21. package/dist/{chunk-ZLV4NMFX.js → chunk-IFRMKVIC.js} +85 -21
  22. package/dist/chunk-IFRMKVIC.js.map +1 -0
  23. package/dist/{chunk-XFNCTOW7.js → chunk-NCPKOWS7.js} +52 -4
  24. package/dist/chunk-NCPKOWS7.js.map +1 -0
  25. package/dist/{chunk-JDH2LDFS.js → chunk-TVX4HV4Y.js} +4 -6
  26. package/dist/{chunk-JDH2LDFS.js.map → chunk-TVX4HV4Y.js.map} +1 -1
  27. package/dist/cli/index.js +149 -26
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/{inbox-P26DDMWC.js → inbox-OWBK6PE2.js} +8 -7
  30. package/dist/index.d.ts +50 -315
  31. package/dist/index.js +288 -920
  32. package/dist/index.js.map +1 -1
  33. package/dist/migrate-TQ5PB2DI.js +27 -0
  34. package/dist/{orchestrator-EUTZ3KG6.js → orchestrator-P7AEWVYR.js} +27 -12
  35. package/dist/{pool-UTXQJJ5C.js → pool-2T6TVS2L.js} +4 -5
  36. package/dist/{snapshot-K5VHETTK.js → snapshot-F5B2DD56.js} +5 -5
  37. package/package.json +84 -83
  38. package/dist/chunk-3MDBU6WY.js.map +0 -1
  39. package/dist/chunk-AJ5HLAL7.js.map +0 -1
  40. package/dist/chunk-CUOSICYJ.js.map +0 -1
  41. package/dist/chunk-IG6YMUHW.js.map +0 -1
  42. package/dist/chunk-IV2ZWQA3.js +0 -56
  43. package/dist/chunk-IV2ZWQA3.js.map +0 -1
  44. package/dist/chunk-K5Y6JP2B.js +0 -399
  45. package/dist/chunk-K5Y6JP2B.js.map +0 -1
  46. package/dist/chunk-WRUTATW4.js.map +0 -1
  47. package/dist/chunk-XFNCTOW7.js.map +0 -1
  48. package/dist/chunk-ZLV4NMFX.js.map +0 -1
  49. package/dist/migrate-D4VKAKYT.js +0 -21
  50. /package/dist/{bindings-225SI7KC.js.map → bindings-U2M3CO2D.js.map} +0 -0
  51. /package/dist/{chunk-TY6UP74X.js.map → chunk-3DNGY4DS.js.map} +0 -0
  52. /package/dist/{chunk-KB5SMG3F.js.map → chunk-DC5HMCJC.js.map} +0 -0
  53. /package/dist/{inbox-P26DDMWC.js.map → inbox-OWBK6PE2.js.map} +0 -0
  54. /package/dist/{migrate-D4VKAKYT.js.map → migrate-TQ5PB2DI.js.map} +0 -0
  55. /package/dist/{orchestrator-EUTZ3KG6.js.map → orchestrator-P7AEWVYR.js.map} +0 -0
  56. /package/dist/{pool-UTXQJJ5C.js.map → pool-2T6TVS2L.js.map} +0 -0
  57. /package/dist/{snapshot-K5VHETTK.js.map → snapshot-F5B2DD56.js.map} +0 -0
@@ -1,399 +0,0 @@
1
- import {
2
- burnFileSync
3
- } from "./chunk-WRUTATW4.js";
4
-
5
- // src/core/paths.ts
6
- import fs2 from "fs";
7
- import os from "os";
8
- import path2 from "path";
9
- import { fileURLToPath } from "url";
10
-
11
- // src/core/data-relocate.ts
12
- import fs from "fs";
13
- import path from "path";
14
- import { createHash } from "crypto";
15
- var SKELETON_DIRS = ["settings", "logs", "tmp", "exports"];
16
- var SCRATCH_DIR = "tmp";
17
- var MAGIC = Buffer.from("KHBV1", "ascii");
18
- function hasPayload(dir) {
19
- if (!fs.existsSync(dir)) return false;
20
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
21
- if (!entry.isDirectory()) return true;
22
- if (!SKELETON_DIRS.includes(entry.name)) return true;
23
- if (entry.name === SCRATCH_DIR) continue;
24
- if (fs.readdirSync(path.join(dir, entry.name)).length > 0) return true;
25
- }
26
- return false;
27
- }
28
- function walk(dir, prefix = "", out = []) {
29
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
30
- const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
31
- if (entry.isDirectory()) {
32
- if (!prefix && entry.name === SCRATCH_DIR) continue;
33
- walk(path.join(dir, entry.name), rel, out);
34
- } else if (entry.isFile()) {
35
- out.push(rel);
36
- }
37
- }
38
- return out.sort();
39
- }
40
- function copyTree(src, dst) {
41
- const scratch = path.resolve(path.join(src, SCRATCH_DIR));
42
- fs.cpSync(src, dst, {
43
- recursive: true,
44
- force: true,
45
- preserveTimestamps: true,
46
- filter: (from) => path.resolve(from) !== scratch
47
- });
48
- }
49
- function removeTree(dir) {
50
- fs.rmSync(dir, { recursive: true, force: true });
51
- }
52
- function sha256(file) {
53
- return createHash("sha256").update(fs.readFileSync(file)).digest("hex");
54
- }
55
- function isEncryptedFile(file) {
56
- const fd = fs.openSync(file, "r");
57
- try {
58
- const head = Buffer.alloc(MAGIC.length);
59
- const read = fs.readSync(fd, head, 0, MAGIC.length, 0);
60
- return read === MAGIC.length && head.equals(MAGIC);
61
- } finally {
62
- fs.closeSync(fd);
63
- }
64
- }
65
- function parsesAsJson(file) {
66
- if (isEncryptedFile(file)) return true;
67
- const text = fs.readFileSync(file, "utf8");
68
- try {
69
- if (file.endsWith(".jsonl")) {
70
- for (const line of text.split("\n")) if (line.trim()) JSON.parse(line);
71
- return true;
72
- }
73
- JSON.parse(text);
74
- return true;
75
- } catch {
76
- return false;
77
- }
78
- }
79
- function verifyTrees(srcDir, dstDir) {
80
- const problems = [];
81
- const warnings = [];
82
- let files = 0;
83
- let bytes = 0;
84
- if (!fs.existsSync(dstDir)) {
85
- return { ok: false, files: 0, bytes: 0, problems: [`copy missing: ${dstDir}`], warnings };
86
- }
87
- const expected = walk(srcDir);
88
- const actual = walk(dstDir);
89
- const actualSet = new Set(actual);
90
- const expectedSet = new Set(expected);
91
- for (const rel of expected) if (!actualSet.has(rel)) problems.push(`missing: ${rel}`);
92
- for (const rel of actual) if (!expectedSet.has(rel)) problems.push(`unexpected: ${rel}`);
93
- for (const rel of expected) {
94
- const from = path.join(srcDir, rel);
95
- const to = path.join(dstDir, rel);
96
- if (!fs.existsSync(to)) continue;
97
- const sizeFrom = fs.statSync(from).size;
98
- const sizeTo = fs.statSync(to).size;
99
- files += 1;
100
- bytes += sizeFrom;
101
- if (sizeFrom !== sizeTo) {
102
- problems.push(`size differs: ${rel} (${sizeFrom} -> ${sizeTo})`);
103
- continue;
104
- }
105
- if (sha256(from) !== sha256(to)) {
106
- problems.push(`content differs: ${rel}`);
107
- continue;
108
- }
109
- if (/\.(json|jsonl)$/i.test(rel) && !parsesAsJson(to)) warnings.push(`unparseable: ${rel}`);
110
- }
111
- return { ok: problems.length === 0, files, bytes, problems, warnings };
112
- }
113
- function stamp(now) {
114
- const d = new Date(now);
115
- const pad = (n) => String(n).padStart(2, "0");
116
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
117
- }
118
- function errorText(error) {
119
- return error instanceof Error ? error.message : String(error);
120
- }
121
- function relocateDataDir(opts) {
122
- const srcDir = path.resolve(opts.srcDir);
123
- const dstDir = path.resolve(opts.dstDir);
124
- if (!fs.existsSync(srcDir)) return { action: "skipped", reason: "source-missing" };
125
- if (!hasPayload(srcDir)) return { action: "skipped", reason: "no-payload" };
126
- if (hasPayload(dstDir)) return { action: "skipped", reason: "destination-exists" };
127
- const suffix = stamp(opts.now ?? Date.now());
128
- const backupDir = `${srcDir}.bak-relocate-${suffix}`;
129
- const stagingDir = `${dstDir}.staging-${suffix}`;
130
- try {
131
- copyTree(srcDir, backupDir);
132
- } catch (error) {
133
- return { action: "failed", stage: "backup", problems: [errorText(error)], backupDir: null };
134
- }
135
- try {
136
- copyTree(srcDir, stagingDir);
137
- } catch (error) {
138
- return { action: "failed", stage: "copy", problems: [errorText(error)], backupDir };
139
- }
140
- const verified = verifyTrees(srcDir, stagingDir);
141
- if (!verified.ok) {
142
- try {
143
- removeTree(stagingDir);
144
- } catch {
145
- }
146
- return { action: "failed", stage: "verify", problems: verified.problems, backupDir };
147
- }
148
- try {
149
- if (fs.existsSync(dstDir)) removeTree(dstDir);
150
- fs.renameSync(stagingDir, dstDir);
151
- } catch (error) {
152
- try {
153
- removeTree(stagingDir);
154
- } catch {
155
- }
156
- return { action: "failed", stage: "commit", problems: [errorText(error)], backupDir };
157
- }
158
- const leftovers = [];
159
- try {
160
- removeTree(srcDir);
161
- } catch {
162
- leftovers.push(srcDir);
163
- }
164
- try {
165
- removeTree(backupDir);
166
- } catch {
167
- leftovers.push(backupDir);
168
- }
169
- return {
170
- action: "relocated",
171
- srcDir,
172
- dstDir,
173
- backupDir,
174
- files: verified.files,
175
- bytes: verified.bytes,
176
- warnings: verified.warnings,
177
- leftovers
178
- };
179
- }
180
-
181
- // src/core/paths.ts
182
- var resolved = null;
183
- function findPackageRoot(startFile) {
184
- let dir = path2.dirname(path2.resolve(startFile));
185
- for (; ; ) {
186
- if (fs2.existsSync(path2.join(dir, "package.json"))) return dir;
187
- const parent = path2.dirname(dir);
188
- if (parent === dir) throw new Error("package.json not found above " + startFile);
189
- dir = parent;
190
- }
191
- }
192
- function packageRoot() {
193
- return findPackageRoot(fileURLToPath(import.meta.url));
194
- }
195
- function defaultDataDir(env = process.env, home = os.homedir()) {
196
- const override = env.DSH_HOME?.trim();
197
- const root = override && override.length > 0 ? override : path2.join(home, ".dsh");
198
- return path2.join(root, "heartbeat-data");
199
- }
200
- function initWorkspace(config = {}) {
201
- if (resolved) return resolved;
202
- const root = packageRoot();
203
- const explicit = process.env.HEARTBEAT_DATA_DIR || config.dataDir;
204
- const homeDir = path2.resolve(defaultDataDir());
205
- const legacyDir = path2.join(root, "data");
206
- let dataDir;
207
- let relocation;
208
- if (explicit) {
209
- dataDir = path2.resolve(explicit);
210
- } else if (hasPayload(homeDir)) {
211
- dataDir = homeDir;
212
- } else if (hasPayload(legacyDir)) {
213
- relocation = relocateDataDir({ srcDir: legacyDir, dstDir: homeDir });
214
- dataDir = relocation.action === "relocated" ? homeDir : legacyDir;
215
- } else {
216
- dataDir = homeDir;
217
- }
218
- const paths = {
219
- dataDir,
220
- settingsDir: path2.join(dataDir, "settings"),
221
- logsDir: path2.join(dataDir, "logs"),
222
- tmpDir: path2.join(dataDir, "tmp"),
223
- exportsDir: path2.join(dataDir, "exports"),
224
- packageRoot: root,
225
- configDir: path2.join(root, "config"),
226
- assetsDir: path2.join(root, "assets"),
227
- ...relocation ? { relocation } : {}
228
- };
229
- for (const dir of [
230
- paths.dataDir,
231
- paths.settingsDir,
232
- paths.logsDir,
233
- paths.tmpDir,
234
- paths.exportsDir
235
- ]) {
236
- fs2.mkdirSync(dir, { recursive: true });
237
- }
238
- resolved = paths;
239
- return paths;
240
- }
241
- function workspace() {
242
- if (!resolved) throw new Error("workspace not initialized: call initWorkspace() first");
243
- return resolved;
244
- }
245
-
246
- // src/vault/vault.ts
247
- import fs3 from "fs";
248
- import path3 from "path";
249
- import { spawnSync } from "child_process";
250
- import { randomUUID } from "crypto";
251
- var MAGIC2 = Buffer.from("KHBV1", "ascii");
252
- var VAULT_TIMEOUT_MS = 3e4;
253
- var cachedScriptPath = null;
254
- function vaultScriptPath() {
255
- if (cachedScriptPath) return cachedScriptPath;
256
- const script = path3.join(workspace().assetsDir, "vault.ps1");
257
- if (!fs3.existsSync(script)) throw new Error(`vault script missing: ${script}`);
258
- cachedScriptPath = script;
259
- return script;
260
- }
261
- function tmpPlainName(target) {
262
- return path3.join(workspace().tmpDir, `.${path3.basename(target)}.${randomUUID().slice(0, 8)}.plain`);
263
- }
264
- function tmpEncName(target) {
265
- return path3.join(workspace().tmpDir, `.${path3.basename(target)}.${randomUUID().slice(0, 8)}.enc`);
266
- }
267
- function runVault(guard, args) {
268
- const r = spawnSync(
269
- "powershell.exe",
270
- ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", vaultScriptPath(), ...args],
271
- { timeout: VAULT_TIMEOUT_MS, encoding: "utf8" }
272
- );
273
- if (r.error) throw new Error(`vault spawn failed: ${String(r.error)}`);
274
- if (r.status !== 0) {
275
- const detail = (r.stderr || r.stdout || "").trim().split("\n").slice(-3).join("; ");
276
- throw new Error(`vault ${args[0]} failed (exit ${r.status}): ${detail}`);
277
- }
278
- }
279
- function isEncrypted(file) {
280
- if (!fs3.existsSync(file)) return false;
281
- const fd = fs3.openSync(file, "r");
282
- try {
283
- const hdr = Buffer.alloc(MAGIC2.length);
284
- const read = fs3.readSync(fd, hdr, 0, hdr.length, 0);
285
- return read === MAGIC2.length && hdr.equals(MAGIC2);
286
- } finally {
287
- fs3.closeSync(fd);
288
- }
289
- }
290
- function loadJson(guard, file) {
291
- const f = guard.assert(file);
292
- if (!fs3.existsSync(f)) return null;
293
- if (!isEncrypted(f)) {
294
- try {
295
- return JSON.parse(fs3.readFileSync(f, "utf8"));
296
- } catch {
297
- return null;
298
- }
299
- }
300
- const tmp = tmpPlainName(f);
301
- try {
302
- runVault(guard, ["unprotect", "-InFile", f, "-OutFile", tmp]);
303
- return JSON.parse(fs3.readFileSync(tmp, "utf8"));
304
- } finally {
305
- burnFileSync(tmp);
306
- }
307
- }
308
- function saveJson(guard, file, value) {
309
- const f = guard.assert(file);
310
- fs3.mkdirSync(path3.dirname(f), { recursive: true });
311
- const plain = tmpPlainName(f);
312
- const enc = tmpEncName(f);
313
- try {
314
- fs3.writeFileSync(plain, JSON.stringify(value, null, 2), "utf8");
315
- runVault(guard, ["protect", "-InFile", plain, "-OutFile", enc]);
316
- fs3.renameSync(enc, f);
317
- } finally {
318
- burnFileSync(plain);
319
- fs3.rmSync(enc, { force: true });
320
- }
321
- }
322
- function readText(guard, file, fallback = "") {
323
- const f = guard.assert(file);
324
- try {
325
- return fs3.readFileSync(f, "utf8");
326
- } catch {
327
- return fallback;
328
- }
329
- }
330
- function writeText(guard, file, content) {
331
- const f = guard.assert(file);
332
- fs3.mkdirSync(path3.dirname(f), { recursive: true });
333
- const tmp = tmpPlainName(f);
334
- try {
335
- fs3.writeFileSync(tmp, content, "utf8");
336
- fs3.renameSync(tmp, f);
337
- } finally {
338
- burnFileSync(tmp);
339
- }
340
- }
341
- function loadEncryptedText(guard, file) {
342
- const f = guard.assert(file);
343
- if (!fs3.existsSync(f)) return null;
344
- if (!isEncrypted(f)) return fs3.readFileSync(f, "utf8");
345
- const tmp = tmpPlainName(f);
346
- try {
347
- runVault(guard, ["unprotect", "-InFile", f, "-OutFile", tmp]);
348
- return fs3.readFileSync(tmp, "utf8");
349
- } finally {
350
- burnFileSync(tmp);
351
- }
352
- }
353
- function saveEncryptedText(guard, file, content) {
354
- const f = guard.assert(file);
355
- fs3.mkdirSync(path3.dirname(f), { recursive: true });
356
- const plain = tmpPlainName(f);
357
- const enc = tmpEncName(f);
358
- try {
359
- fs3.writeFileSync(plain, content, "utf8");
360
- runVault(guard, ["protect", "-InFile", plain, "-OutFile", enc]);
361
- fs3.renameSync(enc, f);
362
- } finally {
363
- burnFileSync(plain);
364
- fs3.rmSync(enc, { force: true });
365
- }
366
- }
367
- function encryptFile(guard, inFile, outFile) {
368
- const inPath = guard.assert(inFile);
369
- const outPath = guard.assert(outFile);
370
- fs3.mkdirSync(path3.dirname(outPath), { recursive: true });
371
- const enc = tmpEncName(outPath);
372
- try {
373
- runVault(guard, ["protect", "-InFile", inPath, "-OutFile", enc]);
374
- fs3.renameSync(enc, outPath);
375
- } finally {
376
- fs3.rmSync(enc, { force: true });
377
- }
378
- }
379
- function decryptFile(guard, inFile, outFile) {
380
- const inPath = guard.assert(inFile);
381
- const outPath = guard.assert(outFile);
382
- fs3.mkdirSync(path3.dirname(outPath), { recursive: true });
383
- runVault(guard, ["unprotect", "-InFile", inPath, "-OutFile", outPath]);
384
- }
385
-
386
- export {
387
- initWorkspace,
388
- workspace,
389
- isEncrypted,
390
- loadJson,
391
- saveJson,
392
- readText,
393
- writeText,
394
- loadEncryptedText,
395
- saveEncryptedText,
396
- encryptFile,
397
- decryptFile
398
- };
399
- //# sourceMappingURL=chunk-K5Y6JP2B.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/paths.ts","../src/core/data-relocate.ts","../src/vault/vault.ts"],"sourcesContent":["// Workspace/data directory resolution and package asset location.\n//\n// The data dir is the ONLY directory the plugin writes to at runtime\n// (requirement 8). Resolution order: env override > plugin config > default\n// (`$DSH_HOME/heartbeat-data`) — with a legacy fallback to `packageRoot()/data`\n// for installs that predate v1.9.2, which is relocated out of the package on\n// first sight (issue #2: a reinstall replaced the package dir and took the\n// user's data with it). All fs writes must go through the path guard created\n// from this dir (see core/path-guard.ts).\n\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { hasPayload, relocateDataDir, type RelocateResult } from './data-relocate.js';\n\nexport interface WorkspacePaths {\n /** Guard boundary and the only writable tree at runtime. */\n dataDir: string;\n settingsDir: string;\n logsDir: string;\n tmpDir: string;\n exportsDir: string;\n /** Package root (where package.json lives). Read-only at runtime. */\n packageRoot: string;\n configDir: string;\n assetsDir: string;\n /**\n * Set only when a legacy package-local data dir was found: the outcome of\n * the one-time relocation. Absent for explicit dirs and fresh installs.\n */\n relocation?: RelocateResult;\n}\n\nexport interface WorkspaceConfigInput {\n dataDir?: string | null;\n}\n\nlet resolved: WorkspacePaths | null = null;\n\n/** Walk up from a module file to the directory containing package.json. */\nexport function findPackageRoot(startFile: string): string {\n let dir = path.dirname(path.resolve(startFile));\n for (;;) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) throw new Error('package.json not found above ' + startFile);\n dir = parent;\n }\n}\n\nexport function packageRoot(): string {\n return findPackageRoot(fileURLToPath(import.meta.url));\n}\n\n/**\n * Default data dir: `$DSH_HOME/heartbeat-data` (or `~/.dsh/heartbeat-data`).\n * Deliberately outside the package tree — reinstalling or upgrading the plugin\n * replaces that directory and must not touch user data (issue #2).\n */\nexport function defaultDataDir(\n env: NodeJS.ProcessEnv = process.env,\n home: string = os.homedir(),\n): string {\n const override = env.DSH_HOME?.trim();\n const root = override && override.length > 0 ? override : path.join(home, '.dsh');\n return path.join(root, 'heartbeat-data');\n}\n\n/**\n * Resolve and create the workspace layout. Idempotent; must be called once\n * from the plugin entry before any fs write happens.\n */\nexport function initWorkspace(config: WorkspaceConfigInput = {}): WorkspacePaths {\n if (resolved) return resolved;\n const root = packageRoot();\n const explicit = process.env.HEARTBEAT_DATA_DIR || config.dataDir;\n const homeDir = path.resolve(defaultDataDir());\n const legacyDir = path.join(root, 'data');\n let dataDir: string;\n let relocation: RelocateResult | undefined;\n if (explicit) {\n // Explicit configuration always wins, and never moves anything.\n dataDir = path.resolve(explicit);\n } else if (hasPayload(homeDir)) {\n // Already on the new home (a previous run, or a reinstall that kept it).\n dataDir = homeDir;\n } else if (hasPayload(legacyDir)) {\n // Pre-v1.9.2 install: the data lived inside the package. Move it out, and\n // keep serving the legacy tree unless the move verified end to end.\n relocation = relocateDataDir({ srcDir: legacyDir, dstDir: homeDir });\n dataDir = relocation.action === 'relocated' ? homeDir : legacyDir;\n } else {\n dataDir = homeDir;\n }\n const paths: WorkspacePaths = {\n dataDir,\n settingsDir: path.join(dataDir, 'settings'),\n logsDir: path.join(dataDir, 'logs'),\n tmpDir: path.join(dataDir, 'tmp'),\n exportsDir: path.join(dataDir, 'exports'),\n packageRoot: root,\n configDir: path.join(root, 'config'),\n assetsDir: path.join(root, 'assets'),\n ...(relocation ? { relocation } : {}),\n };\n // Bootstrap creation happens before the guard exists; creating exactly one\n // directory tree at a known location is the safe window for this.\n for (const dir of [\n paths.dataDir,\n paths.settingsDir,\n paths.logsDir,\n paths.tmpDir,\n paths.exportsDir,\n ]) {\n fs.mkdirSync(dir, { recursive: true });\n }\n resolved = paths;\n return paths;\n}\n\n/** Access already-initialized paths. Throws if initWorkspace was not called. */\nexport function workspace(): WorkspacePaths {\n if (!resolved) throw new Error('workspace not initialized: call initWorkspace() first');\n return resolved;\n}\n\n/** Test-only: forget the resolved singleton so a later init re-resolves. */\nexport function resetWorkspaceForTest(): void {\n resolved = null;\n}\n","// One-time relocation of the data dir out of the package tree (issue #2).\n//\n// Installs created before v1.9.2 defaulted to `packageRoot()/data`, and every\n// `dsh plugin add` / upgrade replaces the package directory wholesale — so a\n// reinstall deleted the profile, the gate and the ledger, which the original\n// design meant to keep until an explicit CLI `burn`. The default now lives at\n// `$DSH_HOME/heartbeat-data`; this module moves an existing package-local tree\n// there.\n//\n// Safety order — a failure at any point leaves the original untouched:\n// 1. copy src -> backup (kept until the move is verified and committed)\n// 2. copy src -> dst.staging-<stamp>\n// 3. verify staging against src: same file list, same size + sha256, and\n// every plaintext .json/.jsonl still parses\n// 4. rename staging -> dst (same volume, atomic)\n// 5. only then delete src and the backup\n// The caller keeps using `src` unless the result is `relocated`.\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\n\n/** Directories the bootstrap always creates: their presence alone is no payload. */\nconst SKELETON_DIRS = ['settings', 'logs', 'tmp', 'exports'];\n/** Scratch space (decrypt windows, downloads): never copied, never verified. */\nconst SCRATCH_DIR = 'tmp';\nconst MAGIC = Buffer.from('KHBV1', 'ascii');\n\nexport interface RelocateOptions {\n srcDir: string;\n dstDir: string;\n /** Injectable clock, so tests get deterministic backup/staging names. */\n now?: number;\n}\n\nexport type RelocateResult =\n | {\n action: 'relocated';\n /** Package-local tree the data came from (now deleted). */\n srcDir: string;\n dstDir: string;\n backupDir: string;\n files: number;\n bytes: number;\n /** Plaintext JSON/JSONL that does not parse (verified byte-identical). */\n warnings: string[];\n /** Paths that could not be deleted after the move (cosmetic leftovers). */\n leftovers: string[];\n }\n | { action: 'skipped'; reason: 'source-missing' | 'no-payload' | 'destination-exists' }\n | {\n action: 'failed';\n stage: 'backup' | 'copy' | 'verify' | 'commit';\n problems: string[];\n /** Null when the backup itself could not be created. */\n backupDir: string | null;\n };\n\nexport interface VerifyResult {\n /** Byte-for-byte the same file list, sizes and contents. */\n ok: boolean;\n files: number;\n bytes: number;\n problems: string[];\n /**\n * Plaintext JSON/JSONL files that do not parse. They are reported but do not\n * fail the move: an identical copy of a file that was already damaged is\n * still byte-for-byte the user's data, and refusing to relocate would strand\n * the data dir in the package forever.\n */\n warnings: string[];\n}\n\n/**\n * True when a data dir holds real user data. An empty bootstrap skeleton\n * (`settings/ logs/ tmp/ exports/`, all empty) does not count — otherwise a\n * freshly created default dir would shadow a legacy tree with actual data.\n */\nexport function hasPayload(dir: string): boolean {\n if (!fs.existsSync(dir)) return false;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n if (!entry.isDirectory()) return true;\n if (!SKELETON_DIRS.includes(entry.name)) return true;\n if (entry.name === SCRATCH_DIR) continue;\n if (fs.readdirSync(path.join(dir, entry.name)).length > 0) return true;\n }\n return false;\n}\n\n/** Every file under `dir` (relative, `/`-separated, sorted), excluding tmp/. */\nfunction walk(dir: string, prefix = '', out: string[] = []): string[] {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n if (!prefix && entry.name === SCRATCH_DIR) continue;\n walk(path.join(dir, entry.name), rel, out);\n } else if (entry.isFile()) {\n out.push(rel);\n }\n }\n return out.sort();\n}\n\nfunction copyTree(src: string, dst: string): void {\n const scratch = path.resolve(path.join(src, SCRATCH_DIR));\n fs.cpSync(src, dst, {\n recursive: true,\n force: true,\n preserveTimestamps: true,\n filter: (from) => path.resolve(from) !== scratch,\n });\n}\n\nfunction removeTree(dir: string): void {\n fs.rmSync(dir, { recursive: true, force: true });\n}\n\nfunction sha256(file: string): string {\n return createHash('sha256').update(fs.readFileSync(file)).digest('hex');\n}\n\n/** KHBV1 header check (local copy: importing vault here would cycle through paths). */\nfunction isEncryptedFile(file: string): boolean {\n const fd = fs.openSync(file, 'r');\n try {\n const head = Buffer.alloc(MAGIC.length);\n const read = fs.readSync(fd, head, 0, MAGIC.length, 0);\n return read === MAGIC.length && head.equals(MAGIC);\n } finally {\n fs.closeSync(fd);\n }\n}\n\n/**\n * Schema-free readability check: an encrypted container is opaque here (its\n * bytes were already compared by hash), a plaintext .json/.jsonl must still\n * parse — that is what catches a half-written or truncated copy.\n */\nfunction parsesAsJson(file: string): boolean {\n if (isEncryptedFile(file)) return true;\n const text = fs.readFileSync(file, 'utf8');\n try {\n if (file.endsWith('.jsonl')) {\n for (const line of text.split('\\n')) if (line.trim()) JSON.parse(line);\n return true;\n }\n JSON.parse(text);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Compare a copy against its source: identical file list, identical size and\n * content (the hard gate), plus a readability report on the plaintext\n * JSON/JSONL in the copy (warnings only — see VerifyResult).\n */\nexport function verifyTrees(srcDir: string, dstDir: string): VerifyResult {\n const problems: string[] = [];\n const warnings: string[] = [];\n let files = 0;\n let bytes = 0;\n if (!fs.existsSync(dstDir)) {\n return { ok: false, files: 0, bytes: 0, problems: [`copy missing: ${dstDir}`], warnings };\n }\n const expected = walk(srcDir);\n const actual = walk(dstDir);\n const actualSet = new Set(actual);\n const expectedSet = new Set(expected);\n for (const rel of expected) if (!actualSet.has(rel)) problems.push(`missing: ${rel}`);\n for (const rel of actual) if (!expectedSet.has(rel)) problems.push(`unexpected: ${rel}`);\n for (const rel of expected) {\n const from = path.join(srcDir, rel);\n const to = path.join(dstDir, rel);\n if (!fs.existsSync(to)) continue; // already reported above\n const sizeFrom = fs.statSync(from).size;\n const sizeTo = fs.statSync(to).size;\n files += 1;\n bytes += sizeFrom;\n if (sizeFrom !== sizeTo) {\n problems.push(`size differs: ${rel} (${sizeFrom} -> ${sizeTo})`);\n continue;\n }\n if (sha256(from) !== sha256(to)) {\n problems.push(`content differs: ${rel}`);\n continue;\n }\n if (/\\.(json|jsonl)$/i.test(rel) && !parsesAsJson(to)) warnings.push(`unparseable: ${rel}`);\n }\n return { ok: problems.length === 0, files, bytes, problems, warnings };\n}\n\nfunction stamp(now: number): string {\n const d = new Date(now);\n const pad = (n: number): string => String(n).padStart(2, '0');\n return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;\n}\n\nfunction errorText(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * Move a package-local data dir to its new home. Never throws: every failure\n * is reported as `failed` with the original tree left in place.\n */\nexport function relocateDataDir(opts: RelocateOptions): RelocateResult {\n const srcDir = path.resolve(opts.srcDir);\n const dstDir = path.resolve(opts.dstDir);\n if (!fs.existsSync(srcDir)) return { action: 'skipped', reason: 'source-missing' };\n if (!hasPayload(srcDir)) return { action: 'skipped', reason: 'no-payload' };\n if (hasPayload(dstDir)) return { action: 'skipped', reason: 'destination-exists' };\n\n const suffix = stamp(opts.now ?? Date.now());\n const backupDir = `${srcDir}.bak-relocate-${suffix}`;\n const stagingDir = `${dstDir}.staging-${suffix}`;\n\n // 1. backup first — the user asked for a copy that outlives a failed move.\n try {\n copyTree(srcDir, backupDir);\n } catch (error) {\n return { action: 'failed', stage: 'backup', problems: [errorText(error)], backupDir: null };\n }\n\n // 2. copy to a staging dir beside the destination (same volume → atomic rename).\n try {\n copyTree(srcDir, stagingDir);\n } catch (error) {\n return { action: 'failed', stage: 'copy', problems: [errorText(error)], backupDir };\n }\n\n // 3. verify before anything is deleted or published.\n const verified = verifyTrees(srcDir, stagingDir);\n if (!verified.ok) {\n try {\n removeTree(stagingDir);\n } catch {\n // A stranded staging dir is harmless: the next run overwrites it.\n }\n return { action: 'failed', stage: 'verify', problems: verified.problems, backupDir };\n }\n\n // 4. publish: the destination may hold an empty skeleton from an earlier\n // bootstrap, which is not payload (checked above), so clear it and rename.\n try {\n if (fs.existsSync(dstDir)) removeTree(dstDir);\n fs.renameSync(stagingDir, dstDir);\n } catch (error) {\n try {\n removeTree(stagingDir);\n } catch {\n // best effort\n }\n return { action: 'failed', stage: 'commit', problems: [errorText(error)], backupDir };\n }\n\n // 5. the new tree is live; the originals go last. A leftover only wastes\n // space — the destination already wins on the next resolve.\n const leftovers: string[] = [];\n try {\n removeTree(srcDir);\n } catch {\n leftovers.push(srcDir);\n }\n try {\n removeTree(backupDir);\n } catch {\n leftovers.push(backupDir);\n }\n\n return {\n action: 'relocated',\n srcDir,\n dstDir,\n backupDir,\n files: verified.files,\n bytes: verified.bytes,\n warnings: verified.warnings,\n leftovers,\n };\n}\n","// Transparent encryption read/write layer (port of v1 lib/vault.mjs).\n//\n// Mechanism: Node <-> vault.ps1 hand off through temp files (plain text\n// windows are millisecond-scale). v2 hardening over v1:\n// - plaintext temp files live ONLY under data/tmp (never beside targets);\n// - every path passes the workspace guard before reaching PowerShell;\n// - encrypted output lands via same-volume rename (atomic replace).\n// Encryption failure is fail-closed: throw, never fall back to plaintext.\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { spawnSync } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport type { PathGuard } from '../core/path-guard.js';\nimport { workspace } from '../core/paths.js';\nimport { burnFileSync } from '../core/atomic-fs.js';\n\nconst MAGIC = Buffer.from('KHBV1', 'ascii');\nconst VAULT_TIMEOUT_MS = 30_000;\n\nlet cachedScriptPath: string | null = null;\n\nfunction vaultScriptPath(): string {\n if (cachedScriptPath) return cachedScriptPath;\n const script = path.join(workspace().assetsDir, 'vault.ps1');\n if (!fs.existsSync(script)) throw new Error(`vault script missing: ${script}`);\n cachedScriptPath = script;\n return script;\n}\n\nfunction tmpPlainName(target: string): string {\n return path.join(workspace().tmpDir, `.${path.basename(target)}.${randomUUID().slice(0, 8)}.plain`);\n}\n\nfunction tmpEncName(target: string): string {\n return path.join(workspace().tmpDir, `.${path.basename(target)}.${randomUUID().slice(0, 8)}.enc`);\n}\n\nfunction runVault(guard: PathGuard, args: string[]): void {\n const r = spawnSync(\n 'powershell.exe',\n ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', vaultScriptPath(), ...args],\n { timeout: VAULT_TIMEOUT_MS, encoding: 'utf8' },\n );\n if (r.error) throw new Error(`vault spawn failed: ${String(r.error)}`);\n if (r.status !== 0) {\n const detail = (r.stderr || r.stdout || '').trim().split('\\n').slice(-3).join('; ');\n throw new Error(`vault ${args[0]} failed (exit ${r.status}): ${detail}`);\n }\n}\n\n/** KHBV1 header check without reading the whole file. */\nexport function isEncrypted(file: string): boolean {\n if (!fs.existsSync(file)) return false;\n const fd = fs.openSync(file, 'r');\n try {\n const hdr = Buffer.alloc(MAGIC.length);\n const read = fs.readSync(fd, hdr, 0, hdr.length, 0);\n return read === MAGIC.length && hdr.equals(MAGIC);\n } finally {\n fs.closeSync(fd);\n }\n}\n\n/**\n * Read JSON: encrypted files are decrypted to a data/tmp plaintext window\n * that is shredded immediately after parse; plaintext stays compatible.\n */\nexport function loadJson<T>(guard: PathGuard, file: string): T | null {\n const f = guard.assert(file);\n if (!fs.existsSync(f)) return null;\n if (!isEncrypted(f)) {\n try {\n return JSON.parse(fs.readFileSync(f, 'utf8')) as T;\n } catch {\n return null;\n }\n }\n const tmp = tmpPlainName(f);\n try {\n runVault(guard, ['unprotect', '-InFile', f, '-OutFile', tmp]);\n return JSON.parse(fs.readFileSync(tmp, 'utf8')) as T;\n } finally {\n burnFileSync(tmp);\n }\n}\n\n/** Write JSON: plaintext window in data/tmp -> DPAPI encrypt -> atomic rename into place. */\nexport function saveJson(guard: PathGuard, file: string, value: unknown): void {\n const f = guard.assert(file);\n fs.mkdirSync(path.dirname(f), { recursive: true });\n const plain = tmpPlainName(f);\n const enc = tmpEncName(f);\n try {\n fs.writeFileSync(plain, JSON.stringify(value, null, 2), 'utf8');\n runVault(guard, ['protect', '-InFile', plain, '-OutFile', enc]);\n fs.renameSync(enc, f);\n } finally {\n burnFileSync(plain);\n fs.rmSync(enc, { force: true });\n }\n}\n\n/** Read a plaintext UTF-8 text file (ledger etc.), with fallback. */\nexport function readText(guard: PathGuard, file: string, fallback = ''): string {\n const f = guard.assert(file);\n try {\n return fs.readFileSync(f, 'utf8');\n } catch {\n return fallback;\n }\n}\n\n/** Write a plaintext UTF-8 text file atomically (ledger etc.). */\nexport function writeText(guard: PathGuard, file: string, content: string): void {\n const f = guard.assert(file);\n fs.mkdirSync(path.dirname(f), { recursive: true });\n const tmp = tmpPlainName(f);\n try {\n fs.writeFileSync(tmp, content, 'utf8');\n fs.renameSync(tmp, f);\n } finally {\n burnFileSync(tmp);\n }\n}\n\n/** Read an encrypted UTF-8 text file (e.g. seeds.jsonl). Missing -> null. */\nexport function loadEncryptedText(guard: PathGuard, file: string): string | null {\n const f = guard.assert(file);\n if (!fs.existsSync(f)) return null;\n if (!isEncrypted(f)) return fs.readFileSync(f, 'utf8');\n const tmp = tmpPlainName(f);\n try {\n runVault(guard, ['unprotect', '-InFile', f, '-OutFile', tmp]);\n return fs.readFileSync(tmp, 'utf8');\n } finally {\n burnFileSync(tmp);\n }\n}\n\n/** Write an encrypted UTF-8 text file atomically. */\nexport function saveEncryptedText(guard: PathGuard, file: string, content: string): void {\n const f = guard.assert(file);\n fs.mkdirSync(path.dirname(f), { recursive: true });\n const plain = tmpPlainName(f);\n const enc = tmpEncName(f);\n try {\n fs.writeFileSync(plain, content, 'utf8');\n runVault(guard, ['protect', '-InFile', plain, '-OutFile', enc]);\n fs.renameSync(enc, f);\n } finally {\n burnFileSync(plain);\n fs.rmSync(enc, { force: true });\n }\n}\n\n/** Encrypt any binary file (e.g. screenshot): inFile -> encrypted outFile. */\nexport function encryptFile(guard: PathGuard, inFile: string, outFile: string): void {\n const inPath = guard.assert(inFile);\n const outPath = guard.assert(outFile);\n fs.mkdirSync(path.dirname(outPath), { recursive: true });\n const enc = tmpEncName(outPath);\n try {\n runVault(guard, ['protect', '-InFile', inPath, '-OutFile', enc]);\n fs.renameSync(enc, outPath);\n } finally {\n fs.rmSync(enc, { force: true });\n }\n}\n\n/**\n * Decrypt an encrypted file to a plaintext output (caller must place the\n * output under data/tmp and delete it after use; \"use then burn\" discipline).\n */\nexport function decryptFile(guard: PathGuard, inFile: string, outFile: string): void {\n const inPath = guard.assert(inFile);\n const outPath = guard.assert(outFile);\n fs.mkdirSync(path.dirname(outPath), { recursive: true });\n runVault(guard, ['unprotect', '-InFile', inPath, '-OutFile', outPath]);\n}\n"],"mappings":"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","names":["fs","path","path","fs","fs","path","MAGIC","path","fs"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/atomic-fs.ts"],"sourcesContent":["// Crash-safe file replacement: write to a random-suffix temp file in the SAME\n// directory as the target, then rename over it. Same-directory rename stays\n// on one volume (atomic) and Node's rename replaces existing files on Windows.\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { randomUUID, randomFillSync } from 'node:crypto';\n\nexport function tmpSibling(target: string, tag = 'w'): string {\n return path.join(\n path.dirname(target),\n `.${path.basename(target)}.${tag}-${randomUUID().slice(0, 8)}.tmp`,\n );\n}\n\nexport function atomicWriteFileSync(target: string, data: string | Uint8Array): void {\n const tmp = tmpSibling(target);\n try {\n fs.writeFileSync(tmp, data);\n fs.renameSync(tmp, target);\n } finally {\n fs.rmSync(tmp, { force: true });\n }\n}\n\nexport function atomicWriteJsonSync(target: string, value: unknown): void {\n atomicWriteFileSync(target, JSON.stringify(value, null, 2));\n}\n\n/** Overwrite a file's bytes with random data before unlinking. */\nexport function shredFileSync(target: string, passes = 3): void {\n const stat = fs.statSync(target);\n if (!stat.isFile()) throw new Error(`shred: not a file: ${target}`);\n const buf = Buffer.alloc(Math.max(stat.size, 1));\n for (let i = 0; i < passes; i++) {\n randomFillSync(buf);\n fs.writeFileSync(target, buf);\n }\n fs.rmSync(target, { force: true });\n}\n\n/**\n * Best-effort burn for a plaintext window: shred it when it exists, ignore it\n * when it is already gone (H-59 — `rm` alone leaves the bytes on disk).\n */\nexport function burnFileSync(target: string): void {\n try {\n shredFileSync(target);\n } catch {\n fs.rmSync(target, { force: true });\n }\n}\n"],"mappings":";AAIA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,YAAY,sBAAsB;AAEpC,SAAS,WAAW,QAAgB,MAAM,KAAa;AAC5D,SAAO,KAAK;AAAA,IACV,KAAK,QAAQ,MAAM;AAAA,IACnB,IAAI,KAAK,SAAS,MAAM,CAAC,IAAI,GAAG,IAAI,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,EAC9D;AACF;AAEO,SAAS,oBAAoB,QAAgB,MAAiC;AACnF,QAAM,MAAM,WAAW,MAAM;AAC7B,MAAI;AACF,OAAG,cAAc,KAAK,IAAI;AAC1B,OAAG,WAAW,KAAK,MAAM;AAAA,EAC3B,UAAE;AACA,OAAG,OAAO,KAAK,EAAE,OAAO,KAAK,CAAC;AAAA,EAChC;AACF;AAEO,SAAS,oBAAoB,QAAgB,OAAsB;AACxE,sBAAoB,QAAQ,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAC5D;AAGO,SAAS,cAAc,QAAgB,SAAS,GAAS;AAC9D,QAAM,OAAO,GAAG,SAAS,MAAM;AAC/B,MAAI,CAAC,KAAK,OAAO,EAAG,OAAM,IAAI,MAAM,sBAAsB,MAAM,EAAE;AAClE,QAAM,MAAM,OAAO,MAAM,KAAK,IAAI,KAAK,MAAM,CAAC,CAAC;AAC/C,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,mBAAe,GAAG;AAClB,OAAG,cAAc,QAAQ,GAAG;AAAA,EAC9B;AACA,KAAG,OAAO,QAAQ,EAAE,OAAO,KAAK,CAAC;AACnC;AAMO,SAAS,aAAa,QAAsB;AACjD,MAAI;AACF,kBAAc,MAAM;AAAA,EACtB,QAAQ;AACN,OAAG,OAAO,QAAQ,EAAE,OAAO,KAAK,CAAC;AAAA,EACnC;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/vault/migrate.ts"],"sourcesContent":["// Cross-machine migration (v1.8.0). DPAPI binds the sensitive stores to one\r\n// machine + account, so a new machine cannot read them — this module packs\r\n// the memory files into ONE passphrase-encrypted container (AES-256-GCM,\r\n// scrypt-derived key, node:crypto only) that can travel safely, and restores\r\n// them on the other side re-encrypted with the LOCAL DPAPI.\r\n//\r\n// Restore is byte-identical: for each file we record whether it was DPAPI\r\n// encrypted on disk (isEncrypted magic check) and write it back through the\r\n// same path — no per-module write semantics to keep in sync. Existing files\r\n// are backed up before overwrite (rule 4).\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport {\r\n createCipheriv,\r\n createDecipheriv,\r\n randomBytes,\r\n scryptSync,\r\n} from 'node:crypto';\r\nimport type { PathGuard } from '../core/path-guard.js';\r\nimport type { WorkspacePaths } from '../core/paths.js';\r\nimport { isEncrypted, loadEncryptedText, saveEncryptedText } from './vault.js';\r\n\r\nexport const MIGRATE_MAGIC = 'HBMIG1';\r\nexport const MIGRATE_VERSION = 1;\r\n/** scrypt cost for NEW containers (2^14 = Node's default, 16 MiB of work).\r\n * Reading is parameterised by the container's own kdf.N, so raising this\r\n * never orphans an existing export. */\r\nconst KDF_N = 163_84;\r\n/** Hard bounds for a container-supplied N: a hostile/broken container must not\r\n * be able to make the import allocate unbounded memory. */\r\nconst KDF_N_MIN = 1 << 12; // 2^12 = 4 MiB\r\nconst KDF_N_MAX = 1 << 16; // 2^16 = 64 MiB\r\n/** Node's scrypt maxmem defaults to 32 MiB, which N=2^15 already exceeds — a\r\n * legal container with a larger N would fail without an explicit cap. */\r\nconst SCRYPT_MAXMEM = 128 * 1024 * 1024;\r\nconst KEY_LEN = 32;\r\n\r\n/** Memory-relevant runtime files (dataDir-relative). Deliberately NOT here:\r\n * gate.json (machine-local counters + home session id — the new machine\r\n * builds its own) and screen snapshots (per-beat transients, not memory). */\r\nexport const MIGRATE_FILES = [\r\n 'profile.json',\r\n 'profile_snapshot.json',\r\n 'profile_inbox.jsonl',\r\n 'profile_journal.jsonl',\r\n 'profile_rhythm.json',\r\n 'sent.json',\r\n 'browse.json',\r\n 'seeds.jsonl',\r\n 'ledger.md',\r\n 'cursors.json',\r\n // v1.9.0: persona report log + topic preference stats\r\n 'seed_report.jsonl',\r\n 'preference.json',\r\n];\r\n\r\nexport interface MigrateEntry {\r\n path: string; // dataDir-relative\r\n encrypted: boolean; // was DPAPI-encrypted on the source machine\r\n content: string;\r\n}\r\n\r\nexport interface MigrateProgress {\r\n packed: string[];\r\n missing: string[];\r\n}\r\n\r\nfunction collectWeeklyFiles(guard: PathGuard, paths: WorkspacePaths): string[] {\r\n const dir = path.join(paths.dataDir, 'weekly');\r\n try {\r\n return fs.readdirSync(guard.assert(dir))\r\n .filter((f) => f.endsWith('.json'))\r\n .map((f) => path.join('weekly', f).replace(/\\\\/g, '/'));\r\n } catch {\r\n return [];\r\n }\r\n}\r\n\r\n/** Journal archive shards (snapshot rotation output) travel with the memory. */\r\nfunction collectJournalArchives(guard: PathGuard, paths: WorkspacePaths): string[] {\r\n try {\r\n return fs.readdirSync(guard.assert(paths.dataDir))\r\n .filter((f) => f.startsWith('profile_journal.archive-') && f.endsWith('.jsonl'));\r\n } catch {\r\n return [];\r\n }\r\n}\r\n\r\n/** Read every migration file. A DPAPI file that fails to decrypt aborts the\r\n * export — a silent half-memory container would be worse than a loud error. */\r\nexport function collectMigrationEntries(guard: PathGuard, paths: WorkspacePaths): { entries: MigrateEntry[]; progress: MigrateProgress } {\r\n const entries: MigrateEntry[] = [];\r\n const packed: string[] = [];\r\n const missing: string[] = [];\r\n const rels = [...MIGRATE_FILES, ...collectJournalArchives(guard, paths), ...collectWeeklyFiles(guard, paths)];\r\n for (const rel of rels) {\r\n const abs = path.join(paths.dataDir, rel);\r\n let absCanon: string;\r\n try {\r\n absCanon = guard.assert(abs);\r\n } catch {\r\n missing.push(rel);\r\n continue;\r\n }\r\n if (!fs.existsSync(absCanon)) {\r\n missing.push(rel);\r\n continue;\r\n }\r\n const encrypted = isEncrypted(absCanon);\r\n const content = encrypted\r\n ? loadEncryptedText(guard, absCanon) ?? (() => { throw new Error(`cannot decrypt ${rel} on this machine`); })()\r\n : fs.readFileSync(absCanon, 'utf8');\r\n entries.push({ path: rel.replace(/\\\\/g, '/'), encrypted, content });\r\n packed.push(rel);\r\n }\r\n return { entries, progress: { packed, missing } };\r\n}\r\n\r\n/** Derive the key with the container's OWN cost parameter. `n` must already\r\n * have passed assertKdfN (export uses the current default). */\r\nfunction deriveKey(passphrase: string, salt: Buffer, n: number): Buffer {\r\n return scryptSync(passphrase, salt, KEY_LEN, { N: n, r: 8, p: 1, maxmem: SCRYPT_MAXMEM });\r\n}\r\n\r\n/** Validate the scrypt cost carried by a container. A missing/absurd N is a\r\n * BROKEN container — reported as such, never as \"wrong passphrase\" (before\r\n * 2026-10-06 the N in the container was ignored and the two were the same\r\n * error, so raising the cost would have orphaned every old export). */\r\nfunction assertKdfN(v: unknown): number {\r\n const n = Number(v);\r\n if (!Number.isInteger(n) || n <= 0 || (n & (n - 1)) !== 0) {\r\n throw new Error('migrate: container has invalid kdf parameters (N must be a power of two)');\r\n }\r\n if (n < KDF_N_MIN || n > KDF_N_MAX) {\r\n throw new Error(`migrate: container kdf N ${n} is outside the supported range [${KDF_N_MIN}, ${KDF_N_MAX}]`);\r\n }\r\n return n;\r\n}\r\n\r\n/** Build the container file content. Everything sensitive lives inside the\r\n * ciphertext; the outer JSON carries only crypto parameters. */\r\nexport function encryptContainer(entries: MigrateEntry[], passphrase: string, now = new Date()): string {\r\n if (!passphrase) throw new Error('migrate: passphrase must not be empty');\r\n const salt = randomBytes(16);\r\n const iv = randomBytes(12);\r\n const key = deriveKey(passphrase, salt, KDF_N);\r\n const cipher = createCipheriv('aes-256-gcm', key, iv);\r\n const plain = Buffer.from(JSON.stringify({ version: MIGRATE_VERSION, createdAt: now.toISOString(), files: entries }), 'utf8');\r\n const data = Buffer.concat([cipher.update(plain), cipher.final()]);\r\n const tag = cipher.getAuthTag();\r\n return JSON.stringify({\r\n magic: MIGRATE_MAGIC,\r\n kdf: { name: 'scrypt', salt: salt.toString('hex'), N: KDF_N },\r\n iv: iv.toString('hex'),\r\n tag: tag.toString('hex'),\r\n data: data.toString('base64'),\r\n }, null, 1) + '\\n';\r\n}\r\n\r\n/** Parse + decrypt a container. Throws (loudly) on a wrong passphrase or a\r\n * corrupted file — never returns partial data. */\r\nexport function decryptContainer(text: string, passphrase: string): { createdAt: string; files: MigrateEntry[] } {\r\n let outer: { magic?: string; kdf?: { salt?: string; N?: unknown }; iv?: string; tag?: string; data?: string };\r\n try {\r\n outer = JSON.parse(text);\r\n } catch {\r\n throw new Error('migrate: not a migration container (bad JSON)');\r\n }\r\n if (outer.magic !== MIGRATE_MAGIC) throw new Error('migrate: not a migration container (bad magic)');\r\n const n = assertKdfN(outer.kdf?.N);\r\n const salt = Buffer.from(String(outer.kdf?.salt ?? ''), 'hex');\r\n const iv = Buffer.from(String(outer.iv ?? ''), 'hex');\r\n const tag = Buffer.from(String(outer.tag ?? ''), 'hex');\r\n const data = Buffer.from(String(outer.data ?? ''), 'base64');\r\n const key = deriveKey(passphrase, salt, n);\r\n const decipher = createDecipheriv('aes-256-gcm', key, iv);\r\n decipher.setAuthTag(tag);\r\n let plain: Buffer;\r\n try {\r\n plain = Buffer.concat([decipher.update(data), decipher.final()]);\r\n } catch {\r\n throw new Error('migrate: wrong passphrase or corrupted container');\r\n }\r\n const inner = JSON.parse(plain.toString('utf8')) as { version: number; createdAt: string; files: MigrateEntry[] };\r\n if (inner.version !== MIGRATE_VERSION) throw new Error(`migrate: unsupported container version ${inner.version}`);\r\n if (!Array.isArray(inner.files)) throw new Error('migrate: container has no file list');\r\n return { createdAt: inner.createdAt, files: inner.files };\r\n}\r\n\r\nexport interface MigrateImportResult {\r\n restored: string[];\r\n backedUp: string[];\r\n skipped: string[];\r\n /** H-21: files put back from their backups after a failed commit phase. */\r\n rolledBack?: string[];\r\n /** H-21: set when the commit phase failed and the import was rolled back. */\r\n error?: string;\r\n}\r\n\r\n/**\r\n * Restore entries into the local dataDir, re-encrypting DPAPI files with the\r\n * LOCAL machine key.\r\n *\r\n * H-21: this used to be a per-file \"back up, then overwrite\" loop with no\r\n * pre-flight and no rollback — a failure halfway through left a mixed memory\r\n * (a new `profile.json` next to a stale `profile_journal.jsonl`). Now it runs\r\n * in two phases: stage every entry next to its target (nothing visible changes\r\n * yet, and a staging failure only skips that entry), then commit each one by\r\n * rename. A commit failure rolls the already-committed files back from their\r\n * backups, so the import is all-or-nothing apart from entries that never\r\n * staged.\r\n */\r\nexport function applyMigrationEntries(guard: PathGuard, paths: WorkspacePaths, files: MigrateEntry[], now = Date.now()): MigrateImportResult {\r\n const restored: string[] = [];\r\n const backedUp: string[] = [];\r\n const skipped: string[] = [];\r\n const stamp = new Date(now).toISOString().replace(/[-:T]/g, '').slice(0, 14);\r\n const staged: Array<{ rel: string; abs: string; incoming: string }> = [];\r\n for (const f of files) {\r\n const rel = String(f.path ?? '');\r\n // String-level rejection BEFORE the path is resolved: the guard's realpath\r\n // check already refuses escapes, but an import must not depend on another\r\n // module staying correct.\r\n if (!rel || rel.includes('..') || path.isAbsolute(rel)) {\r\n skipped.push(rel);\r\n continue;\r\n }\r\n const abs = path.join(paths.dataDir, rel);\r\n let absCanon: string;\r\n try {\r\n absCanon = guard.assert(abs);\r\n } catch {\r\n skipped.push(rel); // path refused by the guard — never write it\r\n continue;\r\n }\r\n const incoming = `${absCanon}.incoming-${stamp}`;\r\n try {\r\n fs.mkdirSync(path.dirname(absCanon), { recursive: true });\r\n if (f.encrypted) saveEncryptedText(guard, incoming, f.content);\r\n else fs.writeFileSync(incoming, f.content, 'utf8');\r\n staged.push({ rel, abs: absCanon, incoming });\r\n } catch {\r\n skipped.push(rel);\r\n try {\r\n fs.rmSync(incoming, { force: true });\r\n } catch {\r\n /* nothing staged */\r\n }\r\n }\r\n }\r\n const committed: Array<{ rel: string; abs: string; backup: string | null }> = [];\r\n try {\r\n for (const s of staged) {\r\n let backup: string | null = null;\r\n if (fs.existsSync(s.abs)) {\r\n backup = `${s.abs}.bak-migrate-${stamp}`;\r\n fs.copyFileSync(s.abs, backup);\r\n backedUp.push(s.rel);\r\n }\r\n fs.renameSync(s.incoming, s.abs);\r\n committed.push({ rel: s.rel, abs: s.abs, backup });\r\n restored.push(s.rel);\r\n }\r\n return { restored, backedUp, skipped };\r\n } catch (e) {\r\n // Roll back so the dataDir never holds a half-migrated mix; staged files\r\n // that were never committed are simply dropped.\r\n const rolledBack: string[] = [];\r\n for (const c of committed.reverse()) {\r\n try {\r\n if (c.backup) fs.renameSync(c.backup, c.abs);\r\n else fs.rmSync(c.abs, { force: true });\r\n rolledBack.push(c.rel);\r\n } catch {\r\n /* best effort — the backup file itself is still on disk */\r\n }\r\n }\r\n for (const s of staged) {\r\n if (committed.some((c) => c.abs === s.abs)) continue;\r\n try {\r\n fs.rmSync(s.incoming, { force: true });\r\n } catch {\r\n /* already gone */\r\n }\r\n }\r\n return { restored: [], backedUp, skipped, rolledBack, error: String(e) };\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/path-guard.ts","../src/config/schema.ts","../src/config/load.ts","../src/core/preset-install.ts"],"sourcesContent":["// Path whitelist guard (requirement 8 / design doc §10.2).\n//\n// Every fs write/delete must have its target canonicalized first, then be\n// checked against the canonical workspace prefix with a separator boundary.\n// A raw startsWith check is bypassable via \"..\", symlinks/junctions, and\n// case variants; canonical realpath closes all of those on Windows.\n\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nexport class PathOutsideWorkspaceError extends Error {\n constructor(target: string, workspace: string) {\n super(`path outside workspace: \"${target}\" (workspace: \"${workspace}\")`);\n this.name = 'PathOutsideWorkspaceError';\n }\n}\n\n/**\n * Resolve to an absolute canonical path. For targets that do not exist yet,\n * realpath the deepest existing ancestor and re-append the virtual tail, so\n * planned files under the workspace validate while `..` escapes still resolve\n * through the real filesystem.\n */\nexport function canonicalize(target: string): string {\n const abs = path.resolve(target);\n try {\n return fs.realpathSync(abs);\n } catch {\n // Walk up from the missing leaf, recording each missing component, until\n // an existing ancestor is found; re-append the missing tail afterwards.\n const tail: string[] = [];\n let dir = abs;\n for (;;) {\n const base = path.basename(dir);\n const parent = path.dirname(dir);\n if (parent === dir) {\n throw new Error(`cannot canonicalize \"${target}\": no existing ancestor`);\n }\n tail.push(base);\n dir = parent;\n try {\n const realDir = fs.realpathSync(dir);\n return path.join(realDir, ...tail.reverse());\n } catch {\n continue;\n }\n }\n }\n}\n\n/**\n * True when `targetCanon` equals the workspace dir or lies under it.\n * Comparison is case-insensitive (Windows filesystems) and requires a\n * separator boundary so `D:\\ws-data-evil` does not match workspace `D:\\ws-data`.\n */\nexport function isInsideWorkspace(workspaceCanon: string, targetCanon: string): boolean {\n const norm = (p: string) => {\n let n = path.normalize(p).toLowerCase();\n if (!n.endsWith(path.sep)) n += path.sep;\n return n;\n };\n const w = norm(workspaceCanon);\n const t = norm(targetCanon);\n return t === w || t.startsWith(w);\n}\n\nexport interface PathGuard {\n /** Canonical workspace boundary. */\n readonly workspace: string;\n /** Canonicalize then validate; returns the canonical path or throws. */\n assert(target: string): string;\n /** Canonicalize then validate; returns null instead of throwing. */\n check(target: string): string | null;\n}\n\nexport function createPathGuard(workspaceDir: string): PathGuard {\n const workspace = canonicalize(workspaceDir);\n const guard: PathGuard = {\n workspace,\n check(target: string): string | null {\n const canon = canonicalize(target);\n return isInsideWorkspace(workspace, canon) ? canon : null;\n },\n assert(target: string): string {\n const canon = guard.check(target);\n if (canon === null) throw new PathOutsideWorkspaceError(target, workspace);\n return canon;\n },\n };\n return guard;\n}\n","// Policy shape + runtime validation. Factory defaults live in config/policy.json\r\n// (read-only); the user layer in data/settings/policy.json overrides via deep\r\n// merge (design doc §13, D3).\r\n\r\nexport interface QuietHours {\r\n start: string; // \"HH:MM\"\r\n end: string; // \"HH:MM\"\r\n}\r\n\r\nexport interface BrowseWindow {\r\n start: string;\r\n end: string;\r\n}\r\n\r\nexport interface Policy {\r\n heartbeat: { intervalMin: number; idleMode: boolean; archiveRotatedHome: boolean };\r\n gate: {\r\n maxDailySend: number;\r\n cooldownMinutes: number;\r\n quietHours: QuietHours;\r\n };\r\n browse: {\r\n windows: BrowseWindow[];\r\n minIntervalHours: number;\r\n maxSeedsPerVisit: number;\r\n };\r\n seeds: {\r\n maxActive: number;\r\n ttlDays: { news: number; fandom: number; scene: number; promise: number };\r\n coldBenchDays: number;\r\n /** Archive-area cap (v1.9.0): gc trims archived seeds beyond this, oldest first. */\r\n archiveCap: number;\r\n retireAfterUsed: number;\r\n scoreWeights: { freshness: number; unused: number; confidence: number };\r\n };\r\n profile: {\r\n consolidation: { minIntervalHours: number; inboxBacklog: number };\r\n partitionCap: number;\r\n maxOpsPerRun: number;\r\n confidenceCap: { chat: number; screen: number; browse: number };\r\n volatileDays: number;\r\n stableLowActivityDays: number;\r\n psyEnabled: boolean;\r\n };\r\n /** Observation depth (v1.8.0): how much of each user message enters the inbox. */\r\n observe: { maxChars: number; perBeat: number };\r\n weekly: { enabled: boolean };\r\n retention: { envPulseHours: number; decisionLogDays: number };\r\n}\r\n\r\nconst HHMM = /^([01]\\d|2[0-3]):[0-5]\\d$/;\r\n\r\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\r\n return typeof v === 'object' && v !== null && !Array.isArray(v);\r\n}\r\n\r\nfunction fail(msg: string): never {\r\n throw new Error(`policy: ${msg}`);\r\n}\r\n\r\nexport function assertPolicy(input: unknown): asserts input is Policy {\r\n if (!isPlainObject(input)) fail('root must be an object');\r\n const p = input;\r\n const hb = p.heartbeat;\r\n if (!isPlainObject(hb)) fail('heartbeat missing');\r\n if (typeof hb.intervalMin !== 'number' || hb.intervalMin < 1 || hb.intervalMin > 1440) {\r\n fail('heartbeat.intervalMin must be a number in [1, 1440]');\r\n }\r\n if (typeof hb.idleMode !== 'boolean') fail('heartbeat.idleMode must be boolean');\r\n if (typeof hb.archiveRotatedHome !== 'boolean') fail('heartbeat.archiveRotatedHome must be boolean');\r\n const g = p.gate;\r\n if (!isPlainObject(g)) fail('gate missing');\r\n if (typeof g.maxDailySend !== 'number' || g.maxDailySend < 0) fail('gate.maxDailySend must be >= 0');\r\n if (typeof g.cooldownMinutes !== 'number' || g.cooldownMinutes < 0) fail('gate.cooldownMinutes must be >= 0');\r\n const qh = g.quietHours;\r\n if (!isPlainObject(qh)) fail('gate.quietHours missing');\r\n if (typeof qh.start !== 'string' || !HHMM.test(qh.start) || typeof qh.end !== 'string' || !HHMM.test(qh.end)) {\r\n fail('gate.quietHours must be {start:\"HH:MM\", end:\"HH:MM\"}');\r\n }\r\n const b = p.browse;\r\n if (!isPlainObject(b)) fail('browse missing');\r\n if (!Array.isArray(b.windows) || b.windows.length === 0) fail('browse.windows must be a non-empty array');\r\n for (const w of b.windows) {\r\n if (!isPlainObject(w)) fail('browse.windows entries must be objects');\r\n if (typeof w.start !== 'string' || !HHMM.test(w.start) || typeof w.end !== 'string' || !HHMM.test(w.end)) {\r\n fail('browse.windows entries must be {start:\"HH:MM\", end:\"HH:MM\"}');\r\n }\r\n }\r\n if (typeof b.minIntervalHours !== 'number' || b.minIntervalHours <= 0) fail('browse.minIntervalHours must be > 0');\r\n if (typeof b.maxSeedsPerVisit !== 'number' || b.maxSeedsPerVisit < 1) fail('browse.maxSeedsPerVisit must be >= 1');\r\n const s = p.seeds;\r\n if (!isPlainObject(s)) fail('seeds missing');\r\n if (typeof s.maxActive !== 'number' || s.maxActive < 1) fail('seeds.maxActive must be >= 1');\r\n if (!isPlainObject(s.ttlDays)) fail('seeds.ttlDays missing');\r\n for (const k of ['news', 'fandom', 'scene', 'promise'] as const) {\r\n if (typeof s.ttlDays[k] !== 'number') fail(`seeds.ttlDays.${k} missing`);\r\n }\r\n if (typeof s.coldBenchDays !== 'number') fail('seeds.coldBenchDays missing');\r\n if (typeof s.archiveCap !== 'number' || s.archiveCap < 1) fail('seeds.archiveCap must be >= 1');\r\n if (typeof s.retireAfterUsed !== 'number' || s.retireAfterUsed < 1) fail('seeds.retireAfterUsed must be >= 1');\r\n if (!isPlainObject(s.scoreWeights)) fail('seeds.scoreWeights missing');\r\n const pr = p.profile;\r\n if (!isPlainObject(pr)) fail('profile missing');\r\n const c = pr.consolidation;\r\n if (!isPlainObject(c)) fail('profile.consolidation missing');\r\n if (typeof c.minIntervalHours !== 'number' || typeof c.inboxBacklog !== 'number') fail('profile.consolidation fields missing');\r\n if (typeof pr.partitionCap !== 'number' || pr.partitionCap < 1) fail('profile.partitionCap must be >= 1');\r\n if (typeof pr.maxOpsPerRun !== 'number' || pr.maxOpsPerRun < 1) fail('profile.maxOpsPerRun must be >= 1');\r\n const cc = pr.confidenceCap;\r\n if (!isPlainObject(cc)) fail('profile.confidenceCap missing');\r\n if (typeof cc.chat !== 'number' || typeof cc.screen !== 'number' || typeof cc.browse !== 'number') {\r\n fail('profile.confidenceCap fields missing');\r\n }\r\n if (typeof pr.volatileDays !== 'number' || pr.volatileDays < 1) fail('profile.volatileDays must be >= 1');\r\n if (typeof pr.stableLowActivityDays !== 'number' || pr.stableLowActivityDays < 1) fail('profile.stableLowActivityDays must be >= 1');\r\n if (typeof pr.psyEnabled !== 'boolean') fail('profile.psyEnabled must be boolean');\r\n const ob = p.observe;\r\n if (!isPlainObject(ob)) fail('observe missing');\r\n if (typeof ob.maxChars !== 'number' || ob.maxChars < 20 || ob.maxChars > 2000) fail('observe.maxChars must be in [20, 2000]');\r\n if (typeof ob.perBeat !== 'number' || ob.perBeat < 1 || ob.perBeat > 100) fail('observe.perBeat must be in [1, 100]');\r\n if (!isPlainObject(p.weekly)) fail('weekly missing');\r\n if (typeof p.weekly.enabled !== 'boolean') fail('weekly.enabled must be boolean');\r\n const r = p.retention;\r\n if (!isPlainObject(r)) fail('retention missing');\r\n if (typeof r.envPulseHours !== 'number' || typeof r.decisionLogDays !== 'number') fail('retention fields missing');\r\n}\r\n\r\n/** Recursive merge: user values win; objects merge, arrays and scalars replace. */\r\nexport function deepMerge<T>(base: T, override: unknown): T {\r\n if (!isPlainObject(base) || !isPlainObject(override)) {\r\n return (override === undefined ? base : (override as T));\r\n }\r\n const out: Record<string, unknown> = { ...base };\r\n for (const [k, v] of Object.entries(override)) {\r\n out[k] = v === undefined ? (base as Record<string, unknown>)[k] : deepMerge((base as Record<string, unknown>)[k], v);\r\n }\r\n return out as T;\r\n}","// Two-layer policy loading (D3): factory defaults (config/policy.json, read-only)\n// + user layer (data/settings/policy.json). Missing user layer is normal; an\n// invalid factory file or user layer is fail-closed (throw at startup).\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { PathGuard } from '../core/path-guard.js';\nimport { assertPolicy, deepMerge, type Policy } from './schema.js';\nimport { atomicWriteFileSync } from '../core/atomic-fs.js';\n\nexport const USER_POLICY_FILE = 'policy.json';\n\nexport function loadPolicy(guard: PathGuard, configDir: string, settingsDir: string): Policy {\n const factoryPath = path.join(configDir, 'policy.json');\n let factoryRaw: unknown;\n try {\n factoryRaw = JSON.parse(fs.readFileSync(factoryPath, 'utf8'));\n } catch (e) {\n throw new Error(`factory policy unreadable at ${factoryPath}: ${String(e)}`);\n }\n assertPolicy(factoryRaw);\n\n const userPath = guard.assert(path.join(settingsDir, USER_POLICY_FILE));\n let merged: Policy = factoryRaw;\n if (fs.existsSync(userPath)) {\n try {\n const userRaw: unknown = JSON.parse(fs.readFileSync(userPath, 'utf8'));\n merged = deepMerge(factoryRaw, userRaw);\n } catch (e) {\n throw new Error(`user policy layer unparseable at ${userPath}: ${String(e)}`);\n }\n }\n assertPolicy(merged);\n return merged;\n}\n\n/**\n * Merge `patch` into the user policy layer (data/settings/policy.json) without\n * touching the factory file. Used by the settings card for policy-backed\n * toggles (psyEnabled, v1.7.0). No full-Policy validation here: the layer is\n * merged over the factory and validated together at the next loadPolicy.\n */\nexport function updateUserPolicy(guard: PathGuard, settingsDir: string, patch: Record<string, unknown>): Record<string, unknown> {\n const userPath = guard.assert(path.join(settingsDir, USER_POLICY_FILE));\n let user: Record<string, unknown> = {};\n try {\n user = JSON.parse(fs.readFileSync(userPath, 'utf8')) as Record<string, unknown>;\n if (!user || typeof user !== 'object' || Array.isArray(user)) user = {};\n } catch {\n user = {}; // fresh layer\n }\n const merged = deepMerge(user, patch) as Record<string, unknown>;\n fs.mkdirSync(path.dirname(userPath), { recursive: true });\n atomicWriteFileSync(userPath, JSON.stringify(merged, null, 2) + '\\n');\n return merged;\n}\n","// Bundled agent-preset installation (contract C13).\n//\n// Why this exists: the heartbeat agent is created by the host `agents` service,\n// which does NOT go through the session-start preset picker. A bare agent joins\n// no preset, and dsh-agent-presets states the consequence verbatim — \"its tools,\n// prompt sections, and skill catalog resolve against the empty global layer\" —\n// so it cannot even see `web_search`. The preset is therefore not optional, and\n// asking the operator to hand-copy two YAML files was the most error-prone step\n// of setup. The plugin now materialises its own bundled template in the roster's\n// USER root on first run.\n//\n// Two properties keep that safe:\n// 1. An existing preset is NEVER overwritten — the composition file belongs to\n// whoever edited it. Only a missing (or ghost) directory is filled in.\n// 2. The target comes from the roster's OWN roots (`agentPresets.roots`,\n// trust === \"user\") rather than a guessed `~/.dsh`, so `$DSH_HOME` and a\n// configured home are honoured without re-deriving them here.\n//\n// Timing: dsh-agent-presets re-scans the filesystem on every read\n// (`list()` -> `discoverPresets` -> `scanRoot`, no cache), so a directory\n// created here is visible to the very next `mount()` — no restart in between.\n\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport const COMPOSITION_FILE = 'agent.cordis.yml';\nexport const METADATA_FILE = 'preset.yml';\n/** The template this package ships, in `assets/presets/<id>/`. */\nexport const BUNDLED_PRESET_ID = 'heartbeat';\n\n/** One entry of the roster's root list (`AgentPresets.roots`). */\nexport interface PresetRootLike {\n path?: string;\n trust?: string;\n}\n\nexport type PresetInstallAction =\n | 'exists'\n | 'created'\n | 'repaired'\n | 'restored'\n | 'skipped-disabled'\n | 'skipped-custom-id'\n | 'skipped-no-root'\n | 'error';\n\nexport interface PresetInstallResult {\n action: PresetInstallAction;\n id: string;\n /** Where the preset lives (or would live). */\n dir?: string;\n /** Where the bundled template was read from. */\n bundledDir?: string;\n detail?: string;\n}\n\n/**\n * Locate the bundled template directory by walking up from a module URL.\n * Works from both entry points (`dist/index.js` and `dist/cli/index.js`) and\n * from the pnpm copy of an installed package.\n */\nexport function bundledPresetDir(\n moduleUrl: string,\n id: string = BUNDLED_PRESET_ID,\n): string | undefined {\n let dir: string;\n try {\n dir = path.dirname(fileURLToPath(moduleUrl));\n } catch {\n return undefined;\n }\n for (let depth = 0; depth < 5; depth += 1) {\n const candidate = path.join(dir, 'assets', 'presets', id);\n if (fs.existsSync(path.join(candidate, COMPOSITION_FILE))) return candidate;\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return undefined;\n}\n\n/** The roster's user-trust root, which is where locally authored presets live. */\nexport function userPresetRoot(roots?: readonly PresetRootLike[]): string | undefined {\n const found = roots?.find(\n (root) => root?.trust === 'user' && typeof root.path === 'string' && root.path.length > 0,\n );\n return found?.path === undefined ? undefined : path.resolve(found.path);\n}\n\n/**\n * `<dshHome>/.agent-presets` derived from the documented precedence\n * (`$DSH_HOME`, then `~/.dsh`). Only used where no roster is available — the\n * plugin itself prefers {@link userPresetRoot}.\n */\nexport function conventionalUserPresetRoot(\n env: NodeJS.ProcessEnv = process.env,\n home: string = os.homedir(),\n): string {\n const override = env.DSH_HOME?.trim();\n const root = override && override.length > 0 ? override : path.join(home, '.dsh');\n return path.join(root, '.agent-presets');\n}\n\nexport interface InstallPresetOptions {\n /** `import.meta.url` of the calling entry point. */\n moduleUrl: string;\n /** Preset id the heartbeat agent will join (config `agentPreset`). */\n id?: string;\n /** User-trust root reported by the roster. */\n root?: string;\n /** True when the roster answered — then an absent user root is a fact, not a guess. */\n rosterKnown?: boolean;\n /** `false` disables the install (config `installPreset`). */\n enabled?: boolean;\n /** Overwrite an existing composition file with the bundled one. */\n force?: boolean;\n}\n\n/**\n * Materialise the bundled preset in the roster's user root when it is absent.\n * Never destructive: an existing composition file is kept unless `force`.\n */\nexport function installBundledPreset(options: InstallPresetOptions): PresetInstallResult {\n const id = options.id && options.id.length > 0 ? options.id : BUNDLED_PRESET_ID;\n if (options.enabled === false) {\n return { action: 'skipped-disabled', id, detail: 'installPreset=false' };\n }\n const bundledDir = bundledPresetDir(options.moduleUrl, BUNDLED_PRESET_ID);\n if (id !== BUNDLED_PRESET_ID) {\n return {\n action: 'skipped-custom-id',\n id,\n ...(bundledDir === undefined ? {} : { bundledDir }),\n detail: `only \"${BUNDLED_PRESET_ID}\" ships with the plugin; \"${id}\" is yours to provide`,\n };\n }\n if (bundledDir === undefined) {\n return {\n action: 'error',\n id,\n detail: 'bundled template not found next to the plugin (assets/presets/heartbeat)',\n };\n }\n const root =\n options.root ?? (options.rosterKnown ? undefined : conventionalUserPresetRoot());\n if (root === undefined) {\n return {\n action: 'skipped-no-root',\n id,\n bundledDir,\n detail: 'the roster mounts no user preset root (includeUserRoot=false)',\n };\n }\n const dir = path.join(root, id);\n const composition = path.join(dir, COMPOSITION_FILE);\n try {\n if (fs.existsSync(composition)) {\n if (options.force !== true) {\n const drifted = !sameBytes(composition, path.join(bundledDir, COMPOSITION_FILE));\n return {\n action: 'exists',\n id,\n dir,\n bundledDir,\n detail: drifted ? 'kept as-is (differs from the bundled template)' : 'kept as-is',\n };\n }\n fs.copyFileSync(path.join(bundledDir, COMPOSITION_FILE), composition);\n return {\n action: 'restored',\n id,\n dir,\n bundledDir,\n detail: 'composition replaced from the bundled template',\n };\n }\n const existed = fs.existsSync(dir);\n fs.mkdirSync(dir, { recursive: true });\n fs.copyFileSync(path.join(bundledDir, COMPOSITION_FILE), composition);\n const metadata = path.join(dir, METADATA_FILE);\n // A directory with a composition but no metadata is legal; only fill a void.\n if (!fs.existsSync(metadata)) fs.copyFileSync(path.join(bundledDir, METADATA_FILE), metadata);\n return {\n action: existed ? 'repaired' : 'created',\n id,\n dir,\n bundledDir,\n detail: existed\n ? 'directory existed without a composition file (it occupied the id as a broken row)'\n : undefined,\n };\n } catch (error) {\n return { action: 'error', id, dir, bundledDir, detail: String(error).slice(0, 200) };\n }\n}\n\n/** One line suitable for the audit log and `ctx.logger`. */\nexport function describeInstall(result: PresetInstallResult): string {\n const where = result.dir === undefined ? '' : ` (${result.dir})`;\n const why = result.detail === undefined ? '' : ` — ${result.detail}`;\n return `preset ${result.id} ${result.action}${where}${why}`;\n}\n\nexport interface PresetStatus {\n id: string;\n dir: string;\n bundledDir?: string;\n installed: boolean;\n compositionMatches: boolean;\n metadataMatches: boolean;\n}\n\n/** Read-only inspection for the CLI (`preset status`). */\nexport function presetStatus(\n moduleUrl: string,\n id: string = BUNDLED_PRESET_ID,\n root: string = conventionalUserPresetRoot(),\n): PresetStatus {\n const dir = path.join(root, id);\n const bundledDir = bundledPresetDir(moduleUrl, id);\n const installed = fs.existsSync(path.join(dir, COMPOSITION_FILE));\n return {\n id,\n dir,\n ...(bundledDir === undefined ? {} : { bundledDir }),\n installed,\n compositionMatches:\n installed && bundledDir !== undefined && sameBytes(path.join(dir, COMPOSITION_FILE), path.join(bundledDir, COMPOSITION_FILE)),\n metadataMatches:\n bundledDir !== undefined &&\n fs.existsSync(path.join(dir, METADATA_FILE)) &&\n sameBytes(path.join(dir, METADATA_FILE), path.join(bundledDir, METADATA_FILE)),\n };\n}\n\nfunction sameBytes(left: string, right: string): boolean {\n try {\n return fs.readFileSync(left).equals(fs.readFileSync(right));\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["fs","path","path","fs","fs","path"]}
@@ -1,21 +0,0 @@
1
- import {
2
- MIGRATE_FILES,
3
- MIGRATE_MAGIC,
4
- MIGRATE_VERSION,
5
- applyMigrationEntries,
6
- collectMigrationEntries,
7
- decryptContainer,
8
- encryptContainer
9
- } from "./chunk-XFNCTOW7.js";
10
- import "./chunk-K5Y6JP2B.js";
11
- import "./chunk-WRUTATW4.js";
12
- export {
13
- MIGRATE_FILES,
14
- MIGRATE_MAGIC,
15
- MIGRATE_VERSION,
16
- applyMigrationEntries,
17
- collectMigrationEntries,
18
- decryptContainer,
19
- encryptContainer
20
- };
21
- //# sourceMappingURL=migrate-D4VKAKYT.js.map