@kanadego/dsh-heartbeat 1.9.0 → 1.9.2

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 (59) hide show
  1. package/README.md +58 -71
  2. package/assets/frontwin.ps1 +1 -0
  3. package/assets/idle.ps1 +1 -0
  4. package/assets/screenpulse.ps1 +4 -2
  5. package/config/profile-schema.json +306 -306
  6. package/cordis.patch.yml +18 -39
  7. package/dist/{bindings-XPPSKILN.js → bindings-225SI7KC.js} +3 -2
  8. package/dist/{chunk-63FUJLIF.js → chunk-3MDBU6WY.js} +17 -2
  9. package/dist/chunk-3MDBU6WY.js.map +1 -0
  10. package/dist/chunk-AJ5HLAL7.js +2477 -0
  11. package/dist/chunk-AJ5HLAL7.js.map +1 -0
  12. package/dist/{chunk-VIZNIQLK.js → chunk-CUOSICYJ.js} +82 -74
  13. package/dist/chunk-CUOSICYJ.js.map +1 -0
  14. package/dist/chunk-IG6YMUHW.js +569 -0
  15. package/dist/chunk-IG6YMUHW.js.map +1 -0
  16. package/dist/chunk-IV2ZWQA3.js +56 -0
  17. package/dist/chunk-IV2ZWQA3.js.map +1 -0
  18. package/dist/{chunk-PQTSXW4F.js → chunk-JDH2LDFS.js} +97 -71
  19. package/dist/chunk-JDH2LDFS.js.map +1 -0
  20. package/dist/chunk-K5Y6JP2B.js +399 -0
  21. package/dist/chunk-K5Y6JP2B.js.map +1 -0
  22. package/dist/{chunk-RB3NLLGF.js → chunk-KB5SMG3F.js} +3 -3
  23. package/dist/{chunk-J6ZTRFFW.js → chunk-TY6UP74X.js} +6 -2
  24. package/dist/chunk-TY6UP74X.js.map +1 -0
  25. package/dist/chunk-WRUTATW4.js +47 -0
  26. package/dist/chunk-WRUTATW4.js.map +1 -0
  27. package/dist/{chunk-IPOZFZPY.js → chunk-XFNCTOW7.js} +48 -10
  28. package/dist/chunk-XFNCTOW7.js.map +1 -0
  29. package/dist/chunk-ZLV4NMFX.js +333 -0
  30. package/dist/chunk-ZLV4NMFX.js.map +1 -0
  31. package/dist/cli/index.d.ts +3 -0
  32. package/dist/cli/index.js +565 -497
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/{inbox-57NHQHY3.js → inbox-P26DDMWC.js} +5 -3
  35. package/dist/index.d.ts +550 -0
  36. package/dist/index.js +665 -2730
  37. package/dist/index.js.map +1 -1
  38. package/dist/{migrate-N6NXMZ3G.js → migrate-D4VKAKYT.js} +4 -3
  39. package/dist/orchestrator-EUTZ3KG6.js +62 -0
  40. package/dist/{pool-7I4VACG2.js → pool-UTXQJJ5C.js} +5 -3
  41. package/dist/{snapshot-ZBCSX67Y.js → snapshot-K5VHETTK.js} +5 -4
  42. package/dist/snapshot-K5VHETTK.js.map +1 -0
  43. package/package.json +83 -76
  44. package/config/watchlist.json +0 -9
  45. package/dist/chunk-2SYXKKZF.js +0 -837
  46. package/dist/chunk-2SYXKKZF.js.map +0 -1
  47. package/dist/chunk-63FUJLIF.js.map +0 -1
  48. package/dist/chunk-IFTFDHZX.js +0 -203
  49. package/dist/chunk-IFTFDHZX.js.map +0 -1
  50. package/dist/chunk-IPOZFZPY.js.map +0 -1
  51. package/dist/chunk-J6ZTRFFW.js.map +0 -1
  52. package/dist/chunk-PQTSXW4F.js.map +0 -1
  53. package/dist/chunk-VIZNIQLK.js.map +0 -1
  54. /package/dist/{bindings-XPPSKILN.js.map → bindings-225SI7KC.js.map} +0 -0
  55. /package/dist/{chunk-RB3NLLGF.js.map → chunk-KB5SMG3F.js.map} +0 -0
  56. /package/dist/{inbox-57NHQHY3.js.map → inbox-P26DDMWC.js.map} +0 -0
  57. /package/dist/{migrate-N6NXMZ3G.js.map → migrate-D4VKAKYT.js.map} +0 -0
  58. /package/dist/{pool-7I4VACG2.js.map → orchestrator-EUTZ3KG6.js.map} +0 -0
  59. /package/dist/{snapshot-ZBCSX67Y.js.map → pool-UTXQJJ5C.js.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -1,28 +1,34 @@
1
+ import {
2
+ applyMigrationEntries,
3
+ collectMigrationEntries,
4
+ decryptContainer,
5
+ encryptContainer
6
+ } from "../chunk-XFNCTOW7.js";
1
7
  import {
2
8
  BUNDLED_PRESET_ID,
9
+ conventionalUserPresetRoot,
10
+ createPathGuard,
11
+ describeInstall,
12
+ installBundledPreset,
13
+ loadPolicy,
14
+ presetStatus
15
+ } from "../chunk-ZLV4NMFX.js";
16
+ import {
3
17
  adviseWander,
4
18
  appendEntry,
5
19
  browseStatus,
6
- checkWatchlist,
7
20
  completeWander,
8
- conventionalUserPresetRoot,
9
- createPathGuard,
10
- describeInstall,
11
21
  ensureRegistered,
12
- installBundledPreset,
13
22
  ledgerFilePath,
14
23
  listWeeklyReports,
15
24
  loadInterests,
16
- loadPolicy,
17
- loadWatchlist,
18
25
  markDone,
19
- presetStatus,
20
26
  readLedger,
21
27
  readWeeklyReport,
22
28
  scanPending,
23
29
  sendNewMessageHint,
24
30
  weeklyDirPath
25
- } from "../chunk-2SYXKKZF.js";
31
+ } from "../chunk-IG6YMUHW.js";
26
32
  import {
27
33
  activeSeeds,
28
34
  addSeed,
@@ -32,27 +38,24 @@ import {
32
38
  loadPool,
33
39
  seedsFilePath,
34
40
  surfaceSeed
35
- } from "../chunk-PQTSXW4F.js";
41
+ } from "../chunk-JDH2LDFS.js";
42
+ import "../chunk-IV2ZWQA3.js";
36
43
  import {
37
44
  appendAuditLine,
38
45
  loadProfile,
39
46
  profileFilePath,
40
47
  pruneAuditFile,
41
48
  rebuildProfile,
42
- shredFileSync,
43
49
  verifyProfile
44
- } from "../chunk-VIZNIQLK.js";
45
- import {
46
- applyMigrationEntries,
47
- collectMigrationEntries,
48
- decryptContainer,
49
- encryptContainer
50
- } from "../chunk-IPOZFZPY.js";
50
+ } from "../chunk-CUOSICYJ.js";
51
51
  import {
52
52
  initWorkspace,
53
- loadEncryptedText,
54
53
  writeText
55
- } from "../chunk-IFTFDHZX.js";
54
+ } from "../chunk-K5Y6JP2B.js";
55
+ import {
56
+ atomicWriteFileSync,
57
+ shredFileSync
58
+ } from "../chunk-WRUTATW4.js";
56
59
 
57
60
  // src/cli/index.ts
58
61
  import { spawn } from "child_process";
