volute 0.43.1 → 0.45.1

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 (132) hide show
  1. package/dist/{activity-events-UISXY4Y5.js → activity-events-OMCSM7XW.js} +3 -3
  2. package/dist/{ai-service-FBCCEYF5.js → ai-service-534WUSDL.js} +4 -4
  3. package/dist/{archive-I32WHCJB.js → archive-6R4TZMYI.js} +3 -3
  4. package/dist/{auth-3RIG6GKN.js → auth-FRZJVQCS.js} +4 -4
  5. package/dist/avatar-image-BNWEXSA2.js +16 -0
  6. package/dist/backup-OZXHL3JS.js +381 -0
  7. package/dist/{chat-SDSX5TVV.js → chat-I4EDDIQC.js} +2 -2
  8. package/dist/{chunk-AD46AYZQ.js → chunk-2BONPXAF.js} +1 -1
  9. package/dist/{chunk-ONM2AZFL.js → chunk-322KE457.js} +14 -2
  10. package/dist/{chunk-PGPLCJPK.js → chunk-3ERIL673.js} +1 -1
  11. package/dist/{chunk-QQ2MVQU3.js → chunk-5AUKCNPE.js} +10 -10
  12. package/dist/{chunk-XDTKJNW5.js → chunk-5GT7URXJ.js} +1 -1
  13. package/dist/{chunk-DMUPZQWP.js → chunk-77BVGCTZ.js} +8 -8
  14. package/dist/{chunk-EFL5UOY4.js → chunk-CYI534DJ.js} +3 -3
  15. package/dist/{chunk-57LBQ7JX.js → chunk-FHWUQZXE.js} +1 -1
  16. package/dist/{chunk-YAMO2U3N.js → chunk-H7IYFRAL.js} +1 -1
  17. package/dist/{chunk-M3PGGSBH.js → chunk-HVU45COX.js} +1 -1
  18. package/dist/{chunk-O3DXAR44.js → chunk-IEWZ7VD7.js} +2 -2
  19. package/dist/{chunk-DE4EKQFH.js → chunk-IUXTYXFI.js} +3 -3
  20. package/dist/{chunk-K4EHBHPE.js → chunk-JSRMEY52.js} +7 -5
  21. package/dist/{chunk-QHG4OMZL.js → chunk-KDLYIECM.js} +37 -32
  22. package/dist/{chunk-EE7SZJON.js → chunk-KP3KWNG6.js} +2 -2
  23. package/dist/chunk-KVKP6VQ5.js +318 -0
  24. package/dist/{chunk-JVK6QQIC.js → chunk-LAXUUY7O.js} +2 -2
  25. package/dist/{chunk-C6ESRQZ7.js → chunk-OXXTPILE.js} +1 -1
  26. package/dist/{chunk-ATEX3ZPG.js → chunk-Q2XBKQ2D.js} +2 -2
  27. package/dist/{chunk-GZJFC76M.js → chunk-QMJJWTNL.js} +1 -1
  28. package/dist/chunk-SQL7M4HJ.js +120 -0
  29. package/dist/{chunk-YSV3VJQK.js → chunk-TSK5PI4L.js} +1 -1
  30. package/dist/{chunk-4SVONUTC.js → chunk-U4FNXDEJ.js} +350 -207
  31. package/dist/{chunk-2HIVYZDB.js → chunk-UMI4UIJG.js} +1 -1
  32. package/dist/{chunk-Y6O35D7K.js → chunk-V4GYPD5L.js} +41 -16
  33. package/dist/{chunk-KINGVQSR.js → chunk-Y6OPHHGM.js} +5 -5
  34. package/dist/{chunk-SPLPKBES.js → chunk-ZIR7HSAX.js} +6 -9
  35. package/dist/{chunk-7G2EEYXX.js → chunk-ZMIMSIIT.js} +2 -2
  36. package/dist/{chunk-L3IV23ZG.js → chunk-ZV7MFISX.js} +2 -0
  37. package/dist/cli.js +53 -22
  38. package/dist/{cloud-sync-KOEBUFW5.js → cloud-sync-Y7LQPRE3.js} +16 -16
  39. package/dist/{conversations-67WWW25C.js → conversations-G4LYMHAA.js} +4 -4
  40. package/dist/{create-7W3LV3J7.js → create-2YY7IPMS.js} +1 -1
  41. package/dist/{daemon-restart-EZXB73W7.js → daemon-restart-ABP54YN6.js} +7 -7
  42. package/dist/daemon.js +679 -478
  43. package/dist/{db-CZV62BOQ.js → db-JFLCJCUG.js} +2 -2
  44. package/dist/{db-IJL6B26S.js → db-LZTLA7XC.js} +1 -1
  45. package/dist/delivery-manager-R5STGLXB.js +32 -0
  46. package/dist/{delivery-router-HXBHYHJM.js → delivery-router-6DE3BPHO.js} +3 -3
  47. package/dist/{down-RRNYIGCU.js → down-XCAZXWLB.js} +5 -5
  48. package/dist/echo-text-NQXYYYCU.js +32 -0
  49. package/dist/{exec-SJK7VUTC.js → exec-4GWHAHS3.js} +3 -3
  50. package/dist/{export-7LPCIB5J.js → export-ID7YUSLZ.js} +3 -3
  51. package/dist/{extensions-PQPZT6LS.js → extensions-AYSPKE33.js} +16 -16
  52. package/dist/{files-LV7VAKPC.js → files-BQ2WB5KC.js} +3 -3
  53. package/dist/{import-XPECCRQ7.js → import-SKID37QX.js} +6 -6
  54. package/dist/{isolation-5AYEY55P.js → isolation-5MB3J73F.js} +3 -3
  55. package/dist/{login-KJSN4JDA.js → login-LM4NF4ZB.js} +2 -2
  56. package/dist/{logout-TMJGUQV2.js → logout-CTJVIZGA.js} +2 -2
  57. package/dist/message-delivery-IUAXRPFE.js +34 -0
  58. package/dist/{mind-G3WEWDM4.js → mind-KLHC7XYR.js} +6 -6
  59. package/dist/{mind-activity-tracker-JHAT6456.js → mind-activity-tracker-BGHJE7XA.js} +4 -4
  60. package/dist/mind-service-BF2LUQMH.js +36 -0
  61. package/dist/{package-24TL6DTF.js → package-BMHV7UHN.js} +1 -1
  62. package/dist/{registry-7LQHTRUX.js → registry-NUJAGLQJ.js} +2 -2
  63. package/dist/{sandbox-SJH3XOPZ.js → sandbox-72MWV4JL.js} +4 -4
  64. package/dist/scheduler-YHJRRV6E.js +30 -0
  65. package/dist/{schema-LRUTFOER.js → schema-PVNG3PQM.js} +1 -1
  66. package/dist/{seed-HHSJTM6O.js → seed-4MKQ5QKG.js} +1 -1
  67. package/dist/{seed-cmd-UPQY35RG.js → seed-cmd-LCXMR7VV.js} +2 -2
  68. package/dist/{seed-create-MDRMXMSB.js → seed-create-N2BF5LQX.js} +3 -3
  69. package/dist/{seed-sprout-4M7IQWMH.js → seed-sprout-6ZDP23YI.js} +7 -7
  70. package/dist/{send-ZBZWIH5V.js → send-O6W53VBM.js} +37 -42
  71. package/dist/{service-4EVZKGSA.js → service-AQVPUANO.js} +4 -4
  72. package/dist/{service-install-SHRM6NHK.js → service-install-4QNUDKLM.js} +5 -5
  73. package/dist/{setup-ZD2LMBR3.js → setup-3O6O2JZ7.js} +7 -7
  74. package/dist/{setup-25MMJ3J4.js → setup-73YBJZQL.js} +3 -3
  75. package/dist/skills/memory/SKILL.md +2 -1
  76. package/dist/skills/orientation/SKILL.md +1 -1
  77. package/dist/skills/plan-coordinator/SKILL.md +2 -2
  78. package/dist/skills/volute-admin/SKILL.md +9 -6
  79. package/dist/skills/volute-mind/SKILL.md +15 -16
  80. package/dist/skills/volute-mind/references/extensions.md +5 -3
  81. package/dist/skills/volute-mind/references/integrations.md +2 -2
  82. package/dist/skills/volute-mind/references/routing.md +19 -23
  83. package/dist/skills/volute-mind/references/variants.md +10 -8
  84. package/dist/{skills-4KQ377GN.js → skills-ND4ZFYUW.js} +5 -5
  85. package/dist/sleep-manager-YAHDOIU5.js +34 -0
  86. package/dist/{spirit-53DQPRNR.js → spirit-II3HOERM.js} +7 -7
  87. package/dist/{sprout-YKM2M6D6.js → sprout-4KUG76AQ.js} +1 -1
  88. package/dist/{src-O4PRLMKM.js → src-JAOVXO5W.js} +1 -0
  89. package/dist/{src-3EGHHKZK.js → src-XFY3XDD6.js} +16 -11
  90. package/dist/{src-L2Z7X5UT.js → src-YH6PQ7LJ.js} +264 -52
  91. package/dist/{status-WTU2WHXY.js → status-E5DQNCAO.js} +5 -5
  92. package/dist/system-chat-E65T2HSW.js +34 -0
  93. package/dist/{tailscale-Q7ZJL7S7.js → tailscale-QAWVKQKU.js} +2 -2
  94. package/dist/{turn-tracker-JZYERJ76.js → turn-tracker-QQIVVXBI.js} +7 -5
  95. package/dist/{up-LFZA4GI5.js → up-LG56MPBI.js} +6 -6
  96. package/dist/{update-EJ7Q77ZX.js → update-7TYVH5FM.js} +5 -5
  97. package/dist/{update-check-ACYYNKTW.js → update-check-VSCSJ7KX.js} +3 -3
  98. package/dist/{upgrade-37DAGF6H.js → upgrade-NWAZ7MLD.js} +1 -1
  99. package/dist/{version-notify-SRQJCQEK.js → version-notify-CODJOUW2.js} +17 -17
  100. package/dist/web-assets/assets/index-B1JBQv2m.css +1 -0
  101. package/dist/web-assets/assets/index-Dac11sKB.js +86 -0
  102. package/dist/web-assets/ext-theme.css +5 -0
  103. package/dist/web-assets/index.html +2 -2
  104. package/package.json +1 -1
  105. package/packages/extensions/notes/dist/ui/assets/{index-BbgANhFm.js → index-D0-DgYyj.js} +1 -1
  106. package/packages/extensions/notes/dist/ui/index.html +1 -1
  107. package/packages/extensions/notes/skills/notes/SKILL.md +31 -3
  108. package/packages/extensions/pages/dist/ui/assets/{index-Dxk-DYrf.js → index-CQjEQg1r.js} +1 -1
  109. package/packages/extensions/pages/dist/ui/index.html +1 -1
  110. package/packages/extensions/plan/skills/plan/SKILL.md +1 -1
  111. package/templates/_base/.init/.config/prompts.json +1 -1
  112. package/templates/_base/.init/.local/hooks/startup-context.ts +21 -4
  113. package/templates/_base/.init/MEMORY.md +2 -1
  114. package/templates/_base/home/VOLUTE.md +6 -9
  115. package/templates/_base/src/lib/startup.ts +1 -1
  116. package/templates/claude/.init/CLAUDE.md +1 -1
  117. package/templates/claude/src/agent.ts +30 -3
  118. package/templates/claude/src/lib/stream-consumer.ts +7 -4
  119. package/templates/codex/.init/AGENTS.md +4 -2
  120. package/templates/codex/src/agent.ts +13 -7
  121. package/templates/pi/.init/MINDS.md +4 -2
  122. package/templates/pi/src/lib/event-handler.ts +6 -2
  123. package/dist/delivery-manager-OMCCJEVP.js +0 -32
  124. package/dist/echo-text-KFSUCZXL.js +0 -32
  125. package/dist/message-delivery-HRGFNWO7.js +0 -34
  126. package/dist/mind-service-2C7AINBO.js +0 -36
  127. package/dist/scheduler-VON2TB52.js +0 -30
  128. package/dist/sleep-manager-AG5SQDDI.js +0 -34
  129. package/dist/system-chat-54RN3K67.js +0 -34
  130. package/dist/web-assets/assets/index-D7udpBCx.css +0 -1
  131. package/dist/web-assets/assets/index-DTDmEfPU.js +0 -79
  132. package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +0 -40
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  broadcast
4
- } from "./chunk-7G2EEYXX.js";
4
+ } from "./chunk-ZMIMSIIT.js";
5
5
  import {
6
6
  getDb
7
- } from "./chunk-YAMO2U3N.js";
7
+ } from "./chunk-H7IYFRAL.js";
8
8
  import {
9
9
  users
10
- } from "./chunk-L3IV23ZG.js";
10
+ } from "./chunk-ZV7MFISX.js";
11
11
 
