@kanadego/dsh-heartbeat 1.5.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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +192 -0
  3. package/assets/frontwin.ps1 +44 -0
  4. package/assets/idle.ps1 +27 -0
  5. package/assets/notify.ps1 +69 -0
  6. package/assets/presets/heartbeat/agent.cordis.yml +66 -0
  7. package/assets/presets/heartbeat/preset.yml +2 -0
  8. package/assets/screenpulse.ps1 +168 -0
  9. package/assets/vault.ps1 +52 -0
  10. package/client.js +428 -0
  11. package/config/busy-rules.json +63 -0
  12. package/config/interests.json +30 -0
  13. package/config/policy.json +57 -0
  14. package/config/profile-schema.json +68 -0
  15. package/config/watchlist.json +9 -0
  16. package/cordis.patch.yml +14 -0
  17. package/dist/bindings-XPPSKILN.js +19 -0
  18. package/dist/bindings-XPPSKILN.js.map +1 -0
  19. package/dist/chunk-2M35HRL6.js +1207 -0
  20. package/dist/chunk-2M35HRL6.js.map +1 -0
  21. package/dist/chunk-4UE74TUB.js +98 -0
  22. package/dist/chunk-4UE74TUB.js.map +1 -0
  23. package/dist/chunk-AISZRA4C.js +235 -0
  24. package/dist/chunk-AISZRA4C.js.map +1 -0
  25. package/dist/chunk-J6ZTRFFW.js +64 -0
  26. package/dist/chunk-J6ZTRFFW.js.map +1 -0
  27. package/dist/chunk-LLD7LUNN.js +202 -0
  28. package/dist/chunk-LLD7LUNN.js.map +1 -0
  29. package/dist/chunk-S7PTR42P.js +19 -0
  30. package/dist/chunk-S7PTR42P.js.map +1 -0
  31. package/dist/cli/index.js +589 -0
  32. package/dist/cli/index.js.map +1 -0
  33. package/dist/inbox-MMLHISQV.js +22 -0
  34. package/dist/inbox-MMLHISQV.js.map +1 -0
  35. package/dist/index.js +2745 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/lib-FJP7J4T6.js +2281 -0
  38. package/dist/lib-FJP7J4T6.js.map +1 -0
  39. package/dist/runtime-J5NOPRBA.js +11 -0
  40. package/dist/runtime-J5NOPRBA.js.map +1 -0
  41. package/package.json +61 -0