@@ -67,6 +70,9 @@ var BURN_LIST = [
67
70
  { file: "profile.json", note: "\u753B\u50CF\u7269\u5316\u89C6\u56FE" },
68
71
  { file: "profile_inbox.jsonl", note: "\u89C2\u5BDF\u6536\u4EF6\u7BB1" },
69
72
  { file: "profile_journal.jsonl", note: "\u753B\u50CF\u64CD\u4F5C\u6D41\u6C34" },
73
+ // H-19: the rotation shards hold the whole profile history and are written
74
+ // in PLAINTEXT — a burn that leaves them behind leaves the memory readable.
75
+ { prefix: "profile_journal.archive-", note: "\u753B\u50CF\u6D41\u6C34\u5F52\u6863\u5206\u7247\uFF08\u660E\u6587\uFF1Bjournal \u8F6E\u8F6C\u4EA7\u7269\uFF09" },
70
76
  { file: "profile_rhythm.json", note: "\u4F5C\u606F\u805A\u5408\uFF08\u7EAF\u7EDF\u8BA1\uFF09" },
71
77
  { file: "screen.json", note: "\u5C4F\u5E55\u5FEB\u7167\uFF08\u52A0\u5BC6\uFF09" },
72
78
  { file: "screen.jpg", note: "\u622A\u56FE\uFF08\u52A0\u5BC6\uFF09" },
@@ -76,22 +82,40 @@ var BURN_LIST = [
76
82
  { file: "ledger.md", note: "\u8D26\u672C\uFF08\u660E\u6587\uFF0C\u4EBA\u53EF\u8BFB\u662F\u8BBE\u8BA1\u76EE\u6807\uFF09" },
77
83
  { file: "envpulse.json", note: "\u73AF\u5883\u6E29\u5EA6\u8BA1\u5FEB\u7167" },
78
84
  { file: "gate.json", note: "\u95F8\u95E8\u8FD0\u884C\u65F6\u8BA1\u6570" },
85
+ // v1.9.0 completion (H-19): these carry memory just as much as the rows
86
+ // above — the persona report log records WHAT SHE SAID (spoken / seed_ids /
87
+ // profile_ids / reason), so a burn that skips it is a burn that lies.
88
+ { file: "profile_snapshot.json", note: "\u753B\u50CF\u5FEB\u7167\uFF08journal \u8F6E\u8F6C\u57FA\u7EBF\uFF09" },
89
+ { file: "cursors.json", note: "\u89C2\u5BDF\u6E38\u6807\uFF08\u4F1A\u8BDD\u4E8B\u4EF6\u4F4D\u70B9\uFF09" },
90
+ { file: "seed_report.jsonl", note: "\u6295\u9012\u62A5\u8D26\u65E5\u5FD7\uFF08\u52A0\u5BC6\uFF09" },
91
+ { file: "preference.json", note: "\u8BDD\u9898\u504F\u597D\u7EDF\u8BA1" },
92
+ { dir: "weekly", note: "\u5468\u62A5\uFF08\u753B\u50CF\u4E0E\u7D20\u6750\u6563\u6587\uFF09" },
79
93
  { dir: "tmp", note: "\u89E3\u9501\u4E34\u65F6\u6587\u4EF6\uFF08\u5D29\u6E83\u6B8B\u7559\u7684\u89E3\u5BC6\u660E\u6587\u662F\u6700\u8BE5\u70E7\u7684\u4E1C\u897F\uFF09" },
80
94
  { dir: "logs", note: "\u5BA1\u8BA1\u4E0E\u51B3\u7B56\u65E5\u5FD7" },
81
95
  { dir: "exports", note: "\u5BFC\u51FA\u4EA7\u7269" }
82
96
  ];
83
97
  var SETTINGS_DIR = "settings";
98
+ function prefixMatches(guard, paths, prefix) {
99
+ try {
100
+ return fs.readdirSync(guard.assert(paths.dataDir)).filter((f) => f.startsWith(prefix)).sort();
101
+ } catch {
102
+ return [];
103
+ }
104
+ }
84
105
  function planBurn(guard, paths, all = false) {
85
106
  const plan = [];
86
107
  for (const t of BURN_LIST) {
87
- const rel = t.file ?? t.dir;
108
+ const rel = t.file ?? t.dir ?? t.prefix ?? "";
88
109
  if (!all && rel === SETTINGS_DIR) continue;
89
- const abs = path.join(paths.dataDir, rel);
90
110
  let exists = false;
91
- try {
92
- exists = fs.existsSync(guard.assert(abs));
93
- } catch {
94
- exists = false;
111
+ if (t.prefix) {
112
+ exists = prefixMatches(guard, paths, t.prefix).length > 0;
113
+ } else {
114
+ try {
115
+ exists = fs.existsSync(guard.assert(path.join(paths.dataDir, rel)));
116
+ } catch {
117
+ exists = false;
118
+ }
95
119
  }
96
120
  plan.push({ target: t, exists });
97
121
  }
@@ -105,6 +129,21 @@ function executeBurn(guard, paths, opts = {}) {
105
129
  const missing = [];
106
130
  const passes = opts.shredPasses ?? 3;
107
131
  for (const t of BURN_LIST) {
132
+ if (t.prefix) {
133
+ const matches = prefixMatches(guard, paths, t.prefix);
134
+ if (matches.length === 0) {
135
+ missing.push(`${t.prefix}*`);
136
+ continue;
137
+ }
138
+ for (const f of matches) {
139
+ try {
140
+ shredFileSync(guard.assert(path.join(paths.dataDir, f)), passes);
141
+ burned.push(f);
142
+ } catch {
143
+ }
144
+ }
145
+ continue;
146
+ }
108
147
  const rel = t.file ?? t.dir;
109
148
  const abs = path.join(paths.dataDir, rel);
110
149
  let absCanon;
@@ -159,9 +198,8 @@ function usage() {
159
198
  " ledger done <id|substring> mark an entry done",
160
199
  " ledger open open ledger.md in the default editor",
161
200
  " logs cleanup [--dry-run] apply log retention now",
162
- " browse status browse state summary (watch + wander)",
201
+ " browse status browse state summary (wander)",
163
202
  " browse dry wander adjudication with forced noon window",
164
- " browse watch run watchlist check now (network)",
165
203
  " browse done <focus> CODE-side registration after a wander visit",
166
204
  " bind list list session bindings (D13)",
167
205
  " bind add <sessionId> [--observe] bind a session (deliver by default)",
@@ -194,6 +232,13 @@ function flag(argv, name) {
194
232
  const i = argv.indexOf(name);
195
233
  return i > -1 ? argv[i + 1] : void 0;
196
234
  }
235
+ var SEED_TAGS = ["news", "fandom", "scene", "promise"];
236
+ var SEED_SOURCES = ["chat", "screen", "browse", "hand", "profile"];
237
+ function flagOneOf(argv, name, allowed) {
238
+ const v = flag(argv, name);
239
+ if (v === void 0) return void 0;
240
+ return allowed.includes(v) ? v : void 0;
241
+ }
197
242
  async function readLines(prompts) {
198
243
  const readline = await import("readline/promises");
199
244
  const it = readline.createInterface({ input: process.stdin });
@@ -202,521 +247,544 @@ async function readLines(prompts) {
202
247
  it.close();
203
248
  return out;
204
249
  }
205
- async function main(argv) {
206
- const [cmd, sub, ...rest] = argv;
207
- if (!cmd || cmd === "help" || cmd === "--help") {
208
- console.log(usage());
209
- return 0;
210
- }
211
- const paths = initWorkspace();
212
- const guard = createPathGuard(paths.dataDir);
213
- const policy = loadPolicy(guard, paths.configDir, paths.settingsDir);
214
- const seedsFile = seedsFilePath(paths.dataDir);
215
- const ledgerFile = ledgerFilePath(paths.dataDir);
216
- const decisionLog = path2.join(paths.logsDir, "heartbeat.jsonl");
217
- const seedAudit = (entry) => {
218
- try {
219
- appendAuditLine(decisionLog, entry);
220
- } catch {
221
- }
222
- };
223
- switch (cmd) {
224
- case "status": {
225
- console.log(JSON.stringify({
226
- dataDir: paths.dataDir,
227
- heartbeatIntervalMin: policy.heartbeat.intervalMin,
228
- maxDailySend: policy.gate.maxDailySend,
229
- seedsMaxActive: policy.seeds.maxActive,
230
- psyEnabled: policy.profile.psyEnabled
231
- }, null, 2));
250
+ async function verbStatus(c) {
251
+ const { paths, policy } = c;
252
+ console.log(JSON.stringify({
253
+ dataDir: paths.dataDir,
254
+ heartbeatIntervalMin: policy.heartbeat.intervalMin,
255
+ maxDailySend: policy.gate.maxDailySend,
256
+ seedsMaxActive: policy.seeds.maxActive,
257
+ psyEnabled: policy.profile.psyEnabled
258
+ }, null, 2));
259
+ return 0;
260
+ }
261
+ async function verbSeeds(c) {
262
+ const { guard, policy, sub, rest, seedsFile, seedAudit } = c;
263
+ switch (sub) {
264
+ case "add": {
265
+ let text = rest[0]?.trim() ?? "";
266
+ if (text === "-") text = readStdinText();
267
+ if (!text) {
268
+ console.error("usage: seeds add <text|-> [--tag t] [--source s] [--topic x] [--confidence n]");
269
+ return 1;
270
+ }
271
+ const result = addSeed(guard, seedsFile, policy, {
272
+ text,
273
+ tag: flagOneOf(rest, "--tag", SEED_TAGS),
274
+ source: flagOneOf(rest, "--source", SEED_SOURCES),
275
+ topic: flag(rest, "--topic"),
276
+ confidence: flag(rest, "--confidence") ? Number(flag(rest, "--confidence")) : void 0
277
+ }, Date.now(), seedAudit);
278
+ if (result.kind === "duplicate") {
279
+ console.log(`DUPLICATE: active seed ${result.seed.id} has the same text`);
280
+ return 0;
281
+ }
282
+ console.log(`${result.kind.toUpperCase()} ${result.seed.id} (${activeSeeds(loadPool(guard, seedsFile)).length}/${policy.seeds.maxActive})` + (result.evicted ? ` [evicted ${result.evicted.id}: ${result.evicted.retireReason}]` : ""));
232
283
  return 0;
233
284
  }
234
- case "seeds": {
235
- switch (sub) {
236
- case "add": {
237
- let text = rest[0]?.trim() ?? "";
238
- if (text === "-") text = readStdinText();
239
- if (!text) {
240
- console.error("usage: seeds add <text|-> [--tag t] [--source s] [--topic x] [--confidence n]");
241
- return 1;
242
- }
243
- const result = addSeed(guard, seedsFile, policy, {
244
- text,
245
- tag: flag(rest, "--tag"),
246
- source: flag(rest, "--source"),
247
- topic: flag(rest, "--topic"),
248
- confidence: flag(rest, "--confidence") ? Number(flag(rest, "--confidence")) : void 0
249
- }, Date.now(), seedAudit);
250
- if (result.kind === "duplicate") {
251
- console.log(`DUPLICATE: active seed ${result.seed.id} has the same text`);
252
- return 0;
253
- }
254
- console.log(`${result.kind.toUpperCase()} ${result.seed.id} (${activeSeeds(loadPool(guard, seedsFile)).length}/${policy.seeds.maxActive})` + (result.evicted ? ` [evicted ${result.evicted.id}: ${result.evicted.retireReason}]` : ""));
255
- return 0;
256
- }
257
- case "list": {
258
- const db = loadPool(guard, seedsFile);
259
- const items = rest.includes("--archived") ? archivedSeeds(db) : activeSeeds(db);
260
- for (const s of items) {
261
- const ageDays = Math.floor((Date.now() - Date.parse(s.bornAt)) / 864e5);
262
- console.log(`${s.id} [${s.tag}/${s.source}] d${ageDays} used:${s.used}${s.protected ? " *" : ""} ${s.text.slice(0, 60)}`);
263
- }
264
- if (items.length === 0) console.log("(empty)");
265
- return 0;
266
- }
267
- case "surface": {
268
- const s = surfaceSeed(guard, seedsFile, policy, rest[0] ?? "", Date.now(), seedAudit);
269
- if (!s) {
270
- console.log("NOT_FOUND");
271
- return 1;
272
- }
273
- console.log(`SURFACED ${s.id} used:${s.used}${s.status === "archived" ? " -> archived (consumed)" : ""}`);
274
- return 0;
275
- }
276
- case "archive": {
277
- const s = archiveSeedById(guard, seedsFile, rest[0] ?? "", "completed", Date.now(), seedAudit);
278
- if (!s) {
279
- console.log("NOT_FOUND");
280
- return 1;
281
- }
282
- console.log(`ARCHIVED ${s.id}`);
283
- return 0;
284
- }
285
- case "gc": {
286
- const report = gcPool(guard, seedsFile, policy, Date.now(), seedAudit);
287
- console.log(`GC consumed:${report.consumed} expired:${report.expired} cold_bench:${report.coldBench} archive_trimmed:${report.archiveTrimmed} -> active ${report.activeAfter}/${policy.seeds.maxActive} archive ${archivedSeeds(loadPool(guard, seedsFile)).length}/${policy.seeds.archiveCap}`);
288
- return 0;
289
- }
290
- case "stats": {
291
- const db = loadPool(guard, seedsFile);
292
- console.log(JSON.stringify({
293
- active: activeSeeds(db).length,
294
- archived: archivedSeeds(db).length,
295
- cap: policy.seeds.maxActive,
296
- seq: db.seq
297
- }, null, 2));
298
- return 0;
299
- }
300
- default:
301
- console.error(`unknown seeds subcommand: ${sub}`);
302
- return 1;
285
+ case "list": {
286
+ const db = loadPool(guard, seedsFile);
287
+ const items = rest.includes("--archived") ? archivedSeeds(db) : activeSeeds(db);
288
+ for (const s of items) {
289
+ const ageDays = Math.floor((Date.now() - Date.parse(s.bornAt)) / 864e5);
290
+ console.log(`${s.id} [${s.tag}/${s.source}] d${ageDays} used:${s.used}${s.protected ? " *" : ""} ${s.text.slice(0, 60)}`);
303
291
  }
292
+ if (items.length === 0) console.log("(empty)");
293
+ return 0;
304
294
  }
305
- case "ledger": {
306
- switch (sub) {
307
- case "add": {
308
- let text = rest.join(" ").trim();
309
- if (text === "-") text = readStdinText();
310
- if (!text) {
311
- console.error("usage: ledger add <text|->");
312
- return 1;
313
- }
314
- const e = appendEntry(guard, ledgerFile, text);
315
- console.log(`OPEN #${e.id} ${e.text.slice(0, 60)}`);
316
- return 0;
317
- }
318
- case "list": {
319
- const { entries } = readLedger(guard, ledgerFile);
320
- const items = rest.includes("--pending") ? scanPending(guard, ledgerFile) : entries;
321
- for (const e of items) console.log(`${e.status === "open" ? " " : "x"} #${e.id} ${e.date} ${e.text.slice(0, 60)}`);
322
- if (items.length === 0) console.log("(empty)");
323
- return 0;
324
- }
325
- case "done": {
326
- const key = rest.join(" ").trim();
327
- if (!key) {
328
- console.error("usage: ledger done <id|substring>");
329
- return 1;
330
- }
331
- const e = markDone(guard, ledgerFile, key);
332
- if (!e) {
333
- console.log("NOT_FOUND");
334
- return 1;
335
- }
336
- console.log(`DONE #${e.id}`);
337
- return 0;
338
- }
339
- case "open": {
340
- const f = ledgerFile;
341
- if (!fs2.existsSync(f)) {
342
- console.log(`(ledger will be created at ${f})`);
343
- }
344
- spawn("cmd", ["/c", "start", "", f], { detached: true, stdio: "ignore" }).unref();
345
- console.log(`opened ${f}`);
346
- return 0;
347
- }
348
- default:
349
- console.error(`unknown ledger subcommand: ${sub}`);
350
- return 1;
295
+ case "surface": {
296
+ const s = surfaceSeed(guard, seedsFile, policy, rest[0] ?? "", Date.now(), seedAudit);
297
+ if (!s) {
298
+ console.log("NOT_FOUND");
299
+ return 1;
351
300
  }
301
+ console.log(`SURFACED ${s.id} used:${s.used}${s.status === "archived" ? " -> archived (consumed)" : ""}`);
302
+ return 0;
352
303
  }
353
- case "weekly": {
354
- const reports = listWeeklyReports(guard, paths.dataDir);
355
- if (sub === "list" || sub === void 0) {
356
- if (reports.length === 0) console.log("(no reports yet \u2014 heartbeat generates one every 7 days)");
357
- for (const r of reports) console.log(`${r.file} ${r.start.slice(0, 10)} ~ ${r.end.slice(0, 10)} [${r.source}]`);
358
- return 0;
359
- }
360
- if (sub === "show") {
361
- const file = rest[0] ?? reports[0]?.file;
362
- if (!file) {
363
- console.log("(no reports yet)");
364
- return 1;
365
- }
366
- const rep = readWeeklyReport(guard, paths.dataDir, file);
367
- if (!rep) {
368
- console.error(`no such report: ${file} (see: weekly list; files live in ${weeklyDirPath(paths.dataDir)})`);
369
- return 1;
370
- }
371
- console.log(`# ${rep.start.slice(0, 10)} ~ ${rep.end.slice(0, 10)} [${rep.source}]`);
372
- console.log(rep.text);
373
- return 0;
304
+ case "archive": {
305
+ const s = archiveSeedById(guard, seedsFile, rest[0] ?? "", "completed", Date.now(), seedAudit);
306
+ if (!s) {
307
+ console.log("NOT_FOUND");
308
+ return 1;
374
309
  }
375
- console.error(`unknown weekly subcommand: ${sub}`);
310
+ console.log(`ARCHIVED ${s.id}`);
311
+ return 0;
312
+ }
313
+ case "gc": {
314
+ const report = gcPool(guard, seedsFile, policy, Date.now(), seedAudit);
315
+ console.log(`GC consumed:${report.consumed} expired:${report.expired} cold_bench:${report.coldBench} archive_trimmed:${report.archiveTrimmed} -> active ${report.activeAfter}/${policy.seeds.maxActive} archive ${archivedSeeds(loadPool(guard, seedsFile)).length}/${policy.seeds.archiveCap}`);
316
+ return 0;
317
+ }
318
+ case "stats": {
319
+ const db = loadPool(guard, seedsFile);
320
+ console.log(JSON.stringify({
321
+ active: activeSeeds(db).length,
322
+ archived: archivedSeeds(db).length,
323
+ cap: policy.seeds.maxActive,
324
+ seq: db.seq
325
+ }, null, 2));
326
+ return 0;
327
+ }
328
+ default:
329
+ console.error(`unknown seeds subcommand: ${sub}`);
376
330
  return 1;
331
+ }
332
+ }
333
+ async function verbLedger(c) {
334
+ const { guard, sub, rest, ledgerFile } = c;
335
+ switch (sub) {
336
+ case "add": {
337
+ let text = rest.join(" ").trim();
338
+ if (text === "-") text = readStdinText();
339
+ if (!text) {
340
+ console.error("usage: ledger add <text|->");
341
+ return 1;
342
+ }
343
+ const e = appendEntry(guard, ledgerFile, text);
344
+ console.log(`OPEN #${e.id} ${e.text.slice(0, 60)}`);
345
+ return 0;
346
+ }
347
+ case "list": {
348
+ const { entries } = readLedger(guard, ledgerFile);
349
+ const items = rest.includes("--pending") ? scanPending(guard, ledgerFile) : entries;
350
+ for (const e of items) console.log(`${e.status === "open" ? " " : "x"} #${e.id} ${e.date} ${e.text.slice(0, 60)}`);
351
+ if (items.length === 0) console.log("(empty)");
352
+ return 0;
377
353
  }
378
- case "logs": {
379
- if (sub !== "cleanup") {
380
- console.error("usage: logs cleanup [--dry-run]");
354
+ case "done": {
355
+ const key = rest.join(" ").trim();
356
+ if (!key) {
357
+ console.error("usage: ledger done <id|substring>");
381
358
  return 1;
382
359
  }
383
- const dry = rest.includes("--dry-run");
384
- const envPulse = path2.join(paths.logsDir, "envpulse.jsonl");
385
- const cut1 = policy.retention.envPulseHours * 36e5;
386
- const cut2 = policy.retention.decisionLogDays * 864e5;
387
- if (dry) {
388
- console.log(`(dry-run) would prune ${envPulse} to ${policy.retention.envPulseHours}h and ${decisionLog} to ${policy.retention.decisionLogDays}d`);
389
- return 0;
360
+ const e = markDone(guard, ledgerFile, key);
361
+ if (!e) {
362
+ console.log("NOT_FOUND");
363
+ return 1;
390
364
  }
391
- const a = pruneAuditFile(envPulse, cut1);
392
- const b = pruneAuditFile(decisionLog, cut2);
393
- appendAuditLine(decisionLog, { event: "retention", pruned_envpulse: a, pruned_decision: b });
394
- console.log(`PRUNED envpulse:${a} decision:${b}`);
365
+ console.log(`DONE #${e.id}`);
395
366
  return 0;
396
367
  }
397
- case "browse": {
398
- switch (sub) {
399
- case "status": {
400
- const st = browseStatus(guard, paths);
401
- const interests = loadInterests(paths);
402
- console.log(JSON.stringify({
403
- watchTargets: loadWatchlist(paths).targets?.length ?? 0,
404
- lastCheckAt: st.last_check_at ? new Date(st.last_check_at).toISOString() : null,
405
- lastWanderAt: st.wander.last_wander_at ? new Date(st.wander.last_wander_at).toISOString() : null,
406
- focusCount: st.wander.focusCount,
407
- interestCount: interests.interests?.length ?? 0
408
- }, null, 2));
409
- return 0;
410
- }
411
- case "dry": {
412
- const forced = /* @__PURE__ */ new Date();
413
- forced.setHours(12, 0, 0, 0);
414
- const advice = adviseWander(guard, paths, policy, forced);
415
- console.log(JSON.stringify(advice, null, 2));
416
- return 0;
417
- }
418
- case "watch": {
419
- const report = await checkWatchlist(guard, paths, { throttleOk: true });
420
- console.log(JSON.stringify({ checked: report.checked, updates: report.items.length, errors: report.errors }, null, 2));
421
- for (const item of report.items) console.log(`NEWS: ${item.text}`);
422
- return 0;
423
- }
424
- case "done": {
425
- const focus = rest.join(" ").trim();
426
- if (!focus) {
427
- console.error("usage: browse done <focus>");
428
- return 1;
429
- }
430
- const r = completeWander(guard, paths, focus);
431
- console.log(JSON.stringify(r));
432
- return 0;
433
- }
434
- default:
435
- console.error(`unknown browse subcommand: ${sub}`);
436
- return 1;
368
+ case "open": {
369
+ const f = ledgerFile;
370
+ if (!fs2.existsSync(f)) {
371
+ console.log(`(ledger will be created at ${f})`);
437
372
  }
373
+ spawn("cmd", ["/c", "start", "", f], { detached: true, stdio: "ignore" }).unref();
374
+ console.log(`opened ${f}`);
375
+ return 0;
438
376
  }
439
- case "notify": {
440
- switch (sub) {
441
- case "check":
442
- console.log(ensureRegistered(paths) ? "REGISTERED: yes" : "REGISTERED: no");
443
- return 0;
444
- case "register":
445
- ensureRegistered(paths);
446
- console.log("register attempted");
447
- return 0;
448
- case "send":
449
- console.log(sendNewMessageHint(paths) ? "SENT" : "FAILED");
450
- return 0;
451
- default:
452
- console.error("usage: notify check|register|send");
453
- return 1;
377
+ default:
378
+ console.error(`unknown ledger subcommand: ${sub}`);
379
+ return 1;
380
+ }
381
+ }
382
+ async function verbWeekly(c) {
383
+ const { guard, paths, sub, rest } = c;
384
+ const reports = listWeeklyReports(guard, paths.dataDir);
385
+ if (sub === "list" || sub === void 0) {
386
+ if (reports.length === 0) console.log("(no reports yet \u2014 heartbeat generates one every 7 days)");
387
+ for (const r of reports) console.log(`${r.file} ${r.start.slice(0, 10)} ~ ${r.end.slice(0, 10)} [${r.source}]`);
388
+ return 0;
389
+ }
390
+ if (sub === "show") {
391
+ const file = rest[0] ?? reports[0]?.file;
392
+ if (!file) {
393
+ console.log("(no reports yet)");
394
+ return 1;
395
+ }
396
+ const rep = readWeeklyReport(guard, paths.dataDir, file);
397
+ if (!rep) {
398
+ console.error(`no such report: ${file} (see: weekly list; files live in ${weeklyDirPath(paths.dataDir)})`);
399
+ return 1;
400
+ }
401
+ console.log(`# ${rep.start.slice(0, 10)} ~ ${rep.end.slice(0, 10)} [${rep.source}]`);
402
+ console.log(rep.text);
403
+ return 0;
404
+ }
405
+ console.error(`unknown weekly subcommand: ${sub}`);
406
+ return 1;
407
+ }
408
+ async function verbLogs(c) {
409
+ const { paths, policy, sub, rest, decisionLog } = c;
410
+ if (sub !== "cleanup") {
411
+ console.error("usage: logs cleanup [--dry-run]");
412
+ return 1;
413
+ }
414
+ const dry = rest.includes("--dry-run");
415
+ const envPulse = path2.join(paths.logsDir, "envpulse.jsonl");
416
+ const cut1 = policy.retention.envPulseHours * 36e5;
417
+ const cut2 = policy.retention.decisionLogDays * 864e5;
418
+ if (dry) {
419
+ console.log(`(dry-run) would prune ${envPulse} to ${policy.retention.envPulseHours}h and ${decisionLog} to ${policy.retention.decisionLogDays}d`);
420
+ return 0;
421
+ }
422
+ const a = pruneAuditFile(envPulse, cut1);
423
+ const b = pruneAuditFile(decisionLog, cut2);
424
+ appendAuditLine(decisionLog, { event: "retention", pruned_envpulse: a, pruned_decision: b });
425
+ console.log(`PRUNED envpulse:${a} decision:${b}`);
426
+ return 0;
427
+ }
428
+ async function verbBrowse(c) {
429
+ const { guard, paths, policy, sub, rest } = c;
430
+ switch (sub) {
431
+ case "status": {
432
+ const st = browseStatus(guard, paths);
433
+ const interests = loadInterests(paths);
434
+ console.log(JSON.stringify({
435
+ lastWanderAt: st.wander.last_wander_at ? new Date(st.wander.last_wander_at).toISOString() : null,
436
+ focusCount: st.wander.focusCount,
437
+ interestCount: interests.interests?.length ?? 0
438
+ }, null, 2));
439
+ return 0;
440
+ }
441
+ case "dry": {
442
+ const forced = /* @__PURE__ */ new Date();
443
+ forced.setHours(12, 0, 0, 0);
444
+ const advice = adviseWander(guard, paths, policy, forced);
445
+ console.log(JSON.stringify(advice, null, 2));
446
+ return 0;
447
+ }
448
+ case "done": {
449
+ const focus = rest.join(" ").trim();
450
+ if (!focus) {
451
+ console.error("usage: browse done <focus>");
452
+ return 1;
454
453
  }
454
+ const r = completeWander(guard, paths, focus);
455
+ console.log(JSON.stringify(r));
456
+ return 0;
455
457
  }
456
- case "profile": {
457
- const doc = loadProfile(guard, profileFilePath(paths.dataDir));
458
- const all = rest.includes("--all");
459
- switch (sub) {
460
- case "list": {
461
- let n = 0;
462
- for (const p of ["interest", "projects", "comm", "psy"]) {
463
- for (const e of doc.partitions[p].entries) {
464
- if (!all && e.validTo !== null) continue;
465
- n += 1;
466
- console.log(`${e.id} [${e.partition}/${e.topic}/${e.subTopic}] ${e.temporal} conf=${e.confidence.toFixed(2)}${e.lowActivity ? " \u4E45\u672A\u9A8C\u8BC1" : ""}${e.validTo ? " [\u5931\u6548]" : ""}: ${e.content.slice(0, 60)}`);
467
- }
468
- }
469
- if (n === 0) console.log("(empty)");
470
- return 0;
471
- }
472
- case "export": {
473
- const lines = [`# \u753B\u50CF\u5BFC\u51FA ${(/* @__PURE__ */ new Date()).toISOString()}`, ""];
474
- for (const p of ["interest", "projects", "comm", "psy"]) {
475
- lines.push(`## ${p}`);
476
- for (const e of doc.partitions[p].entries) {
477
- if (e.validTo !== null) continue;
478
- lines.push(`- [${e.topic}/${e.subTopic}] ${e.content} (conf ${e.confidence.toFixed(2)}, ${e.temporal})`);
479
- }
480
- }
481
- const out = path2.join(paths.exportsDir, `profile-export-${Date.now()}.md`);
482
- writeText(guard, out, lines.join("\n") + "\n");
483
- console.log(`EXPORTED: ${out}`);
484
- return 0;
485
- }
486
- case "verify": {
487
- const r = verifyProfile(guard, paths.dataDir);
488
- console.log(JSON.stringify(r, null, 2));
489
- return r.ok ? 0 : 1;
490
- }
491
- case "rebuild": {
492
- const r = rebuildProfile(guard, paths.dataDir, { check: rest.includes("--check") });
493
- console.log(JSON.stringify(r, null, 2));
494
- return r.ok ? 0 : 1;
495
- }
496
- case "wipe": {
497
- if (!rest.includes("--yes")) {
498
- console.log("REFUSED: add --yes to wipe profile data (profile.json/inbox/journal)");
499
- return 1;
500
- }
501
- for (const f of ["profile.json", "profile_inbox.jsonl", "profile_journal.jsonl"]) {
502
- const abs = guard.assert(path2.join(paths.dataDir, f));
503
- if (fs2.existsSync(abs)) fs2.rmSync(abs, { force: true });
504
- }
505
- console.log("WIPED (settings preserved; use burn for full shredding)");
506
- return 0;
458
+ default:
459
+ console.error(`unknown browse subcommand: ${sub}`);
460
+ return 1;
461
+ }
462
+ }
463
+ async function verbNotify(c) {
464
+ const { paths, sub } = c;
465
+ switch (sub) {
466
+ case "check":
467
+ console.log(await ensureRegistered(paths) ? "REGISTERED: yes" : "REGISTERED: no");
468
+ return 0;
469
+ case "register":
470
+ await ensureRegistered(paths);
471
+ console.log("register attempted");
472
+ return 0;
473
+ case "send":
474
+ console.log(await sendNewMessageHint(paths) ? "SENT" : "FAILED");
475
+ return 0;
476
+ default:
477
+ console.error("usage: notify check|register|send");
478
+ return 1;
479
+ }
480
+ }
481
+ async function verbProfile(c) {
482
+ const { guard, paths, sub, rest } = c;
483
+ const doc = loadProfile(guard, profileFilePath(paths.dataDir));
484
+ const all = rest.includes("--all");
485
+ switch (sub) {
486
+ case "list": {
487
+ let n = 0;
488
+ for (const p of ["interest", "projects", "comm", "psy"]) {
489
+ for (const e of doc.partitions[p].entries) {
490
+ if (!all && e.validTo !== null) continue;
491
+ n += 1;
492
+ console.log(`${e.id} [${e.partition}/${e.topic}/${e.subTopic}] ${e.temporal} conf=${e.confidence.toFixed(2)}${e.lowActivity ? " \u4E45\u672A\u9A8C\u8BC1" : ""}${e.validTo ? " [\u5931\u6548]" : ""}: ${e.content.slice(0, 60)}`);
507
493
  }
508
- case "snapshot": {
509
- const { snapshotDue, snapshotProfile, SNAPSHOT_THRESHOLD } = await import("../snapshot-ZBCSX67Y.js");
510
- const { needed, lines } = snapshotDue(guard, paths.dataDir);
511
- console.log(`journal ${lines} \u6761\uFF08\u9608\u503C ${SNAPSHOT_THRESHOLD}\uFF09`);
512
- if (!needed && !rest.includes("--force")) {
513
- console.log("\u672A\u5230\u9608\u503C\uFF0C\u672A\u6267\u884C\uFF08\u52A0 --force \u5F3A\u5236\u6298\u53E0\uFF09\u3002");
514
- return 0;
515
- }
516
- const report = snapshotProfile(guard, paths.dataDir);
517
- if (!report.ok) {
518
- console.log(`\u672A\u6267\u884C\uFF1A${report.reason}`);
519
- return 0;
520
- }
521
- console.log(`\u5DF2\u6298\u53E0 ${report.folded} \u6761 \u2192 \u5FEB\u7167\uFF08baseline ${report.baselineTs}\uFF09\uFF0C\u5F52\u6863 ${report.archiveFile}\uFF0Clive journal \u5DF2\u6E05\u7A7A\u3002`);
522
- console.log("verify/rebuild \u81EA\u52A8\u4ECE\u5FEB\u7167\u57FA\u70B9\u91CD\u653E\uFF0C\u65E0\u9700\u989D\u5916\u64CD\u4F5C\u3002");
523
- return 0;
494
+ }
495
+ if (n === 0) console.log("(empty)");
496
+ return 0;
497
+ }
498
+ case "export": {
499
+ const lines = [`# \u753B\u50CF\u5BFC\u51FA ${(/* @__PURE__ */ new Date()).toISOString()}`, ""];
500
+ for (const p of ["interest", "projects", "comm", "psy"]) {
501
+ lines.push(`## ${p}`);
502
+ for (const e of doc.partitions[p].entries) {
503
+ if (e.validTo !== null) continue;
504
+ lines.push(`- [${e.topic}/${e.subTopic}] ${e.content} (conf ${e.confidence.toFixed(2)}, ${e.temporal})`);
524
505
  }
525
- default:
526
- console.error(`unknown profile subcommand: ${sub}`);
527
- return 1;
528
506
  }
507
+ const out = path2.join(paths.exportsDir, `profile-export-${Date.now()}.md`);
508
+ writeText(guard, out, lines.join("\n") + "\n");
509
+ console.log(`EXPORTED: ${out}`);
510
+ return 0;
529
511
  }
530
- case "burn": {
531
- const yes = rest.includes("--yes");
532
- const all = rest.includes("--all");
533
- const plan = planBurn(guard, paths, all);
534
- console.log("\u9884\u6F14\uFF08\u4E0D\u4F1A\u6267\u884C\uFF09:");
535
- for (const p of plan) console.log(` [${p.exists ? "\u5B58\u5728" : "\u65E0 "}] ${p.target.file ?? p.target.dir} ${p.target.note}`);
536
- if (!yes) {
537
- console.log("\n\u6B64\u64CD\u4F5C\u4F1A\u8BA9\u5FC3\u8DF3 agent \u5931\u5FC6\u3002\u786E\u8BA4\u6267\u884C\u8BF7\u52A0 --yes\uFF08" + (all ? "\u542B --all \u8FDE\u7528\u6237\u8BBE\u5B9A" : "\u7528\u6237\u8BBE\u5B9A\u4FDD\u7559") + "\uFF09");
538
- return 0;
512
+ case "verify": {
513
+ const r = verifyProfile(guard, paths.dataDir);
514
+ console.log(JSON.stringify(r, null, 2));
515
+ return r.ok ? 0 : 1;
516
+ }
517
+ case "rebuild": {
518
+ const r = rebuildProfile(guard, paths.dataDir, { check: rest.includes("--check") });
519
+ console.log(JSON.stringify(r, null, 2));
520
+ return r.ok ? 0 : 1;
521
+ }
522
+ case "wipe": {
523
+ if (!rest.includes("--yes")) {
524
+ console.log("REFUSED: add --yes to wipe profile data (profile.json/inbox/journal)");
525
+ return 1;
539
526
  }
540
- const result = executeBurn(guard, paths, { all });
541
- console.log(`BURNED: ${result.burned.length} \u9879\uFF1BMISSING: ${result.missing.length} \u9879`);
527
+ for (const f of ["profile.json", "profile_inbox.jsonl", "profile_journal.jsonl"]) {
528
+ const abs = guard.assert(path2.join(paths.dataDir, f));
529
+ if (fs2.existsSync(abs)) fs2.rmSync(abs, { force: true });
530
+ }
531
+ console.log("WIPED (settings preserved; use burn for full shredding)");
542
532
  return 0;
543
533
  }
544
- case "migrate": {
545
- if (sub === "export") {
546
- const defaultOut = path2.join(paths.exportsDir, `heartbeat-memory-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.hbmig`);
547
- const outfile = rest[0] ?? defaultOut;
548
- let outCanon;
549
- try {
550
- outCanon = guard.assert(outfile);
551
- } catch {
552
- console.error(`\u5BFC\u51FA\u6587\u4EF6\u5FC5\u987B\u843D\u5728\u63D2\u4EF6 data/ \u76EE\u5F55\u5185\uFF08\u8DEF\u5F84\u5B88\u536B\u62D2\u7EDD\uFF09\uFF1A${outfile}
553
- \u9ED8\u8BA4\u4F4D\u7F6E\uFF1A${defaultOut}`);
554
- return 1;
555
- }
556
- const [pw1, pw2] = await readLines(["\u8BBE\u7F6E\u53E3\u4EE4\uFF08\u7528\u4E8E\u52A0\u5BC6\u8FC1\u79FB\u5305\uFF09: ", "\u518D\u8F93\u5165\u4E00\u6B21\u786E\u8BA4: "]);
557
- if (!pw1 || pw1 !== pw2) {
558
- console.error("\u4E24\u6B21\u8F93\u5165\u4E0D\u4E00\u81F4\u6216\u4E3A\u7A7A\uFF0C\u5DF2\u53D6\u6D88\u3002");
559
- return 1;
560
- }
561
- const { entries, progress } = collectMigrationEntries(guard, paths);
562
- if (entries.length === 0) {
563
- console.log("\u6CA1\u6709\u53EF\u6253\u5305\u7684\u8BB0\u5FC6\u6587\u4EF6\uFF08data/ \u662F\u7A7A\u7684\uFF09\u3002");
564
- return 1;
565
- }
566
- fs2.mkdirSync(path2.dirname(outCanon), { recursive: true });
567
- fs2.writeFileSync(outCanon, encryptContainer(entries, pw1), "utf8");
568
- console.log(`\u5DF2\u6253\u5305 ${entries.length} \u4E2A\u6587\u4EF6 \u2192 ${outCanon}`);
569
- console.log(` \u6253\u5305\uFF1A${progress.packed.join(", ")}`);
570
- if (progress.missing.length > 0) console.log(` \u8DF3\u8FC7\uFF08\u4E0D\u5B58\u5728\uFF09\uFF1A${progress.missing.join(", ")}`);
571
- console.log("\u5BB9\u5668\u662F\u53E3\u4EE4\u52A0\u5BC6\u7684\uFF0C\u53EF\u5B89\u5168\u62F7\u8D1D\u5230\u65B0\u673A\u5668\uFF1BDPAPI \u4F1A\u5728\u5BFC\u5165\u65F6\u7528\u65B0\u673A\u5668\u91CD\u65B0\u52A0\u5BC6\u3002");
534
+ case "snapshot": {
535
+ const { snapshotDue, snapshotProfile, SNAPSHOT_THRESHOLD } = await import("../snapshot-K5VHETTK.js");
536
+ const { needed, lines } = snapshotDue(guard, paths.dataDir);
537
+ console.log(`journal ${lines} \u6761\uFF08\u9608\u503C ${SNAPSHOT_THRESHOLD}\uFF09`);
538
+ if (!needed && !rest.includes("--force")) {
539
+ console.log("\u672A\u5230\u9608\u503C\uFF0C\u672A\u6267\u884C\uFF08\u52A0 --force \u5F3A\u5236\u6298\u53E0\uFF09\u3002");
572
540
  return 0;
573
541
  }
574
- if (sub === "import") {
575
- const infile = rest[0];
576
- if (!infile) {
577
- console.error("usage: migrate import <container-file>");
578
- return 1;
579
- }
580
- const [pw] = await readLines(["\u8F93\u5165\u8FC1\u79FB\u5305\u53E3\u4EE4: "]);
581
- let text;
582
- try {
583
- text = fs2.readFileSync(infile, "utf8");
584
- } catch {
585
- console.error(`\u8BFB\u4E0D\u5230\u8FC1\u79FB\u5305\uFF1A${infile}`);
586
- return 1;
587
- }
588
- let files;
589
- try {
590
- files = decryptContainer(text, pw ?? "");
591
- } catch (e) {
592
- console.error(String(e instanceof Error ? e.message : e));
593
- return 1;
594
- }
595
- const result = applyMigrationEntries(guard, paths, files.files);
596
- console.log(`\u6062\u590D ${result.restored.length} \u4E2A\u6587\u4EF6\uFF1B\u5907\u4EFD ${result.backedUp.length} \u4E2A\u88AB\u8986\u76D6\u6587\u4EF6\uFF1B\u8DF3\u8FC7 ${result.skipped.length} \u4E2A\u3002`);
597
- for (const r of result.restored) console.log(` \u6062\u590D: ${r}`);
598
- for (const b of result.backedUp) console.log(` \u5907\u4EFD: ${b}.bak-migrate-*`);
599
- if (result.skipped.length > 0) console.log(` \u8DF3\u8FC7: ${result.skipped.join(", ")}`);
600
- console.log("\u91CD\u542F DSH \u540E\u751F\u6548\u3002");
542
+ const report = snapshotProfile(guard, paths.dataDir);
543
+ if (!report.ok) {
544
+ console.log(`\u672A\u6267\u884C\uFF1A${report.reason}`);
601
545
  return 0;
602
546
  }
603
- console.error(`unknown migrate subcommand: ${sub}`);
547
+ console.log(`\u5DF2\u6298\u53E0 ${report.folded} \u6761 \u2192 \u5FEB\u7167\uFF08baseline ${report.baselineTs}\uFF09\uFF0C\u5F52\u6863 ${report.archiveFile}\uFF0Clive journal \u5DF2\u6E05\u7A7A\u3002`);
548
+ console.log("verify/rebuild \u81EA\u52A8\u4ECE\u5FEB\u7167\u57FA\u70B9\u91CD\u653E\uFF0C\u65E0\u9700\u989D\u5916\u64CD\u4F5C\u3002");
549
+ return 0;
550
+ }
551
+ default:
552
+ console.error(`unknown profile subcommand: ${sub}`);
553
+ return 1;
554
+ }
555
+ }
556
+ async function verbBurn(c) {
557
+ const { guard, paths, rest } = c;
558
+ const yes = rest.includes("--yes");
559
+ const all = rest.includes("--all");
560
+ const plan = planBurn(guard, paths, all);
561
+ console.log("\u9884\u6F14\uFF08\u4E0D\u4F1A\u6267\u884C\uFF09:");
562
+ for (const p of plan) console.log(` [${p.exists ? "\u5B58\u5728" : "\u65E0 "}] ${p.target.file ?? p.target.dir} ${p.target.note}`);
563
+ if (!yes) {
564
+ console.log("\n\u6B64\u64CD\u4F5C\u4F1A\u8BA9\u5FC3\u8DF3 agent \u5931\u5FC6\u3002\u786E\u8BA4\u6267\u884C\u8BF7\u52A0 --yes\uFF08" + (all ? "\u542B --all \u8FDE\u7528\u6237\u8BBE\u5B9A" : "\u7528\u6237\u8BBE\u5B9A\u4FDD\u7559") + "\uFF09");
565
+ return 0;
566
+ }
567
+ const result = executeBurn(guard, paths, { all });
568
+ console.log(`BURNED: ${result.burned.length} \u9879\uFF1BMISSING: ${result.missing.length} \u9879`);
569
+ return 0;
570
+ }
571
+ async function verbMigrate(c) {
572
+ const { guard, paths, sub, rest } = c;
573
+ if (sub === "export") {
574
+ const defaultOut = path2.join(paths.exportsDir, `heartbeat-memory-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.hbmig`);
575
+ const outfile = rest[0] ?? defaultOut;
576
+ let outCanon;
577
+ try {
578
+ outCanon = guard.assert(outfile);
579
+ } catch {
580
+ console.error(`\u5BFC\u51FA\u6587\u4EF6\u5FC5\u987B\u843D\u5728\u63D2\u4EF6 data/ \u76EE\u5F55\u5185\uFF08\u8DEF\u5F84\u5B88\u536B\u62D2\u7EDD\uFF09\uFF1A${outfile}
581
+ \u9ED8\u8BA4\u4F4D\u7F6E\uFF1A${defaultOut}`);
604
582
  return 1;
605
583
  }
606
- case "sessions": {
607
- const root = path2.join(os.homedir(), ".dsh", "sessions");
608
- let found = 0;
609
- if (fs2.existsSync(root)) {
610
- let own = null;
611
- try {
612
- own = JSON.parse(loadEncryptedText(guard, path2.join(paths.dataDir, "gate.json")) ?? "{}").sessionId ?? null;
613
- } catch {
614
- }
615
- for (const slug of fs2.readdirSync(root)) {
616
- for (const id of fs2.readdirSync(path2.join(root, slug))) {
617
- found += 1;
618
- const mark = id === own ? " \u2190 \u5FC3\u8DF3\u6B63\u8EAB" : "";
619
- console.log(`${id} [${slug}]${mark}`);
620
- }
621
- }
584
+ const [pw1, pw2] = await readLines(["\u8BBE\u7F6E\u53E3\u4EE4\uFF08\u7528\u4E8E\u52A0\u5BC6\u8FC1\u79FB\u5305\uFF09: ", "\u518D\u8F93\u5165\u4E00\u6B21\u786E\u8BA4: "]);
585
+ if (!pw1 || pw1 !== pw2) {
586
+ console.error("\u4E24\u6B21\u8F93\u5165\u4E0D\u4E00\u81F4\u6216\u4E3A\u7A7A\uFF0C\u5DF2\u53D6\u6D88\u3002");
587
+ return 1;
588
+ }
589
+ const { entries, progress } = collectMigrationEntries(guard, paths);
590
+ if (entries.length === 0) {
591
+ console.log("\u6CA1\u6709\u53EF\u6253\u5305\u7684\u8BB0\u5FC6\u6587\u4EF6\uFF08data/ \u662F\u7A7A\u7684\uFF09\u3002");
592
+ return 1;
593
+ }
594
+ fs2.mkdirSync(path2.dirname(outCanon), { recursive: true });
595
+ atomicWriteFileSync(outCanon, encryptContainer(entries, pw1));
596
+ console.log(`\u5DF2\u6253\u5305 ${entries.length} \u4E2A\u6587\u4EF6 \u2192 ${outCanon}`);
597
+ console.log(` \u6253\u5305\uFF1A${progress.packed.join(", ")}`);
598
+ if (progress.missing.length > 0) console.log(` \u8DF3\u8FC7\uFF08\u4E0D\u5B58\u5728\uFF09\uFF1A${progress.missing.join(", ")}`);
599
+ console.log("\u5BB9\u5668\u662F\u53E3\u4EE4\u52A0\u5BC6\u7684\uFF0C\u53EF\u5B89\u5168\u62F7\u8D1D\u5230\u65B0\u673A\u5668\uFF1BDPAPI \u4F1A\u5728\u5BFC\u5165\u65F6\u7528\u65B0\u673A\u5668\u91CD\u65B0\u52A0\u5BC6\u3002");
600
+ return 0;
601
+ }
602
+ if (sub === "import") {
603
+ const infile = rest[0];
604
+ if (!infile) {
605
+ console.error("usage: migrate import <container-file>");
606
+ return 1;
607
+ }
608
+ const [pw] = await readLines(["\u8F93\u5165\u8FC1\u79FB\u5305\u53E3\u4EE4: "]);
609
+ let text;
610
+ try {
611
+ text = fs2.readFileSync(infile, "utf8");
612
+ } catch {
613
+ console.error(`\u8BFB\u4E0D\u5230\u8FC1\u79FB\u5305\uFF1A${infile}`);
614
+ return 1;
615
+ }
616
+ let files;
617
+ try {
618
+ files = decryptContainer(text, pw ?? "");
619
+ } catch (e) {
620
+ console.error(String(e instanceof Error ? e.message : e));
621
+ return 1;
622
+ }
623
+ const result = applyMigrationEntries(guard, paths, files.files);
624
+ console.log(`\u6062\u590D ${result.restored.length} \u4E2A\u6587\u4EF6\uFF1B\u5907\u4EFD ${result.backedUp.length} \u4E2A\u88AB\u8986\u76D6\u6587\u4EF6\uFF1B\u8DF3\u8FC7 ${result.skipped.length} \u4E2A\u3002`);
625
+ for (const r of result.restored) console.log(` \u6062\u590D: ${r}`);
626
+ for (const b of result.backedUp) console.log(` \u5907\u4EFD: ${b}.bak-migrate-*`);
627
+ if (result.skipped.length > 0) console.log(` \u8DF3\u8FC7: ${result.skipped.join(", ")}`);
628
+ if (result.error) console.log(` \u5931\u8D25\u5E76\u5DF2\u56DE\u6EDA\uFF1A${result.error}`);
629
+ console.log("\u91CD\u542F DSH \u540E\u751F\u6548\u3002");
630
+ return result.error ? 1 : 0;
631
+ }
632
+ console.error(`unknown migrate subcommand: ${sub}`);
633
+ return 1;
634
+ }
635
+ async function verbSessions(c) {
636
+ const { guard, paths } = c;
637
+ const root = path2.join(os.homedir(), ".dsh", "sessions");
638
+ let found = 0;
639
+ if (fs2.existsSync(root)) {
640
+ const { homeSessionId } = await import("../orchestrator-EUTZ3KG6.js");
641
+ const own = homeSessionId(guard, paths);
642
+ for (const slug of fs2.readdirSync(root)) {
643
+ for (const id of fs2.readdirSync(path2.join(root, slug))) {
644
+ found += 1;
645
+ const mark = id === own ? " \u2190 \u5FC3\u8DF3\u6B63\u8EAB" : "";
646
+ console.log(`${id} [${slug}]${mark}`);
622
647
  }
623
- if (found === 0) console.log("(no persisted sessions found)");
648
+ }
649
+ }
650
+ if (found === 0) console.log("(no persisted sessions found)");
651
+ return 0;
652
+ }
653
+ async function verbBind(c) {
654
+ const { guard, paths, sub, rest } = c;
655
+ const { loadBindings, addBinding, removeBinding } = await import("../bindings-225SI7KC.js");
656
+ switch (sub) {
657
+ case "list": {
658
+ const data = loadBindings(guard, paths.settingsDir);
659
+ const { homeSessionId } = await import("../orchestrator-EUTZ3KG6.js");
660
+ const own = homeSessionId(guard, paths);
661
+ if (own) console.log(`\u5FC3\u8DF3\u6B63\u8EAB: ${own}\uFF08\u51B3\u7B56\u8F6E\u6B21\u53D1\u751F\u5730\uFF1Bbind remove \u5B83 = \u91CD\u7F6E\u6B63\u8EAB\uFF09`);
662
+ for (const b of data.bindings) {
663
+ console.log(`${b.sessionId} deliver:${b.deliver ? "\u221A" : "\xD7"} observe:${b.observe ? "\u221A" : "\xD7"}`);
664
+ }
665
+ if (data.bindings.length === 0 && !own) console.log("(no bindings \u2014 expressions stay in the dedicated heartbeat session)");
624
666
  return 0;
625
667
  }
626
- case "bind": {
627
- const { loadBindings, addBinding, removeBinding, bindingsFilePath } = await import("../bindings-XPPSKILN.js");
628
- switch (sub) {
629
- case "list": {
630
- const data = loadBindings(guard, paths.settingsDir);
631
- let own = null;
632
- try {
633
- own = JSON.parse(loadEncryptedText(guard, path2.join(paths.dataDir, "gate.json")) ?? "{}").sessionId ?? null;
634
- } catch {
635
- }
636
- if (own) console.log(`\u5FC3\u8DF3\u6B63\u8EAB: ${own}\uFF08\u51B3\u7B56\u8F6E\u6B21\u53D1\u751F\u5730\uFF1Bbind remove \u5B83 = \u91CD\u7F6E\u6B63\u8EAB\uFF09`);
637
- for (const b of data.bindings) {
638
- console.log(`${b.sessionId} deliver:${b.deliver ? "\u221A" : "\xD7"} observe:${b.observe ? "\u221A" : "\xD7"}`);
639
- }
640
- if (data.bindings.length === 0 && !own) console.log("(no bindings \u2014 expressions stay in the dedicated heartbeat session)");
641
- return 0;
642
- }
643
- case "add": {
644
- const id = rest[0];
645
- if (!id || !id.startsWith("session-")) {
646
- console.error("usage: bind add <sessionId> [--observe] [--no-deliver] (see: sessions list)");
647
- return 1;
648
- }
649
- const observe = rest.includes("--observe") || rest.includes("--observe-only");
650
- const deliver = !rest.includes("--no-deliver") && !rest.includes("--observe-only");
651
- const b = addBinding(guard, paths.settingsDir, id, { deliver, observe });
652
- console.log(`BOUND ${b.sessionId} deliver:${b.deliver} observe:${b.observe}`);
653
- return 0;
654
- }
655
- case "remove": {
656
- const id = rest[0];
657
- if (!id) {
658
- console.error("usage: bind remove <sessionId>");
659
- return 1;
660
- }
661
- console.log(removeBinding(guard, paths.settingsDir, id) ? `UNBOUND ${id}` : "NOT_FOUND");
662
- try {
663
- const state = JSON.parse(loadEncryptedText(guard, path2.join(paths.dataDir, "gate.json")) ?? "{}");
664
- if (state.sessionId === id) {
665
- fs2.rmSync(guard.assert(path2.join(paths.dataDir, "gate.json")), { force: true });
666
- console.log("\u6CE8\u610F\uFF1A\u8FD9\u662F\u5FC3\u8DF3\u6B63\u8EAB\u4F1A\u8BDD\u3002\u5DF2\u91CD\u7F6E\u2014\u2014\u4E0B\u6B21\u5FC3\u8DF3\u5C06\u521B\u5EFA\u65B0\u7684\u6B63\u8EAB\u4F1A\u8BDD\uFF08\u65E7\u4F1A\u8BDD\u4E0D\u518D\u6709\u5FC3\u8DF3\uFF09");
667
- appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "home_reset", oldSessionId: id });
668
- }
669
- } catch {
670
- }
671
- return 0;
672
- }
673
- default:
674
- console.error("usage: bind list | add <sessionId> | remove <sessionId>");
675
- return 1;
668
+ case "add": {
669
+ const id = rest[0];
670
+ if (!id || !id.startsWith("session-")) {
671
+ console.error("usage: bind add <sessionId> [--observe] [--no-deliver] (see: sessions list)");
672
+ return 1;
676
673
  }
674
+ const observe = rest.includes("--observe") || rest.includes("--observe-only");
675
+ const deliver = !rest.includes("--no-deliver") && !rest.includes("--observe-only");
676
+ const b = addBinding(guard, paths.settingsDir, id, { deliver, observe });
677
+ console.log(`BOUND ${b.sessionId} deliver:${b.deliver} observe:${b.observe}`);
678
+ return 0;
677
679
  }
678
- case "preset": {
679
- const id = flag(argv, "--id") ?? BUNDLED_PRESET_ID;
680
- const root = conventionalUserPresetRoot();
681
- const status = presetStatus(import.meta.url, id, root);
682
- if (sub === void 0 || sub === "status") {
683
- console.log(`preset id : ${status.id}`);
684
- console.log(`installed : ${status.installed ? "yes" : "no"}`);
685
- console.log(`target dir : ${status.dir}`);
686
- console.log(`bundled at : ${status.bundledDir ?? "(not found next to the plugin)"}`);
687
- if (status.installed) {
688
- console.log(
689
- `composition : ${status.compositionMatches ? "matches the bundled template" : "differs from the bundled template (hand-edited, or an older version)"}`
690
- );
691
- }
692
- if (!status.installed) {
693
- console.log("hint: the plugin installs this itself on the next DSH start (installPreset=true, default);");
694
- console.log(" or run `preset install` now.");
695
- }
696
- return status.installed ? 0 : 1;
680
+ case "remove": {
681
+ const id = rest[0];
682
+ if (!id) {
683
+ console.error("usage: bind remove <sessionId>");
684
+ return 1;
697
685
  }
698
- if (sub === "install") {
699
- const result = installBundledPreset({
700
- moduleUrl: import.meta.url,
701
- id,
702
- root,
703
- force: rest.includes("--force")
704
- });
705
- console.log(describeInstall(result));
706
- return result.action === "error" ? 1 : 0;
686
+ console.log(removeBinding(guard, paths.settingsDir, id) ? `UNBOUND ${id}` : "NOT_FOUND");
687
+ const { resetHomeSession } = await import("../orchestrator-EUTZ3KG6.js");
688
+ if (resetHomeSession(guard, paths, id)) {
689
+ console.log("\u6CE8\u610F\uFF1A\u8FD9\u662F\u5FC3\u8DF3\u6B63\u8EAB\u4F1A\u8BDD\u3002\u5DF2\u91CD\u7F6E\u2014\u2014\u4E0B\u6B21\u5FC3\u8DF3\u5C06\u521B\u5EFA\u65B0\u7684\u6B63\u8EAB\u4F1A\u8BDD\uFF08\u65E7\u4F1A\u8BDD\u4E0D\u518D\u6709\u5FC3\u8DF3\uFF09");
707
690
  }
708
- console.error("usage: preset status | install [--force] [--id <presetId>]");
709
- return 1;
691
+ return 0;
710
692
  }
711
693
  default:
712
- console.error(`unknown command: ${cmd}`);
713
- console.log(usage());
694
+ console.error("usage: bind list | add <sessionId> | remove <sessionId>");
714
695
  return 1;
715
696
  }
716
697
  }
698
+ async function verbPreset(c) {
699
+ const { sub, rest, argv } = c;
700
+ const id = flag(argv, "--id") ?? BUNDLED_PRESET_ID;
701
+ const root = conventionalUserPresetRoot();
702
+ const status = presetStatus(import.meta.url, id, root);
703
+ if (sub === void 0 || sub === "status") {
704
+ console.log(`preset id : ${status.id}`);
705
+ console.log(`installed : ${status.installed ? "yes" : "no"}`);
706
+ console.log(`target dir : ${status.dir}`);
707
+ console.log(`bundled at : ${status.bundledDir ?? "(not found next to the plugin)"}`);
708
+ if (status.installed) {
709
+ console.log(
710
+ `composition : ${status.compositionMatches ? "matches the bundled template" : "differs from the bundled template (hand-edited, or an older version)"}`
711
+ );
712
+ }
713
+ if (!status.installed) {
714
+ console.log("hint: the plugin installs this itself on the next DSH start (installPreset=true, default);");
715
+ console.log(" or run `preset install` now.");
716
+ }
717
+ return status.installed ? 0 : 1;
718
+ }
719
+ if (sub === "install") {
720
+ const result = installBundledPreset({
721
+ moduleUrl: import.meta.url,
722
+ id,
723
+ root,
724
+ force: rest.includes("--force")
725
+ });
726
+ console.log(describeInstall(result));
727
+ return result.action === "error" ? 1 : 0;
728
+ }
729
+ console.error("usage: preset status | install [--force] [--id <presetId>]");
730
+ return 1;
731
+ }
732
+ var VERBS = {
733
+ status: verbStatus,
734
+ seeds: verbSeeds,
735
+ ledger: verbLedger,
736
+ weekly: verbWeekly,
737
+ logs: verbLogs,
738
+ browse: verbBrowse,
739
+ notify: verbNotify,
740
+ profile: verbProfile,
741
+ burn: verbBurn,
742
+ migrate: verbMigrate,
743
+ sessions: verbSessions,
744
+ bind: verbBind,
745
+ preset: verbPreset
746
+ };
747
+ async function main(argv) {
748
+ const [cmd, sub, ...rest] = argv;
749
+ if (!cmd || cmd === "help" || cmd === "--help") {
750
+ console.log(usage());
751
+ return 0;
752
+ }
753
+ const verb = VERBS[cmd];
754
+ if (!verb) {
755
+ console.error(`unknown command: ${cmd}`);
756
+ console.log(usage());
757
+ return 1;
758
+ }
759
+ const paths = initWorkspace();
760
+ const guard = createPathGuard(paths.dataDir);
761
+ const policy = loadPolicy(guard, paths.configDir, paths.settingsDir);
762
+ const decisionLog = path2.join(paths.logsDir, "heartbeat.jsonl");
763
+ const seedAudit = (entry) => {
764
+ try {
765
+ appendAuditLine(decisionLog, entry);
766
+ } catch {
767
+ }
768
+ };
769
+ return verb({
770
+ paths,
771
+ guard,
772
+ policy,
773
+ sub,
774
+ rest,
775
+ argv,
776
+ seedsFile: seedsFilePath(paths.dataDir),
777
+ ledgerFile: ledgerFilePath(paths.dataDir),
778
+ decisionLog,
779
+ seedAudit
780
+ });
781
+ }
717
782
  if (process.argv[1] && import.meta.url === new URL(`file:///${process.argv[1].replace(/\\/g, "/")}`).href) {
718
783
  void main(process.argv.slice(2)).then((code) => {
719
784
  process.exitCode = code;
785
+ }).catch((e) => {
786
+ console.error(e);
787
+ process.exitCode = 1;
720
788
  });
721
789
  }
722
790
  export {