12
12
  // packages/daemon/src/lib/auth.ts
13
13
  import { compareSync, hashSync } from "bcryptjs";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-T2TP6ZC6.js";
5
5
  import {
6
6
  voluteSystemDir
7
- } from "./chunk-YAMO2U3N.js";
7
+ } from "./chunk-H7IYFRAL.js";
8
8
 
9
9
  // packages/daemon/src/lib/bridges/bridges.ts
10
10
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  minds,
4
4
  schema_exports
5
- } from "./chunk-L3IV23ZG.js";
5
+ } from "./chunk-ZV7MFISX.js";
6
6
 
7
7
  // packages/daemon/src/lib/db.ts
8
8
  import { chmodSync, existsSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  publish
4
- } from "./chunk-7G2EEYXX.js";
4
+ } from "./chunk-ZMIMSIIT.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-T2TP6ZC6.js";
@@ -8,10 +8,10 @@ import {
8
8
  pollHealthDown,
9
9
  readDaemonConfig,
10
10
  stopService
11
- } from "./chunk-ATEX3ZPG.js";
11
+ } from "./chunk-Q2XBKQ2D.js";
12
12
  import {
13
13
  voluteSystemDir
14
- } from "./chunk-YAMO2U3N.js";
14
+ } from "./chunk-H7IYFRAL.js";
15
15
 