@@ -0,0 +1,589 @@
1
+ import {
2
+ BUNDLED_PRESET_ID,
3
+ activeSeeds,
4
+ addSeed,
5
+ adviseWander,
6
+ appendAuditLine,
7
+ appendEntry,
8
+ archiveSeedById,
9
+ archivedSeeds,
10
+ browseStatus,
11
+ checkWatchlist,
12
+ completeWander,
13
+ conventionalUserPresetRoot,
14
+ createPathGuard,
15
+ describeInstall,
16
+ ensureRegistered,
17
+ gcPool,
18
+ installBundledPreset,
19
+ ledgerFilePath,
20
+ loadInterests,
21
+ loadPolicy,
22
+ loadPool,
23
+ loadProfile,
24
+ loadWatchlist,
25
+ markDone,
26
+ presetStatus,
27
+ profileFilePath,
28
+ pruneAuditFile,
29
+ readLedger,
30
+ rebuildProfile,
31
+ scanPending,
32
+ seedsFilePath,
33
+ sendNewMessageHint,
34
+ shredFileSync,
35
+ surfaceSeed,
36
+ verifyProfile
37
+ } from "../chunk-2M35HRL6.js";
38
+ import {
39
+ initWorkspace,
40
+ loadEncryptedText,
41
+ writeText
42
+ } from "../chunk-LLD7LUNN.js";
43
+
44
+ // src/cli/index.ts
45
+ import { spawn } from "child_process";
46
+ import fs2 from "fs";
47
+ import os from "os";
48
+ import path2 from "path";
49
+
50
+ // src/vault/burn-list.ts
51
+ import fs from "fs";
52
+ import path from "path";
53
+ var BURN_LIST = [
54
+ { file: "profile.json", note: "\u753B\u50CF\u7269\u5316\u89C6\u56FE" },
55
+ { file: "profile_inbox.jsonl", note: "\u89C2\u5BDF\u6536\u4EF6\u7BB1" },
56
+ { file: "profile_journal.jsonl", note: "\u753B\u50CF\u64CD\u4F5C\u6D41\u6C34" },
57
+ { file: "profile_rhythm.json", note: "\u4F5C\u606F\u805A\u5408\uFF08\u7EAF\u7EDF\u8BA1\uFF09" },
58
+ { file: "screen.json", note: "\u5C4F\u5E55\u5FEB\u7167\uFF08\u52A0\u5BC6\uFF09" },
59
+ { file: "screen.jpg", note: "\u622A\u56FE\uFF08\u52A0\u5BC6\uFF09" },
60
+ { file: "sent.json", note: "\u8868\u8FBE\u8BB0\u5F55\uFF08\u52A0\u5BC6\uFF09" },
61
+ { file: "browse.json", note: "\u6D4F\u89C8\u6D41\u72B6\u6001\uFF08\u52A0\u5BC6\uFF09" },
62
+ { file: "seeds.jsonl", note: "\u7D20\u6750\u6C60\uFF08\u52A0\u5BC6\uFF09" },
63
+ { file: "ledger.md", note: "\u8D26\u672C\uFF08\u660E\u6587\uFF0C\u4EBA\u53EF\u8BFB\u662F\u8BBE\u8BA1\u76EE\u6807\uFF09" },
64
+ { file: "envpulse.json", note: "\u73AF\u5883\u6E29\u5EA6\u8BA1\u5FEB\u7167" },
65
+ { file: "gate.json", note: "\u95F8\u95E8\u8FD0\u884C\u65F6\u8BA1\u6570" },
66
+ { 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" },
67
+ { dir: "logs", note: "\u5BA1\u8BA1\u4E0E\u51B3\u7B56\u65E5\u5FD7" },
68
+ { dir: "exports", note: "\u5BFC\u51FA\u4EA7\u7269" }
69
+ ];
70
+ var SETTINGS_DIR = "settings";
71
+ function planBurn(guard, paths, all = false) {
72
+ const plan = [];
73
+ for (const t of BURN_LIST) {
74
+ const rel = t.file ?? t.dir;
75
+ if (!all && rel === SETTINGS_DIR) continue;
76
+ const abs = path.join(paths.dataDir, rel);
77
+ let exists = false;
78
+ try {
79
+ exists = fs.existsSync(guard.assert(abs));
80
+ } catch {
81
+ exists = false;
82
+ }
83
+ plan.push({ target: t, exists });
84
+ }
85
+ if (all) {
86
+ plan.push({ target: { dir: SETTINGS_DIR, note: "\u7528\u6237\u8BBE\u5B9A\uFF08\u9690\u79C1\u5BAA\u7AE0/\u95F8\u95E8\u53C2\u6570\uFF09\u2014\u2014\u4EC5 --all \u8FDE\u5E26" }, exists: fs.existsSync(paths.settingsDir) });
87
+ }
88
+ return plan;
89
+ }
90
+ function executeBurn(guard, paths, opts = {}) {
91
+ const burned = [];
92
+ const missing = [];
93
+ const passes = opts.shredPasses ?? 3;
94
+ for (const t of BURN_LIST) {
95
+ const rel = t.file ?? t.dir;
96
+ const abs = path.join(paths.dataDir, rel);
97
+ let absCanon;
98
+ try {
99
+ absCanon = guard.assert(abs);
100
+ } catch {
101
+ missing.push(rel);
102
+ continue;
103
+ }
104
+ if (t.file) {
105
+ if (fs.existsSync(absCanon)) {
106
+ shredFileSync(absCanon, passes);
107
+ burned.push(rel);
108
+ } else {
109
+ missing.push(rel);
110
+ }
111
+ } else if (fs.existsSync(absCanon)) {
112
+ fs.rmSync(absCanon, { recursive: true, force: true });
113
+ burned.push(rel);
114
+ } else {
115
+ missing.push(rel);
116
+ }
117
+ }
118
+ if (opts.all) {
119
+ fs.rmSync(paths.settingsDir, { recursive: true, force: true });
120
+ burned.push(SETTINGS_DIR);
121
+ }
122
+ appendAuditLine(guard.assert(path.join(paths.dataDir, "profile_journal.jsonl")), {
123
+ event: "BURN_EVENT",
124
+ burned: burned.length,
125
+ missing: missing.length,
126
+ settingsPreserved: !opts.all
127
+ });
128
+ return { burned, missing };
129
+ }
130
+
131
+ // src/cli/index.ts
132
+ function usage() {
133
+ return [
134
+ "usage: heartbeat <command> [args]",
135
+ "",
136
+ "commands:",
137
+ " status workspace paths + policy summary",
138
+ " seeds add <text> [--tag t] [--source s] [--topic x] [--confidence n]",
139
+ " seeds list [--archived] list active (or archived) seeds",
140
+ " seeds surface <id> count a surfacing (rule 1 accounting)",
141
+ " seeds archive <id> deliberate retirement (completed)",
142
+ " seeds gc run deterministic eviction rules 1-3",
143
+ " seeds stats pool status JSON",
144
+ " ledger add <text> register an open item",
145
+ " ledger list [--pending] list entries",
146
+ " ledger done <id|substring> mark an entry done",
147
+ " ledger open open ledger.md in the default editor",
148
+ " logs cleanup [--dry-run] apply log retention now",
149
+ " browse status browse state summary (watch + wander)",
150
+ " browse dry wander adjudication with forced noon window",
151
+ " browse watch run watchlist check now (network)",
152
+ " browse done <focus> CODE-side registration after a wander visit",
153
+ " bind list list session bindings (D13)",
154
+ " bind add <sessionId> [--observe] bind a session (deliver by default)",
155
+ " bind remove <sessionId> unbind a session",
156
+ " sessions list enumerate persisted sessions (ids)",
157
+ " notify check|register|send toast channel (D12: hint only, no body)",
158
+ " profile list [--all] current valid (or all incl. expired) entries",
159
+ " profile export decrypted Markdown export to data/exports/",
160
+ " profile verify journal replay vs disk (report only)",
161
+ " profile rebuild [--check] rebuild materialized view from journal",
162
+ " profile wipe wipe profile data (asks --yes)",
163
+ " burn [--yes] [--all] shred runtime data (settings kept unless --all)",
164
+ " preset status bundled agent preset: target dir + installed?",
165
+ " preset install [--force] install the bundled preset (never overwrites unless --force)"
166
+ ].join("\n");
167
+ }
168
+ function readStdinText() {
169
+ try {
170
+ return fs2.readFileSync(0, "utf8").trim();
171
+ } catch {
172
+ return "";
173
+ }
174
+ }
175
+ function flag(argv, name) {
176
+ const i = argv.indexOf(name);
177
+ return i > -1 ? argv[i + 1] : void 0;
178
+ }
179
+ async function main(argv) {
180
+ const [cmd, sub, ...rest] = argv;
181
+ if (!cmd || cmd === "help" || cmd === "--help") {
182
+ console.log(usage());
183
+ return 0;
184
+ }
185
+ const paths = initWorkspace();
186
+ const guard = createPathGuard(paths.dataDir);
187
+ const policy = loadPolicy(guard, paths.configDir, paths.settingsDir);
188
+ const seedsFile = seedsFilePath(paths.dataDir);
189
+ const ledgerFile = ledgerFilePath(paths.dataDir);
190
+ const decisionLog = path2.join(paths.logsDir, "heartbeat.jsonl");
191
+ switch (cmd) {
192
+ case "status": {
193
+ console.log(JSON.stringify({
194
+ dataDir: paths.dataDir,
195
+ heartbeatIntervalMin: policy.heartbeat.intervalMin,
196
+ maxDailySend: policy.gate.maxDailySend,
197
+ seedsMaxActive: policy.seeds.maxActive,
198
+ psyEnabled: policy.profile.psyEnabled
199
+ }, null, 2));
200
+ return 0;
201
+ }
202
+ case "seeds": {
203
+ switch (sub) {
204
+ case "add": {
205
+ let text = rest[0]?.trim() ?? "";
206
+ if (text === "-") text = readStdinText();
207
+ if (!text) {
208
+ console.error("usage: seeds add <text|-> [--tag t] [--source s] [--topic x] [--confidence n]");
209
+ return 1;
210
+ }
211
+ const result = addSeed(guard, seedsFile, policy, {
212
+ text,
213
+ tag: flag(rest, "--tag"),
214
+ source: flag(rest, "--source"),
215
+ topic: flag(rest, "--topic"),
216
+ confidence: flag(rest, "--confidence") ? Number(flag(rest, "--confidence")) : void 0
217
+ });
218
+ if (result.kind === "duplicate") {
219
+ console.log(`DUPLICATE: active seed ${result.seed.id} has the same text`);
220
+ return 0;
221
+ }
222
+ 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}]` : ""));
223
+ return 0;
224
+ }
225
+ case "list": {
226
+ const db = loadPool(guard, seedsFile);
227
+ const items = rest.includes("--archived") ? archivedSeeds(db) : activeSeeds(db);
228
+ for (const s of items) {
229
+ const ageDays = Math.floor((Date.now() - Date.parse(s.bornAt)) / 864e5);
230
+ console.log(`${s.id} [${s.tag}/${s.source}] d${ageDays} used:${s.used}${s.protected ? " *" : ""} ${s.text.slice(0, 60)}`);
231
+ }
232
+ if (items.length === 0) console.log("(empty)");
233
+ return 0;
234
+ }
235
+ case "surface": {
236
+ const s = surfaceSeed(guard, seedsFile, policy, rest[0] ?? "");
237
+ if (!s) {
238
+ console.log("NOT_FOUND");
239
+ return 1;
240
+ }
241
+ console.log(`SURFACED ${s.id} used:${s.used}${s.status === "archived" ? " -> archived (consumed)" : ""}`);
242
+ return 0;
243
+ }
244
+ case "archive": {
245
+ const s = archiveSeedById(guard, seedsFile, rest[0] ?? "");
246
+ if (!s) {
247
+ console.log("NOT_FOUND");
248
+ return 1;
249
+ }
250
+ console.log(`ARCHIVED ${s.id}`);
251
+ return 0;
252
+ }
253
+ case "gc": {
254
+ const report = gcPool(guard, seedsFile, policy);
255
+ console.log(`GC consumed:${report.consumed} expired:${report.expired} cold_bench:${report.coldBench} -> active ${report.activeAfter}/${policy.seeds.maxActive}`);
256
+ return 0;
257
+ }
258
+ case "stats": {
259
+ const db = loadPool(guard, seedsFile);
260
+ console.log(JSON.stringify({
261
+ active: activeSeeds(db).length,
262
+ archived: archivedSeeds(db).length,
263
+ cap: policy.seeds.maxActive,
264
+ seq: db.seq
265
+ }, null, 2));
266
+ return 0;
267
+ }
268
+ default:
269
+ console.error(`unknown seeds subcommand: ${sub}`);
270
+ return 1;
271
+ }
272
+ }
273
+ case "ledger": {
274
+ switch (sub) {
275
+ case "add": {
276
+ let text = rest.join(" ").trim();
277
+ if (text === "-") text = readStdinText();
278
+ if (!text) {
279
+ console.error("usage: ledger add <text|->");
280
+ return 1;
281
+ }
282
+ const e = appendEntry(guard, ledgerFile, text);
283
+ console.log(`OPEN #${e.id} ${e.text.slice(0, 60)}`);
284
+ return 0;
285
+ }
286
+ case "list": {
287
+ const { entries } = readLedger(guard, ledgerFile);
288
+ const items = rest.includes("--pending") ? scanPending(guard, ledgerFile) : entries;
289
+ for (const e of items) console.log(`${e.status === "open" ? " " : "x"} #${e.id} ${e.date} ${e.text.slice(0, 60)}`);
290
+ if (items.length === 0) console.log("(empty)");
291
+ return 0;
292
+ }
293
+ case "done": {
294
+ const key = rest.join(" ").trim();
295
+ if (!key) {
296
+ console.error("usage: ledger done <id|substring>");
297
+ return 1;
298
+ }
299
+ const e = markDone(guard, ledgerFile, key);
300
+ if (!e) {
301
+ console.log("NOT_FOUND");
302
+ return 1;
303
+ }
304
+ console.log(`DONE #${e.id}`);
305
+ return 0;
306
+ }
307
+ case "open": {
308
+ const f = ledgerFile;
309
+ if (!fs2.existsSync(f)) {
310
+ console.log(`(ledger will be created at ${f})`);
311
+ }
312
+ spawn("cmd", ["/c", "start", "", f], { detached: true, stdio: "ignore" }).unref();
313
+ console.log(`opened ${f}`);
314
+ return 0;
315
+ }
316
+ default:
317
+ console.error(`unknown ledger subcommand: ${sub}`);
318
+ return 1;
319
+ }
320
+ }
321
+ case "logs": {
322
+ if (sub !== "cleanup") {
323
+ console.error("usage: logs cleanup [--dry-run]");
324
+ return 1;
325
+ }
326
+ const dry = rest.includes("--dry-run");
327
+ const envPulse = path2.join(paths.logsDir, "envpulse.jsonl");
328
+ const cut1 = policy.retention.envPulseHours * 36e5;
329
+ const cut2 = policy.retention.decisionLogDays * 864e5;
330
+ if (dry) {
331
+ console.log(`(dry-run) would prune ${envPulse} to ${policy.retention.envPulseHours}h and ${decisionLog} to ${policy.retention.decisionLogDays}d`);
332
+ return 0;
333
+ }
334
+ const a = pruneAuditFile(envPulse, cut1);
335
+ const b = pruneAuditFile(decisionLog, cut2);
336
+ appendAuditLine(decisionLog, { event: "retention", pruned_envpulse: a, pruned_decision: b });
337
+ console.log(`PRUNED envpulse:${a} decision:${b}`);
338
+ return 0;
339
+ }
340
+ case "browse": {
341
+ switch (sub) {
342
+ case "status": {
343
+ const st = browseStatus(guard, paths);
344
+ const interests = loadInterests(paths);
345
+ console.log(JSON.stringify({
346
+ watchTargets: loadWatchlist(paths).targets?.length ?? 0,
347
+ lastCheckAt: st.last_check_at ? new Date(st.last_check_at).toISOString() : null,
348
+ lastWanderAt: st.wander.last_wander_at ? new Date(st.wander.last_wander_at).toISOString() : null,
349
+ focusCount: st.wander.focusCount,
350
+ interestCount: interests.interests?.length ?? 0
351
+ }, null, 2));
352
+ return 0;
353
+ }
354
+ case "dry": {
355
+ const forced = /* @__PURE__ */ new Date();
356
+ forced.setHours(12, 0, 0, 0);
357
+ const advice = adviseWander(guard, paths, policy, forced);
358
+ console.log(JSON.stringify(advice, null, 2));
359
+ return 0;
360
+ }
361
+ case "watch": {
362
+ const report = await checkWatchlist(guard, paths, { throttleOk: true });
363
+ console.log(JSON.stringify({ checked: report.checked, updates: report.items.length, errors: report.errors }, null, 2));
364
+ for (const item of report.items) console.log(`NEWS: ${item.text}`);
365
+ return 0;
366
+ }
367
+ case "done": {
368
+ const focus = rest.join(" ").trim();
369
+ if (!focus) {
370
+ console.error("usage: browse done <focus>");
371
+ return 1;
372
+ }
373
+ const r = completeWander(guard, paths, focus);
374
+ console.log(JSON.stringify(r));
375
+ return 0;
376
+ }
377
+ default:
378
+ console.error(`unknown browse subcommand: ${sub}`);
379
+ return 1;
380
+ }
381
+ }
382
+ case "notify": {
383
+ switch (sub) {
384
+ case "check":
385
+ console.log(ensureRegistered(paths) ? "REGISTERED: yes" : "REGISTERED: no");
386
+ return 0;
387
+ case "register":
388
+ ensureRegistered(paths);
389
+ console.log("register attempted");
390
+ return 0;
391
+ case "send":
392
+ console.log(sendNewMessageHint(paths) ? "SENT" : "FAILED");
393
+ return 0;
394
+ default:
395
+ console.error("usage: notify check|register|send");
396
+ return 1;
397
+ }
398
+ }
399
+ case "profile": {
400
+ const doc = loadProfile(guard, profileFilePath(paths.dataDir));
401
+ const all = rest.includes("--all");
402
+ switch (sub) {
403
+ case "list": {
404
+ let n = 0;
405
+ for (const p of ["interest", "projects", "comm", "psy"]) {
406
+ for (const e of doc.partitions[p].entries) {
407
+ if (!all && e.validTo !== null) continue;
408
+ n += 1;
409
+ 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)}`);
410
+ }
411
+ }
412
+ if (n === 0) console.log("(empty)");
413
+ return 0;
414
+ }
415
+ case "export": {
416
+ const lines = [`# \u753B\u50CF\u5BFC\u51FA ${(/* @__PURE__ */ new Date()).toISOString()}`, ""];
417
+ for (const p of ["interest", "projects", "comm", "psy"]) {
418
+ lines.push(`## ${p}`);
419
+ for (const e of doc.partitions[p].entries) {
420
+ if (e.validTo !== null) continue;
421
+ lines.push(`- [${e.topic}/${e.subTopic}] ${e.content} (conf ${e.confidence.toFixed(2)}, ${e.temporal})`);
422
+ }
423
+ }
424
+ const out = path2.join(paths.exportsDir, `profile-export-${Date.now()}.md`);
425
+ writeText(guard, out, lines.join("\n") + "\n");
426
+ console.log(`EXPORTED: ${out}`);
427
+ return 0;
428
+ }
429
+ case "verify": {
430
+ const r = verifyProfile(guard, paths.dataDir);
431
+ console.log(JSON.stringify(r, null, 2));
432
+ return r.ok ? 0 : 1;
433
+ }
434
+ case "rebuild": {
435
+ const r = rebuildProfile(guard, paths.dataDir, { check: rest.includes("--check") });
436
+ console.log(JSON.stringify(r, null, 2));
437
+ return r.ok ? 0 : 1;
438
+ }
439
+ case "wipe": {
440
+ if (!rest.includes("--yes")) {
441
+ console.log("REFUSED: add --yes to wipe profile data (profile.json/inbox/journal)");
442
+ return 1;
443
+ }
444
+ for (const f of ["profile.json", "profile_inbox.jsonl", "profile_journal.jsonl"]) {
445
+ const abs = guard.assert(path2.join(paths.dataDir, f));
446
+ if (fs2.existsSync(abs)) fs2.rmSync(abs, { force: true });
447
+ }
448
+ console.log("WIPED (settings preserved; use burn for full shredding)");
449
+ return 0;
450
+ }
451
+ default:
452
+ console.error(`unknown profile subcommand: ${sub}`);
453
+ return 1;
454
+ }
455
+ }
456
+ case "burn": {
457
+ const yes = rest.includes("--yes");
458
+ const all = rest.includes("--all");
459
+ const plan = planBurn(guard, paths, all);
460
+ console.log("\u9884\u6F14\uFF08\u4E0D\u4F1A\u6267\u884C\uFF09:");
461
+ for (const p of plan) console.log(` [${p.exists ? "\u5B58\u5728" : "\u65E0 "}] ${p.target.file ?? p.target.dir} ${p.target.note}`);
462
+ if (!yes) {
463
+ 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");
464
+ return 0;
465
+ }
466
+ const result = executeBurn(guard, paths, { all });
467
+ console.log(`BURNED: ${result.burned.length} \u9879\uFF1BMISSING: ${result.missing.length} \u9879`);
468
+ return 0;
469
+ }
470
+ case "sessions": {
471
+ const root = path2.join(os.homedir(), ".dsh", "sessions");
472
+ let found = 0;
473
+ if (fs2.existsSync(root)) {
474
+ let own = null;
475
+ try {
476
+ own = JSON.parse(loadEncryptedText(guard, path2.join(paths.dataDir, "gate.json")) ?? "{}").sessionId ?? null;
477
+ } catch {
478
+ }
479
+ for (const slug of fs2.readdirSync(root)) {
480
+ for (const id of fs2.readdirSync(path2.join(root, slug))) {
481
+ found += 1;
482
+ const mark = id === own ? " \u2190 \u5FC3\u8DF3\u6B63\u8EAB" : "";
483
+ console.log(`${id} [${slug}]${mark}`);
484
+ }
485
+ }
486
+ }
487
+ if (found === 0) console.log("(no persisted sessions found)");
488
+ return 0;
489
+ }
490
+ case "bind": {
491
+ const { loadBindings, addBinding, removeBinding, bindingsFilePath } = await import("../bindings-XPPSKILN.js");
492
+ switch (sub) {
493
+ case "list": {
494
+ const data = loadBindings(guard, paths.settingsDir);
495
+ let own = null;
496
+ try {
497
+ own = JSON.parse(loadEncryptedText(guard, path2.join(paths.dataDir, "gate.json")) ?? "{}").sessionId ?? null;
498
+ } catch {
499
+ }
500
+ 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`);
501
+ for (const b of data.bindings) {
502
+ console.log(`${b.sessionId} deliver:${b.deliver ? "\u221A" : "\xD7"} observe:${b.observe ? "\u221A" : "\xD7"}`);
503
+ }
504
+ if (data.bindings.length === 0 && !own) console.log("(no bindings \u2014 expressions stay in the dedicated heartbeat session)");
505
+ return 0;
506
+ }
507
+ case "add": {
508
+ const id = rest[0];
509
+ if (!id || !id.startsWith("session-")) {
510
+ console.error("usage: bind add <sessionId> [--observe] [--no-deliver] (see: sessions list)");
511
+ return 1;
512
+ }
513
+ const observe = rest.includes("--observe") || rest.includes("--observe-only");
514
+ const deliver = !rest.includes("--no-deliver") && !rest.includes("--observe-only");
515
+ const b = addBinding(guard, paths.settingsDir, id, { deliver, observe });
516
+ console.log(`BOUND ${b.sessionId} deliver:${b.deliver} observe:${b.observe}`);
517
+ return 0;
518
+ }
519
+ case "remove": {
520
+ const id = rest[0];
521
+ if (!id) {
522
+ console.error("usage: bind remove <sessionId>");
523
+ return 1;
524
+ }
525
+ console.log(removeBinding(guard, paths.settingsDir, id) ? `UNBOUND ${id}` : "NOT_FOUND");
526
+ try {
527
+ const state = JSON.parse(loadEncryptedText(guard, path2.join(paths.dataDir, "gate.json")) ?? "{}");
528
+ if (state.sessionId === id) {
529
+ fs2.rmSync(guard.assert(path2.join(paths.dataDir, "gate.json")), { force: true });
530
+ 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");
531
+ appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "home_reset", oldSessionId: id });
532
+ }
533
+ } catch {
534
+ }
535
+ return 0;
536
+ }
537
+ default:
538
+ console.error("usage: bind list | add <sessionId> | remove <sessionId>");
539
+ return 1;
540
+ }
541
+ }
542
+ case "preset": {
543
+ const id = flag(argv, "--id") ?? BUNDLED_PRESET_ID;
544
+ const root = conventionalUserPresetRoot();
545
+ const status = presetStatus(import.meta.url, id, root);
546
+ if (sub === void 0 || sub === "status") {
547
+ console.log(`preset id : ${status.id}`);
548
+ console.log(`installed : ${status.installed ? "yes" : "no"}`);
549
+ console.log(`target dir : ${status.dir}`);
550
+ console.log(`bundled at : ${status.bundledDir ?? "(not found next to the plugin)"}`);
551
+ if (status.installed) {
552
+ console.log(
553
+ `composition : ${status.compositionMatches ? "matches the bundled template" : "differs from the bundled template (hand-edited, or an older version)"}`
554
+ );
555
+ }
556
+ if (!status.installed) {
557
+ console.log("hint: the plugin installs this itself on the next DSH start (installPreset=true, default);");
558
+ console.log(" or run `preset install` now.");
559
+ }
560
+ return status.installed ? 0 : 1;
561
+ }
562
+ if (sub === "install") {
563
+ const result = installBundledPreset({
564
+ moduleUrl: import.meta.url,
565
+ id,
566
+ root,
567
+ force: rest.includes("--force")
568
+ });
569
+ console.log(describeInstall(result));
570
+ return result.action === "error" ? 1 : 0;
571
+ }
572
+ console.error("usage: preset status | install [--force] [--id <presetId>]");
573
+ return 1;
574
+ }
575
+ default:
576
+ console.error(`unknown command: ${cmd}`);
577
+ console.log(usage());
578
+ return 1;
579
+ }
580
+ }
581
+ if (process.argv[1] && import.meta.url === new URL(`file:///${process.argv[1].replace(/\\/g, "/")}`).href) {
582
+ void main(process.argv.slice(2)).then((code) => {
583
+ process.exitCode = code;
584
+ });
585
+ }
586
+ export {
587
+ main
588
+ };
589
+ //# sourceMappingURL=index.js.map