16
16
  // src/commands/down.ts
17
17
  import { existsSync, readFileSync, unlinkSync } from "fs";
@@ -7,14 +7,14 @@ import {
7
7
  modeLabel,
8
8
  pollHealth,
9
9
  startService
10
- } from "./chunk-ATEX3ZPG.js";
10
+ } from "./chunk-Q2XBKQ2D.js";
11
11
  import {
12
12
  readGlobalConfig
13
- } from "./chunk-ONM2AZFL.js";
13
+ } from "./chunk-322KE457.js";
14
14
  import {
15
15
  voluteHome,
16
16
  voluteSystemDir
17
- } from "./chunk-YAMO2U3N.js";
17
+ } from "./chunk-H7IYFRAL.js";
18
18
 
19
19
  // src/commands/up.ts
20
20
  import { spawn } from "child_process";
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  subscribe
4
- } from "./chunk-7G2EEYXX.js";
4
+ } from "./chunk-ZMIMSIIT.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-T2TP6ZC6.js";
8
8
  import {
9
9
  getDb
10
- } from "./chunk-YAMO2U3N.js";
10
+ } from "./chunk-H7IYFRAL.js";
11
11
  import {
12
12
  channels,
13
13
  conversationParticipants,
@@ -15,7 +15,7 @@ import {
15
15
  conversations,
16
16
  messages,
17
17
  users
18
- } from "./chunk-L3IV23ZG.js";
18
+ } from "./chunk-ZV7MFISX.js";
19
19
 
20
20
  // packages/daemon/src/lib/events/conversations.ts
21
21
  import { randomUUID } from "crypto";
@@ -470,7 +470,7 @@ async function joinChannel(conversationId, userId) {
470
470
  async function leaveChannel(conversationId, userId) {
471
471
  await removeParticipant(conversationId, userId);
472
472
  }
473
- async function getUnreadCounts(userId, conversationIds) {
473
+ async function getUnreadCounts(userId, conversationIds, username) {
474
474
  if (conversationIds.length === 0) return {};
475
475
  const db = await getDb();
476
476
  const rows = await db.select({
@@ -485,7 +485,9 @@ async function getUnreadCounts(userId, conversationIds) {
485
485
  ).where(
486
486
  and(
487
487
  inArray(messages.conversation_id, conversationIds),
488
- sql`${messages.id} > COALESCE(${conversationReads.last_read_message_id}, 0)`
488
+ sql`${messages.id} > COALESCE(${conversationReads.last_read_message_id}, 0)`,
489
+ // A user's own messages are never "unread" for them
490
+ sql`(${messages.sender_name} IS NULL OR ${messages.sender_name} != ${username})`
489
491
  )
490
492
  ).groupBy(messages.conversation_id);
491
493
  const result = {};
@@ -14,11 +14,13 @@ function initDb(db) {
14
14
  CREATE UNIQUE INDEX IF NOT EXISTS idx_pp_mind_file ON published_pages(mind, file);
15
15
  CREATE INDEX IF NOT EXISTS idx_pp_updated_at ON published_pages(updated_at);
16
16
  `);
17
- try {
18
- db.exec("ALTER TABLE published_pages ADD COLUMN author TEXT");
19
- } catch (err) {
20
- if (!(err instanceof Error) || !err.message.includes("duplicate column")) {
21
- throw err;
17
+ for (const column of ["author TEXT", "hash TEXT"]) {
18
+ try {
19
+ db.exec(`ALTER TABLE published_pages ADD COLUMN ${column}`);
20
+ } catch (err) {
21
+ if (!(err instanceof Error) || !err.message.includes("duplicate column")) {
22
+ throw err;
23
+ }
22
24
  }
23
25
  }
24
26
  }
@@ -63,32 +65,31 @@ function getSystemPages(db) {
63
65
  files: rows.map((r) => ({ file: r.file, updated_at: r.updated_at, author: r.author }))
64
66
  };
65
67
  }
66
- function syncSystemPages(db, htmlFiles, author) {
67
- const existing = new Set(
68
- db.prepare("SELECT file FROM published_pages WHERE mind = '_system'").all().map((r) => r.file)
68
+ function syncSystemPages(db, pages, author) {
69
+ const existing = new Map(
70
+ db.prepare("SELECT file, hash FROM published_pages WHERE mind = '_system'").all().map((r) => [r.file, r.hash])
69
71
  );
70
- const newSet = new Set(htmlFiles);
72
+ const newSet = new Set(pages.map((p) => p.file));
71
73
  db.exec("BEGIN");
72
74
  try {
73
- for (const file of htmlFiles) {
74
- if (existing.has(file)) {
75
+ for (const { file, hash } of pages) {
76
+ if (!existing.has(file)) {
77
+ db.prepare(
78
+ "INSERT INTO published_pages (mind, file, hash, author) VALUES ('_system', ?, ?, ?)"
79
+ ).run(file, hash, author ?? null);
80
+ } else if (existing.get(file) !== hash) {
75
81
  if (author) {
76
82
  db.prepare(
77
- "UPDATE published_pages SET updated_at = datetime('now'), author = ? WHERE mind = '_system' AND file = ?"
78
- ).run(author, file);
83
+ "UPDATE published_pages SET updated_at = datetime('now'), hash = ?, author = ? WHERE mind = '_system' AND file = ?"
84
+ ).run(hash, author, file);
79
85
  } else {
80
86
  db.prepare(
81
- "UPDATE published_pages SET updated_at = datetime('now') WHERE mind = '_system' AND file = ?"
82
- ).run(file);
87
+ "UPDATE published_pages SET updated_at = datetime('now'), hash = ? WHERE mind = '_system' AND file = ?"
88
+ ).run(hash, file);
83
89
  }
84
- } else {
85
- db.prepare("INSERT INTO published_pages (mind, file, author) VALUES ('_system', ?, ?)").run(
86
- file,
87
- author ?? null
88
- );
89
90
  }
90
91
  }
91
- for (const file of existing) {
92
+ for (const file of existing.keys()) {
92
93
  if (!newSet.has(file)) {
93
94
  db.prepare("DELETE FROM published_pages WHERE mind = '_system' AND file = ?").run(file);
94
95
  }
@@ -99,28 +100,32 @@ function syncSystemPages(db, htmlFiles, author) {
99
100
  throw err;
100
101
  }
101
102
  }
102
- function syncPublishedPages(db, mind, pageFiles) {
103
+ function syncPublishedPages(db, mind, pages) {
103
104
  const existing = new Map(
104
- db.prepare("SELECT file, updated_at FROM published_pages WHERE mind = ?").all(mind).map((r) => [r.file, r.updated_at])
105
+ db.prepare("SELECT file, hash FROM published_pages WHERE mind = ?").all(mind).map((r) => [r.file, r.hash])
105
106
  );
106
- const newSet = new Set(pageFiles);
107
+ const newSet = new Set(pages.map((p) => p.file));
107
108
  const added = [];
108
109
  const updated = [];
109
110
  const removed = [];
110
111
  db.exec("BEGIN");
111
112
  try {
112
- for (const file of pageFiles) {
113
- if (existing.has(file)) {
113
+ for (const { file, hash } of pages) {
114
+ if (!existing.has(file)) {
115
+ db.prepare("INSERT INTO published_pages (mind, file, hash) VALUES (?, ?, ?)").run(
116
+ mind,
117
+ file,
118
+ hash
119
+ );
120
+ added.push(file);
121
+ } else if (existing.get(file) !== hash) {
114
122
  db.prepare(
115
- "UPDATE published_pages SET updated_at = datetime('now') WHERE mind = ? AND file = ?"
116
- ).run(mind, file);
123
+ "UPDATE published_pages SET updated_at = datetime('now'), hash = ? WHERE mind = ? AND file = ?"
124
+ ).run(hash, mind, file);
117
125
  updated.push(file);
118
- } else {
119
- db.prepare("INSERT INTO published_pages (mind, file) VALUES (?, ?)").run(mind, file);
120
- added.push(file);
121
126
  }
122
127
  }
123
- for (const [file] of existing) {
128
+ for (const file of existing.keys()) {
124
129
  if (!newSet.has(file)) {
125
130
  db.prepare("DELETE FROM published_pages WHERE mind = ? AND file = ?").run(mind, file);
126
131
  removed.push(file);
@@ -3,10 +3,10 @@ import {
3
3
  LAUNCHD_PLIST_LABEL,
4
4
  LAUNCHD_PLIST_PATH,
5
5
  USER_SYSTEMD_UNIT
6
- } from "./chunk-ATEX3ZPG.js";
6
+ } from "./chunk-Q2XBKQ2D.js";
7
7
  import {
8
8
  resolveVoluteBin
9
- } from "./chunk-PGPLCJPK.js";
9
+ } from "./chunk-3ERIL673.js";
10
10
 
11
11
  // packages/daemon/src/lib/config/service-install.ts
12
12
  import { execFile } from "child_process";
@@ -0,0 +1,318 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ exec
4
+ } from "./chunk-3ERIL673.js";
5
+ import {
6
+ readGlobalConfig
7
+ } from "./chunk-322KE457.js";
8
+ import {
9
+ logger_default
10
+ } from "./chunk-T2TP6ZC6.js";
11
+ import {
12
+ getDb,
13
+ readAllMinds,
14
+ voluteHome,
15
+ voluteSystemDir
16
+ } from "./chunk-H7IYFRAL.js";
17
+
18
+ // packages/daemon/src/lib/backup/restic.ts
19
+ import { writeFileSync } from "fs";
20
+ import { resolve } from "path";
21
+ var RESTIC_INSTALL_HINT = "Install restic: `brew install restic` (macOS), `apt-get install restic` (Debian/Ubuntu), or see https://restic.net";
22
+ async function resticVersion() {
23
+ try {
24
+ const out = await exec("restic", ["version"]);
25
+ return out.trim();
26
+ } catch (err) {
27
+ if (err.code !== "ENOENT") {
28
+ logger_default.child("backup").warn("restic exists but failed to run", logger_default.errorData(err));
29
+ }
30
+ return null;
31
+ }
32
+ }
33
+ function resticEnv(config) {
34
+ if (!config.repository) throw new Error("Backup repository is not configured");
35
+ if (!config.password) throw new Error("Backup password is not configured");
36
+ return {
37
+ ...process.env,
38
+ ...config.env,
39
+ RESTIC_REPOSITORY: config.repository,
40
+ RESTIC_PASSWORD: config.password
41
+ };
42
+ }
43
+ async function runRestic(args, config, options) {
44
+ if (!await resticVersion()) {
45
+ throw new Error(`restic is not installed. ${RESTIC_INSTALL_HINT}`);
46
+ }
47
+ return exec("restic", args, { cwd: options?.cwd, env: resticEnv(config) });
48
+ }
49
+ var BASE_EXCLUDES = [
50
+ // dependency trees and build output — rehydrated via lockfiles
51
+ "node_modules",
52
+ ".venv",
53
+ "venv",
54
+ "__pycache__",
55
+ "dist",
56
+ // mind runtime state that is regenerated
57
+ ".variants",
58
+ ".worktrees",
59
+ ".mind/tmp",
60
+ ".npm",
61
+ // toolchains and caches minds install into their home. home/.local must NOT
62
+ // be excluded wholesale: .local/hooks and .local/bin hold mind-authored hooks
63
+ // and skill shims that are never regenerated — only its toolchain subdirs go.
64
+ "home/.cache",
65
+ "home/.npm",
66
+ "home/.rustup",
67
+ "home/.cargo",
68
+ "home/.local/share",
69
+ "home/.local/state",
70
+ "home/.local/lib",
71
+ "home/.local/pipx"
72
+ ];
73
+ function systemExcludes() {
74
+ const sys = voluteSystemDir();
75
+ return [
76
+ resolve(sys, "volute.db"),
77
+ resolve(sys, "volute.db-wal"),
78
+ resolve(sys, "volute.db-shm"),
79
+ `${resolve(sys, "extension-data")}/*/data.db`,
80
+ `${resolve(sys, "extension-data")}/*/data.db-wal`,
81
+ `${resolve(sys, "extension-data")}/*/data.db-shm`,
82
+ `${resolve(sys, "state")}/*/logs`,
83
+ resolve(sys, "daemon.log"),
84
+ resolve(sys, "daemon.pid"),
85
+ resolve(sys, "backup-excludes.txt")
86
+ ];
87
+ }
88
+ var SESSION_EXCLUDES = [
89
+ "home/.claude/projects",
90
+ "home/.claude/debug",
91
+ "home/.claude/telemetry",
92
+ "home/.claude/todos",
93
+ "home/.claude/session-env",
94
+ ".mind/codex/sessions",
95
+ ".mind/pi-sessions"
96
+ ];
97
+ function buildExcludePatterns(config) {
98
+ const patterns = [...BASE_EXCLUDES, ...systemExcludes()];
99
+ if (!config.includeSessions) patterns.push(...SESSION_EXCLUDES);
100
+ return patterns;
101
+ }
102
+ function writeExcludeFile(config) {
103
+ const path = resolve(voluteSystemDir(), "backup-excludes.txt");
104
+ writeFileSync(path, `${buildExcludePatterns(config).join("\n")}
105
+ `);
106
+ return path;
107
+ }
108
+
109
+ // packages/daemon/src/lib/backup/backup.ts
110
+ import {
111
+ existsSync,
112
+ mkdirSync,
113
+ readdirSync,
114
+ readFileSync,
115
+ renameSync,
116
+ rmSync,
117
+ writeFileSync as writeFileSync2
118
+ } from "fs";
119
+ import { dirname, resolve as resolve2 } from "path";
120
+ import { sql } from "drizzle-orm";
121
+ var blog = logger_default.child("backup");
122
+ var DEFAULT_KEEP = { daily: 7, weekly: 4, monthly: 12 };
123
+ function stateFilePath() {
124
+ return resolve2(voluteSystemDir(), "backup-state.json");
125
+ }
126
+ function readBackupState() {
127
+ const path = stateFilePath();
128
+ if (!existsSync(path)) return {};
129
+ try {
130
+ return JSON.parse(readFileSync(path, "utf-8"));
131
+ } catch (err) {
132
+ blog.warn("backup-state.json unreadable, treating as empty", logger_default.errorData(err));
133
+ return {};
134
+ }
135
+ }
136
+ function writeBackupState(state) {
137
+ try {
138
+ writeFileSync2(stateFilePath(), `${JSON.stringify(state, null, 2)}
139
+ `);
140
+ } catch (err) {
141
+ blog.error("failed to write backup-state.json", logger_default.errorData(err));
142
+ }
143
+ }
144
+ function stagingDir() {
145
+ return resolve2(voluteSystemDir(), "backup-staging");
146
+ }
147
+ async function stageDatabases() {
148
+ const staging = stagingDir();
149
+ rmSync(staging, { recursive: true, force: true });
150
+ mkdirSync(staging, { recursive: true });
151
+ const staged = [];
152
+ const mainTarget = resolve2(staging, "volute.db");
153
+ const db = await getDb();
154
+ await db.run(sql.raw(`VACUUM INTO '${mainTarget.replaceAll("'", "''")}'`));
155
+ staged.push(mainTarget);
156
+ const extDataDir = resolve2(voluteSystemDir(), "extension-data");
157
+ if (existsSync(extDataDir)) {
158
+ const { default: Database } = await import("libsql");
159
+ for (const id of readdirSync(extDataDir)) {
160
+ const dbPath = resolve2(extDataDir, id, "data.db");
161
+ if (!existsSync(dbPath)) continue;
162
+ const target = resolve2(staging, "extension-data", id, "data.db");
163
+ mkdirSync(dirname(target), { recursive: true });
164
+ const extDb = new Database(dbPath);
165
+ try {
166
+ extDb.exec(`VACUUM INTO '${target.replaceAll("'", "''")}'`);
167
+ staged.push(target);
168
+ } finally {
169
+ extDb.close();
170
+ }
171
+ }
172
+ }
173
+ return staged;
174
+ }
175
+ async function backupRoots() {
176
+ const roots = /* @__PURE__ */ new Set([voluteHome()]);
177
+ const mindsDir = process.env.VOLUTE_MINDS_DIR;
178
+ if (mindsDir && existsSync(mindsDir)) roots.add(resolve2(mindsDir));
179
+ for (const mind of await readAllMinds()) {
180
+ if (!mind.dir || !existsSync(mind.dir)) continue;
181
+ const dir = resolve2(mind.dir);
182
+ if (![...roots].some((root) => dir === root || dir.startsWith(`${root}/`))) {
183
+ roots.add(dir);
184
+ }
185
+ }
186
+ return [...roots];
187
+ }
188
+ var backupInFlight = false;
189
+ async function runBackup() {
190
+ if (backupInFlight) throw new Error("A backup is already running");
191
+ backupInFlight = true;
192
+ const started = Date.now();
193
+ try {
194
+ const config = readGlobalConfig().backup ?? {};
195
+ if (!config.repository || !config.password) {
196
+ throw new Error("Backup repository is not configured. Run: volute backup init");
197
+ }
198
+ blog.info("starting backup", { repository: config.repository });
199
+ await stageDatabases();
200
+ const roots = await backupRoots();
201
+ const excludeFile = writeExcludeFile(config);
202
+ const out = await runRestic(
203
+ [
204
+ "backup",
205
+ ...roots,
206
+ "--exclude-file",
207
+ excludeFile,
208
+ "--exclude-caches",
209
+ "--tag",
210
+ "volute",
211
+ "--json"
212
+ ],
213
+ config
214
+ );
215
+ const summary = parseBackupSummary(out, Date.now() - started);
216
+ const keep = { ...DEFAULT_KEEP, ...config.keep };
217
+ let pruneError;
218
+ try {
219
+ await runRestic(
220
+ [
221
+ "forget",
222
+ "--tag",
223
+ "volute",
224
+ "--keep-daily",
225
+ String(keep.daily),
226
+ "--keep-weekly",
227
+ String(keep.weekly),
228
+ "--keep-monthly",
229
+ String(keep.monthly),
230
+ "--prune"
231
+ ],
232
+ config
233
+ );
234
+ } catch (err) {
235
+ pruneError = err instanceof Error ? err.message : String(err);
236
+ blog.warn("retention prune failed (snapshot is intact)", logger_default.errorData(err));
237
+ }
238
+ writeBackupState({
239
+ lastRun: (/* @__PURE__ */ new Date()).toISOString(),
240
+ lastSnapshotId: summary.snapshotId,
241
+ lastDurationMs: summary.durationMs,
242
+ ...pruneError ? { pruneError } : {}
243
+ });
244
+ blog.info("backup complete", { snapshot: summary.snapshotId, ms: summary.durationMs });
245
+ return summary;
246
+ } catch (err) {
247
+ writeBackupState({
248
+ ...readBackupState(),
249
+ lastAttempt: (/* @__PURE__ */ new Date()).toISOString(),
250
+ lastError: err instanceof Error ? err.message : String(err)
251
+ });
252
+ throw err;
253
+ } finally {
254
+ backupInFlight = false;
255
+ }
256
+ }
257
+ function parseBackupSummary(output, durationMs) {
258
+ for (const line of output.trim().split("\n").reverse()) {
259
+ let msg;
260
+ try {
261
+ msg = JSON.parse(line);
262
+ } catch {
263
+ continue;
264
+ }
265
+ if (msg.message_type !== "summary") continue;
266
+ if (!msg.snapshot_id) throw new Error("restic summary is missing snapshot_id");
267
+ return {
268
+ snapshotId: msg.snapshot_id,
269
+ filesNew: msg.files_new ?? 0,
270
+ filesChanged: msg.files_changed ?? 0,
271
+ dataAdded: msg.data_added ?? 0,
272
+ totalFilesProcessed: msg.total_files_processed ?? 0,
273
+ durationMs
274
+ };
275
+ }
276
+ throw new Error("restic backup produced no summary");
277
+ }
278
+ async function listSnapshots() {
279
+ const config = readGlobalConfig().backup ?? {};
280
+ const out = await runRestic(["snapshots", "--tag", "volute", "--json"], config);
281
+ const parsed = JSON.parse(out || "[]");
282
+ return parsed.map((s) => ({
283
+ id: s.id,
284
+ short_id: s.short_id,
285
+ time: s.time,
286
+ hostname: s.hostname,
287
+ paths: s.paths
288
+ }));
289
+ }
290
+ async function initRepo(config) {
291
+ try {
292
+ await runRestic(["init"], config);
293
+ } catch (err) {
294
+ const stderr = err.stderr ?? "";
295
+ const msg = err instanceof Error ? err.message : String(err);
296
+ if (/already exists|already initialized/i.test(stderr + msg)) return;
297
+ throw err;
298
+ }
299
+ }
300
+ function restoreStagedDb(stagedPath, livePath) {
301
+ if (!existsSync(stagedPath)) return false;
302
+ mkdirSync(dirname(livePath), { recursive: true });
303
+ rmSync(livePath, { force: true });
304
+ rmSync(`${livePath}-wal`, { force: true });
305
+ rmSync(`${livePath}-shm`, { force: true });
306
+ renameSync(stagedPath, livePath);
307
+ return true;
308
+ }
309
+
310
+ export {
311
+ RESTIC_INSTALL_HINT,
312
+ resticVersion,
313
+ readBackupState,
314
+ runBackup,
315
+ listSnapshots,
316
+ initRepo,
317
+ restoreStagedDb
318
+ };
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  setBridgeConfig
4
- } from "./chunk-57LBQ7JX.js";
4
+ } from "./chunk-FHWUQZXE.js";
5
5
  import {
6
6
  readEnv,
7
7
  sharedEnvPath,
8
8
  writeEnv
9
- } from "./chunk-XDTKJNW5.js";
9
+ } from "./chunk-5GT7URXJ.js";
10
10
 
11
11
  // packages/daemon/src/lib/template/import-utils.ts
12
12
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readGlobalConfig
4
- } from "./chunk-ONM2AZFL.js";
4
+ } from "./chunk-322KE457.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-T2TP6ZC6.js";
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  exec,
4
4
  execInherit
5
- } from "./chunk-PGPLCJPK.js";
5
+ } from "./chunk-3ERIL673.js";
6
6
  import {
7
7
  voluteSystemDir
8
- } from "./chunk-YAMO2U3N.js";
8
+ } from "./chunk-H7IYFRAL.js";
9
9
 
10
10
  // packages/daemon/src/lib/config/service-mode.ts
11
11
  import { execFileSync } from "child_process";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  mindDir,
4
4
  stateDir
5
- } from "./chunk-YAMO2U3N.js";
5
+ } from "./chunk-H7IYFRAL.js";
6
6
 
7
7
  // packages/daemon/src/lib/mind/archive.ts
8
8
  import { execFileSync } from "child_process";