volute 0.31.0 → 0.32.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 (178) hide show
  1. package/README.md +15 -22
  2. package/dist/{accept-GAKQ3MEH.js → accept-74M7I4RZ.js} +3 -2
  3. package/dist/{activity-events-T5ZRCVAL.js → activity-events-HETAODOK.js} +3 -2
  4. package/dist/{ai-service-UWUPM4T6.js → ai-service-ZIPCV3MX.js} +18 -5
  5. package/dist/api.d.ts +98 -281
  6. package/dist/{archive-YBNSJYZZ.js → archive-INXYFVCW.js} +3 -2
  7. package/dist/{auth-T5AW2USD.js → auth-6DMGES3I.js} +4 -3
  8. package/dist/{bridge-4AJ3EY26.js → bridge-BVCBTGPF.js} +3 -2
  9. package/dist/{chat-7YLT7FI3.js → chat-XT4OBJBU.js} +8 -8
  10. package/dist/{chunk-BNC43CSY.js → chunk-2FLJ63GU.js} +2 -2
  11. package/dist/{chunk-NV3TYNWX.js → chunk-2NGTS5UU.js} +1 -1
  12. package/dist/{chunk-LX6T3GKQ.js → chunk-ALEF47VT.js} +1 -1
  13. package/dist/{chunk-S2TZLSDH.js → chunk-D5G5YOPL.js} +163 -15
  14. package/dist/{chunk-VGWJSNHS.js → chunk-G53F3JA4.js} +1 -35
  15. package/dist/{chunk-A6TUJJ3L.js → chunk-G6BSYHPK.js} +2 -2
  16. package/dist/{chunk-BC3P3QCK.js → chunk-I5KY25PQ.js} +1 -9
  17. package/dist/{chunk-PNQCXLSV.js → chunk-IYDIE3HG.js} +58 -1
  18. package/dist/{chunk-HDKY4TWU.js → chunk-JJ7W6WSB.js} +3 -3
  19. package/dist/{chunk-57OKQMP3.js → chunk-LGB6JBHI.js} +1 -1
  20. package/dist/chunk-LRCG2JLP.js +251 -0
  21. package/dist/{chunk-SNVPRRT7.js → chunk-LSGWR54X.js} +2 -2
  22. package/dist/{chunk-EMPFLFTG.js → chunk-M7UL5S3Q.js} +1 -1
  23. package/dist/chunk-PB65JZK2.js +85 -0
  24. package/dist/chunk-PVY5W6QN.js +41 -0
  25. package/dist/{chunk-BWKIHH7B.js → chunk-QBQ424EM.js} +318 -418
  26. package/dist/{chunk-EKDWA7E4.js → chunk-QZANELPX.js} +4 -2
  27. package/dist/{chunk-AAO77TZX.js → chunk-R7E6CRVQ.js} +1 -1
  28. package/dist/{chunk-X62AXPR7.js → chunk-RPZZSXV3.js} +8 -196
  29. package/dist/{chunk-WRS3B556.js → chunk-RSX4OPZY.js} +5 -5
  30. package/dist/{chunk-FAHDKPEH.js → chunk-S6NFERDC.js} +5 -3
  31. package/dist/chunk-SKLSMHXO.js +208 -0
  32. package/dist/{chunk-DAXJKPHZ.js → chunk-SX5TKJBZ.js} +2 -2
  33. package/dist/{chunk-R5QJBZZG.js → chunk-TDRYEPH4.js} +20 -10
  34. package/dist/{chunk-6QIUN46C.js → chunk-TSXLLQZW.js} +11 -3
  35. package/dist/{chunk-4OUOFS23.js → chunk-UKVWJRKN.js} +1 -1
  36. package/dist/{chunk-NOWVQ7AL.js → chunk-WKF5FEFK.js} +318 -167
  37. package/dist/cli.js +38 -20
  38. package/dist/{clock-LJCG426D.js → clock-2UOZ6JPU.js} +5 -4
  39. package/dist/{cloud-sync-O3LXIRN6.js → cloud-sync-JN3NWKEM.js} +16 -14
  40. package/dist/config-H2H4UIF7.js +72 -0
  41. package/dist/connectors/discord-bridge.js +1 -1
  42. package/dist/connectors/slack-bridge.js +1 -1
  43. package/dist/connectors/telegram-bridge.js +1 -1
  44. package/dist/{conversations-RKKGP5IA.js → conversations-3O5O6AS3.js} +4 -3
  45. package/dist/{create-WUTIIRI2.js → create-RNLNCORE.js} +3 -2
  46. package/dist/{create-TL623TFC.js → create-WBBYI6V7.js} +6 -2
  47. package/dist/{daemon-client-CVGM25DM.js → daemon-client-6QXHZ7US.js} +3 -2
  48. package/dist/{daemon-restart-EZP7XH3V.js → daemon-restart-NGFHFAUF.js} +7 -6
  49. package/dist/daemon.js +907 -612
  50. package/dist/{db-SW5PL6QA.js → db-F34YLV7D.js} +2 -1
  51. package/dist/db-RA45JBFG.js +16 -0
  52. package/dist/{delete-Z6HAG35F.js → delete-QTGWEDBI.js} +1 -1
  53. package/dist/delivery-manager-SDVXFD4W.js +28 -0
  54. package/dist/delivery-router-FL45JL7N.js +21 -0
  55. package/dist/down-TB3ESMNP.js +14 -0
  56. package/dist/{env-NHESNNSP.js → env-RLYQBOOP.js} +3 -2
  57. package/dist/{export-EVMP7GWY.js → export-SUYRLI5Q.js} +4 -3
  58. package/dist/{extension-LR7EW3JF.js → extension-FQ5D3NCC.js} +4 -3
  59. package/dist/{extensions-NGEJI7JH.js → extensions-GDYWQXC4.js} +9 -7
  60. package/dist/{files-3SM7V33S.js → files-EAMPO2SJ.js} +4 -3
  61. package/dist/{history-PQD3LXEP.js → history-FO5PHBQ5.js} +7 -2
  62. package/dist/{import-PR2OCGQJ.js → import-DDUFE7AY.js} +4 -3
  63. package/dist/{join-R4EN5CWQ.js → join-I5QEE3LG.js} +1 -1
  64. package/dist/{list-B4XNUOFO.js → list-DW2VRTOZ.js} +3 -2
  65. package/dist/{login-62JVY6A2.js → login-7CHPW2PN.js} +3 -2
  66. package/dist/{login-URWP6S2N.js → login-RIJF2F4G.js} +3 -2
  67. package/dist/{logout-NXJQJDLI.js → logout-5MLHZALK.js} +3 -2
  68. package/dist/{logout-ZK2N62T3.js → logout-UZJRGY4Z.js} +3 -2
  69. package/dist/message-delivery-2FIM7QKO.js +32 -0
  70. package/dist/{mind-E2ZV2WRX.js → mind-2B6M7Y25.js} +18 -18
  71. package/dist/{mind-activity-tracker-ASNZBMLC.js → mind-activity-tracker-NZZT2NTT.js} +4 -3
  72. package/dist/{mind-list-BEI7E5WY.js → mind-list-WUPMQDYQ.js} +3 -2
  73. package/dist/mind-manager-BNCMGYXW.js +28 -0
  74. package/dist/mind-service-AV273WT4.js +34 -0
  75. package/dist/{mind-sleep-CANABWJI.js → mind-sleep-B7BHJLH7.js} +3 -2
  76. package/dist/{mind-status-6WKZVUOP.js → mind-status-L3EFFRPR.js} +3 -2
  77. package/dist/{mind-wake-RZKLH2IN.js → mind-wake-GY3RFX7Y.js} +3 -2
  78. package/dist/{package-NU4CA7OU.js → package-PK6JUFL3.js} +1 -1
  79. package/dist/{read-THL362EI.js → read-5AMJRO3D.js} +3 -2
  80. package/dist/{register-QAQELAS6.js → register-V2JZZKFK.js} +3 -2
  81. package/dist/{registry-ASXCQCNH.js → registry-PJ4S5PHQ.js} +8 -1
  82. package/dist/{reject-AYPBNPNL.js → reject-33HEZMZ4.js} +3 -2
  83. package/dist/{restart-6SKPV3T2.js → restart-3UCMRUVC.js} +3 -2
  84. package/dist/{sandbox-6ZEWQDVU.js → sandbox-JANNTX6U.js} +4 -3
  85. package/dist/schema-PA3M5ZKH.js +32 -0
  86. package/dist/{seed-OWX2AW75.js → seed-ALUQ55FF.js} +26 -9
  87. package/dist/{send-ZO4BTWXK.js → send-3MI36LEF.js} +56 -67
  88. package/dist/{setup-7CFITEQN.js → setup-SZIARWI6.js} +5 -2
  89. package/dist/{setup-ZXBXG7E4.js → setup-WENLVPVP.js} +8 -6
  90. package/dist/{skill-YFXP67A2.js → skill-TUVOTW4Z.js} +3 -2
  91. package/dist/skills/dreaming/SKILL.md +6 -4
  92. package/dist/skills/dreaming/references/INSTALL.md +2 -2
  93. package/dist/skills/dreaming/scripts/dream.ts +2 -2
  94. package/dist/skills/dreaming/scripts/wake-context-dreams.sh +1 -1
  95. package/dist/skills/imagegen/SKILL.md +6 -5
  96. package/dist/skills/imagegen/references/INSTALL.md +1 -1
  97. package/dist/skills/resonance/SKILL.md +4 -1
  98. package/dist/skills/resonance/references/INSTALL.md +2 -2
  99. package/dist/skills/resonance/scripts/resonance-hook.sh +2 -0
  100. package/dist/skills/resonance/scripts/resonance.ts +35 -5
  101. package/dist/skills/volute-admin/SKILL.md +83 -0
  102. package/dist/skills/volute-mind/SKILL.md +11 -11
  103. package/dist/skills-XNZK6P4K.js +61 -0
  104. package/dist/sleep-manager-53DZOWW7.js +32 -0
  105. package/dist/spirit-N4W4UQRH.js +217 -0
  106. package/dist/{split-MI62KJUU.js → split-STOROBYJ.js} +1 -1
  107. package/dist/{sprout-FDVI2CGN.js → sprout-L2GFOVF7.js} +9 -7
  108. package/dist/{start-D64BRKPH.js → start-K2NCUUCG.js} +3 -2
  109. package/dist/{status-ZZWBYFGE.js → status-TCUMUO6M.js} +5 -4
  110. package/dist/{stop-OP2CTXCO.js → stop-H26JZDXF.js} +3 -2
  111. package/dist/system-chat-NPYFYZVI.js +32 -0
  112. package/dist/{systems-EQPPT4B7.js → systems-DHBKVYEY.js} +6 -5
  113. package/dist/{tailscale-6DJKUMNF.js → tailscale-XHQBZROW.js} +2 -1
  114. package/dist/{template-hash-3HOR4UAJ.js → template-hash-A6VVKOXJ.js} +2 -1
  115. package/dist/up-6I6BHRTO.js +17 -0
  116. package/dist/{update-KUJXATRS.js → update-QVPRF6GR.js} +5 -4
  117. package/dist/{update-check-5WVSU37T.js → update-check-ZD6OOIYQ.js} +3 -2
  118. package/dist/{upgrade-KBHCWX6T.js → upgrade-O4Q7WJM3.js} +12 -14
  119. package/dist/{version-notify-75ELVKPV.js → version-notify-TCKWBZZG.js} +21 -18
  120. package/dist/web-assets/assets/index-Bui7U9Uu.css +1 -0
  121. package/dist/web-assets/assets/index-e36DIo1b.js +73 -0
  122. package/dist/web-assets/ext-theme.css +93 -0
  123. package/dist/web-assets/index.html +2 -2
  124. package/drizzle/0004_spirits.sql +5 -0
  125. package/drizzle/meta/0004_snapshot.json +7 -0
  126. package/drizzle/meta/_journal.json +7 -0
  127. package/package.json +1 -1
  128. package/packages/extensions/notes/dist/ui/assets/index-8jWEv9SA.js +61 -0
  129. package/packages/extensions/notes/dist/ui/assets/index-DkaB7Ytd.css +1 -0
  130. package/packages/extensions/notes/dist/ui/index.html +2 -2
  131. package/packages/extensions/pages/skills/pages/SKILL.md +16 -46
  132. package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +40 -0
  133. package/templates/_base/.init/{.config → .local}/bin/volute +1 -1
  134. package/templates/_base/.init/.local/hooks/pre-prompt/session-activity.ts +40 -0
  135. package/templates/_base/.init/.local/hooks/startup-context.ts +58 -0
  136. package/templates/_base/home/.config/routes.json +1 -1
  137. package/templates/_base/src/lib/daemon-client.ts +21 -13
  138. package/templates/_base/src/lib/format-prefix.ts +1 -0
  139. package/templates/_base/src/lib/hook-loader.ts +155 -0
  140. package/templates/_base/src/lib/startup.ts +11 -4
  141. package/templates/_base/src/lib/transparency.ts +2 -2
  142. package/templates/claude/.init/.claude/settings.json +1 -1
  143. package/templates/claude/.init/.config/routes.json +2 -2
  144. package/templates/claude/src/agent.ts +95 -13
  145. package/templates/claude/src/lib/message-channel.ts +7 -2
  146. package/templates/codex/.init/.config/routes.json +11 -0
  147. package/templates/codex/.init/AGENTS.md +29 -0
  148. package/templates/codex/home/.config/config.json.tmpl +7 -0
  149. package/templates/codex/package.json.tmpl +20 -0
  150. package/templates/codex/src/agent.ts +553 -0
  151. package/templates/codex/src/lib/content.ts +16 -0
  152. package/templates/codex/src/lib/session-store.ts +56 -0
  153. package/templates/codex/src/server.ts +59 -0
  154. package/templates/codex/volute-template.json +8 -0
  155. package/templates/pi/.init/.config/routes.json +2 -2
  156. package/templates/pi/src/agent.ts +62 -8
  157. package/templates/pi/src/lib/event-handler.ts +1 -1
  158. package/templates/pi/src/lib/reply-instructions-extension.ts +32 -11
  159. package/dist/chunk-HR5JKIDG.js +0 -222
  160. package/dist/down-TS4XQBA4.js +0 -13
  161. package/dist/message-delivery-UJHCLVU4.js +0 -30
  162. package/dist/mind-manager-IPA6DZXD.js +0 -26
  163. package/dist/pages-watcher-72OVPRMH.js +0 -22
  164. package/dist/skills/sessions/SKILL.md +0 -49
  165. package/dist/sleep-manager-TPS6OGCA.js +0 -30
  166. package/dist/system-chat-B43GIXQU.js +0 -30
  167. package/dist/up-TDXEP3VA.js +0 -16
  168. package/dist/web-assets/assets/index-BM1cTzBg.js +0 -72
  169. package/dist/web-assets/assets/index-BfJkKTPF.css +0 -1
  170. package/packages/extensions/notes/dist/ui/assets/index-B8GdTnXs.css +0 -1
  171. package/packages/extensions/notes/dist/ui/assets/index-CDpGTCWb.js +0 -2
  172. package/packages/extensions/pages/skills/pages/scripts/pages.mjs +0 -58
  173. package/templates/_base/.init/.config/hooks/startup-context.sh +0 -46
  174. package/templates/_base/.init/.config/scripts/session-reader.ts +0 -59
  175. package/templates/_base/src/lib/session-monitor.ts +0 -400
  176. package/templates/claude/src/lib/hooks/session-context.ts +0 -32
  177. package/templates/pi/src/lib/session-context-extension.ts +0 -35
  178. /package/templates/_base/.init/{.config → .local}/hooks/wake-context.sh +0 -0
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ minds,
4
+ schema_exports
5
+ } from "./chunk-RPZZSXV3.js";
6
+
7
+ // src/lib/db.ts
8
+ import { chmodSync, existsSync } from "fs";
9
+ import { dirname as dirname2, resolve as resolve2 } from "path";
10
+ import { fileURLToPath as fileURLToPath2 } from "url";
11
+ import { drizzle } from "drizzle-orm/libsql";
12
+ import { migrate } from "drizzle-orm/libsql/migrator";
13
+
14
+ // src/lib/registry.ts
15
+ import { mkdirSync } from "fs";
16
+ import { homedir } from "os";
17
+ import { dirname, resolve } from "path";
18
+ import { fileURLToPath } from "url";
19
+ import { and, eq, isNull } from "drizzle-orm";
20
+ function voluteHome() {
21
+ if (process.env.VOLUTE_HOME) return process.env.VOLUTE_HOME;
22
+ const dir = dirname(fileURLToPath(import.meta.url));
23
+ if (dir.endsWith("/src/lib")) {
24
+ throw new Error(
25
+ 'VOLUTE_HOME must be set when running from source. For tests, run via "npm test" or add "--import ./test/setup.ts".'
26
+ );
27
+ }
28
+ return resolve(homedir(), ".volute");
29
+ }
30
+ function voluteUserHome() {
31
+ if (process.env.VOLUTE_USER_HOME) return process.env.VOLUTE_USER_HOME;
32
+ return resolve(homedir(), ".volute");
33
+ }
34
+ function voluteSystemDir() {
35
+ return resolve(voluteHome(), "system");
36
+ }
37
+ function ensureSystemDir() {
38
+ mkdirSync(voluteSystemDir(), { recursive: true });
39
+ }
40
+ function ensureVoluteHome() {
41
+ const mindsBase = process.env.VOLUTE_MINDS_DIR ?? resolve(voluteHome(), "minds");
42
+ mkdirSync(mindsBase, { recursive: true });
43
+ ensureSystemDir();
44
+ }
45
+ function rowToEntry(row) {
46
+ return {
47
+ name: row.name,
48
+ port: row.port,
49
+ created: row.created_at,
50
+ running: row.running === 1,
51
+ stage: row.stage ?? (row.parent ? void 0 : "sprouted"),
52
+ template: row.template ?? void 0,
53
+ templateHash: row.template_hash ?? void 0,
54
+ parent: row.parent ?? void 0,
55
+ dir: row.dir ?? void 0,
56
+ branch: row.branch ?? void 0,
57
+ mindType: row.mind_type ?? "mind",
58
+ createdBy: row.created_by ?? void 0
59
+ };
60
+ }
61
+ async function readRegistry() {
62
+ const db2 = await getDb();
63
+ const rows = await db2.select().from(minds).where(and(isNull(minds.parent), eq(minds.mind_type, "mind")));
64
+ return rows.map(rowToEntry);
65
+ }
66
+ async function readAllMinds() {
67
+ const db2 = await getDb();
68
+ const rows = await db2.select().from(minds);
69
+ return rows.map(rowToEntry);
70
+ }
71
+ var MIND_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
72
+ var MIND_NAME_MAX = 64;
73
+ var RESERVED_NAMES = /* @__PURE__ */ new Set(["volute", "system"]);
74
+ function validateMindName(name) {
75
+ if (!name) return "Mind name is required";
76
+ if (name.length > MIND_NAME_MAX) return `Mind name must be at most ${MIND_NAME_MAX} characters`;
77
+ if (!MIND_NAME_RE.test(name)) {
78
+ return "Mind name must start with alphanumeric and contain only alphanumeric, dots, dashes, or underscores";
79
+ }
80
+ if (RESERVED_NAMES.has(name.toLowerCase())) {
81
+ return `"${name}" is a reserved name`;
82
+ }
83
+ return null;
84
+ }
85
+ async function addMind(name, port, stage, template, createdBy) {
86
+ const err = validateMindName(name);
87
+ if (err) throw new Error(err);
88
+ const db2 = await getDb();
89
+ await db2.insert(minds).values({
90
+ name,
91
+ port,
92
+ stage: stage ?? null,
93
+ template: template ?? null,
94
+ created_by: createdBy ?? null
95
+ }).onConflictDoUpdate({
96
+ target: minds.name,
97
+ set: {
98
+ port,
99
+ stage: stage ?? null,
100
+ template: template ?? null,
101
+ created_by: createdBy ?? null
102
+ }
103
+ });
104
+ }
105
+ async function addSpirit(name, port, template, dir) {
106
+ const db2 = await getDb();
107
+ await db2.insert(minds).values({
108
+ name,
109
+ port,
110
+ template,
111
+ dir,
112
+ mind_type: "spirit",
113
+ stage: "sprouted"
114
+ }).onConflictDoUpdate({
115
+ target: minds.name,
116
+ set: { port, template, dir, mind_type: "spirit" }
117
+ });
118
+ }
119
+ async function readSpirits() {
120
+ const db2 = await getDb();
121
+ const rows = await db2.select().from(minds).where(eq(minds.mind_type, "spirit"));
122
+ return rows.map(rowToEntry);
123
+ }
124
+ async function addVariant(name, parent, port, dir, branch) {
125
+ const err = validateMindName(name);
126
+ if (err) throw new Error(err);
127
+ const db2 = await getDb();
128
+ await db2.insert(minds).values({ name, port, parent, dir, branch }).onConflictDoUpdate({
129
+ target: minds.name,
130
+ set: { port, parent, dir, branch }
131
+ });
132
+ }
133
+ async function removeMind(name) {
134
+ const db2 = await getDb();
135
+ await db2.delete(minds).where(eq(minds.name, name));
136
+ }
137
+ async function setMindRunning(name, running) {
138
+ const db2 = await getDb();
139
+ await db2.update(minds).set({ running: running ? 1 : 0 }).where(eq(minds.name, name));
140
+ }
141
+ async function setMindStage(name, stage) {
142
+ const db2 = await getDb();
143
+ await db2.update(minds).set({ stage }).where(eq(minds.name, name));
144
+ }
145
+ async function setMindTemplateHash(name, hash) {
146
+ const db2 = await getDb();
147
+ await db2.update(minds).set({ template_hash: hash }).where(eq(minds.name, name));
148
+ }
149
+ async function findMind(name) {
150
+ const db2 = await getDb();
151
+ const rows = await db2.select().from(minds).where(eq(minds.name, name));
152
+ if (rows.length === 0) return void 0;
153
+ return rowToEntry(rows[0]);
154
+ }
155
+ async function findVariants(parent) {
156
+ const db2 = await getDb();
157
+ const rows = await db2.select().from(minds).where(eq(minds.parent, parent));
158
+ return rows.map(rowToEntry);
159
+ }
160
+ async function getBaseName(name) {
161
+ const entry = await findMind(name);
162
+ return entry?.parent ?? name;
163
+ }
164
+ function mindDir(name) {
165
+ if (process.env.VOLUTE_MINDS_DIR) {
166
+ return resolve(process.env.VOLUTE_MINDS_DIR, name);
167
+ }
168
+ return resolve(voluteHome(), "minds", name);
169
+ }
170
+ async function resolveMindDir(name) {
171
+ const entry = await findMind(name);
172
+ return entry?.dir ?? mindDir(name);
173
+ }
174
+ function stateDir(name) {
175
+ return resolve(voluteSystemDir(), "state", name);
176
+ }
177
+ async function nextPort() {
178
+ const db2 = await getDb();
179
+ const rows = await db2.select({ port: minds.port }).from(minds);
180
+ const usedPorts = new Set(rows.map((r) => r.port));
181
+ const basePort = parseInt(process.env.VOLUTE_BASE_PORT || "4100", 10);
182
+ let port = basePort;
183
+ while (usedPorts.has(port)) port++;
184
+ if (port > 65535) throw new Error("No available ports \u2014 all ports 4100-65535 are allocated");
185
+ return port;
186
+ }
187
+ function daemonLoopback() {
188
+ const host = process.env.VOLUTE_DAEMON_HOSTNAME || "127.0.0.1";
189
+ if (host === "0.0.0.0") return "127.0.0.1";
190
+ if (host === "::") return "[::1]";
191
+ return host;
192
+ }
193
+
194
+ // src/lib/db.ts
195
+ var __dirname = dirname2(fileURLToPath2(import.meta.url));
196
+ var migrationsFolder = existsSync(resolve2(__dirname, "../drizzle")) ? resolve2(__dirname, "../drizzle") : resolve2(__dirname, "../../drizzle");
197
+ var db = null;
198
+ var dbPromise = null;
199
+ async function getDb() {
200
+ if (db) return db;
201
+ if (dbPromise) return dbPromise;
202
+ dbPromise = (async () => {
203
+ try {
204
+ const dbPath = process.env.VOLUTE_DB_PATH || resolve2(voluteSystemDir(), "volute.db");
205
+ const instance = drizzle({ connection: { url: `file:${dbPath}` }, schema: schema_exports });
206
+ await migrate(instance, { migrationsFolder });
207
+ try {
208
+ chmodSync(dbPath, 384);
209
+ } catch (err) {
210
+ console.error(
211
+ `[volute] WARNING: Failed to restrict database file permissions on ${dbPath}:`,
212
+ err
213
+ );
214
+ }
215
+ db = instance;
216
+ return instance;
217
+ } catch (err) {
218
+ dbPromise = null;
219
+ throw err;
220
+ }
221
+ })();
222
+ return dbPromise;
223
+ }
224
+
225
+ export {
226
+ getDb,
227
+ voluteHome,
228
+ voluteUserHome,
229
+ voluteSystemDir,
230
+ ensureSystemDir,
231
+ ensureVoluteHome,
232
+ readRegistry,
233
+ readAllMinds,
234
+ validateMindName,
235
+ addMind,
236
+ addSpirit,
237
+ readSpirits,
238
+ addVariant,
239
+ removeMind,
240
+ setMindRunning,
241
+ setMindStage,
242
+ setMindTemplateHash,
243
+ findMind,
244
+ findVariants,
245
+ getBaseName,
246
+ mindDir,
247
+ resolveMindDir,
248
+ stateDir,
249
+ nextPort,
250
+ daemonLoopback
251
+ };
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  exec,
4
4
  execInherit
5
- } from "./chunk-57OKQMP3.js";
5
+ } from "./chunk-LGB6JBHI.js";
6
6
  import {
7
7
  voluteSystemDir
8
- } from "./chunk-X62AXPR7.js";
8
+ } from "./chunk-LRCG2JLP.js";
9
9
 
10
10
  // src/lib/service-mode.ts
11
11
  import { execFileSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  voluteSystemDir
4
- } from "./chunk-X62AXPR7.js";
4
+ } from "./chunk-LRCG2JLP.js";
5
5
 
6
6
  // src/lib/update-check.ts
7
7
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+
3
+ // packages/extensions/pages/src/db.ts
4
+ function initDb(db) {
5
+ db.exec(`
6
+ CREATE TABLE IF NOT EXISTS published_pages (
7
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
8
+ mind TEXT NOT NULL,
9
+ file TEXT NOT NULL,
10
+ published_at TEXT NOT NULL DEFAULT (datetime('now')),
11
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
12
+ );
13
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_pp_mind_file ON published_pages(mind, file);
14
+ CREATE INDEX IF NOT EXISTS idx_pp_updated_at ON published_pages(updated_at);
15
+ `);
16
+ }
17
+ function getPublishedPages(db, mind) {
18
+ return db.prepare(
19
+ "SELECT file, published_at, updated_at FROM published_pages WHERE mind = ? ORDER BY file"
20
+ ).all(mind);
21
+ }
22
+ function getRecentPages(db, opts) {
23
+ const limit = opts?.limit ?? 10;
24
+ if (opts?.mind) {
25
+ return db.prepare(
26
+ "SELECT mind, file, updated_at FROM published_pages WHERE mind = ? ORDER BY updated_at DESC LIMIT ?"
27
+ ).all(opts.mind, limit);
28
+ }
29
+ return db.prepare("SELECT mind, file, updated_at FROM published_pages ORDER BY updated_at DESC LIMIT ?").all(limit);
30
+ }
31
+ function getAllSites(db) {
32
+ const rows = db.prepare("SELECT mind, file, updated_at FROM published_pages ORDER BY mind, file").all();
33
+ const siteMap = /* @__PURE__ */ new Map();
34
+ for (const row of rows) {
35
+ let files = siteMap.get(row.mind);
36
+ if (!files) {
37
+ files = [];
38
+ siteMap.set(row.mind, files);
39
+ }
40
+ files.push({ file: row.file, updated_at: row.updated_at });
41
+ }
42
+ return Array.from(siteMap.entries()).map(([mind, files]) => ({ mind, files }));
43
+ }
44
+ function syncPublishedPages(db, mind, htmlFiles) {
45
+ const existing = new Map(
46
+ db.prepare("SELECT file, updated_at FROM published_pages WHERE mind = ?").all(mind).map((r) => [r.file, r.updated_at])
47
+ );
48
+ const newSet = new Set(htmlFiles);
49
+ const added = [];
50
+ const updated = [];
51
+ const removed = [];
52
+ db.exec("BEGIN");
53
+ try {
54
+ for (const file of htmlFiles) {
55
+ if (existing.has(file)) {
56
+ db.prepare(
57
+ "UPDATE published_pages SET updated_at = datetime('now') WHERE mind = ? AND file = ?"
58
+ ).run(mind, file);
59
+ updated.push(file);
60
+ } else {
61
+ db.prepare("INSERT INTO published_pages (mind, file) VALUES (?, ?)").run(mind, file);
62
+ added.push(file);
63
+ }
64
+ }
65
+ for (const [file] of existing) {
66
+ if (!newSet.has(file)) {
67
+ db.prepare("DELETE FROM published_pages WHERE mind = ? AND file = ?").run(mind, file);
68
+ removed.push(file);
69
+ }
70
+ }
71
+ db.exec("COMMIT");
72
+ } catch (err) {
73
+ db.exec("ROLLBACK");
74
+ throw err;
75
+ }
76
+ return { added, removed, updated };
77
+ }
78
+
79
+ export {
80
+ initDb,
81
+ getPublishedPages,
82
+ getRecentPages,
83
+ getAllSites,
84
+ syncPublishedPages
85
+ };
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ composeTemplate,
4
+ findTemplatesRoot,
5
+ listFiles
6
+ } from "./chunk-G53F3JA4.js";
7
+
8
+ // src/lib/template-hash.ts
9
+ import { createHash } from "crypto";
10
+ import { existsSync, readFileSync, rmSync } from "fs";
11
+ import { resolve } from "path";
12
+ var hashCache = /* @__PURE__ */ new Map();
13
+ function computeTemplateHash(templateName) {
14
+ const cached = hashCache.get(templateName);
15
+ if (cached) return cached;
16
+ const templatesRoot = findTemplatesRoot();
17
+ const baseDir = resolve(templatesRoot, "_base");
18
+ const templateDir = resolve(templatesRoot, templateName);
19
+ if (!existsSync(baseDir)) throw new Error(`Base template not found: ${baseDir}`);
20
+ if (!existsSync(templateDir)) throw new Error(`Template not found: ${templateName}`);
21
+ const { composedDir } = composeTemplate(templatesRoot, templateName);
22
+ try {
23
+ const files = listFiles(composedDir).filter((f) => !f.startsWith(".init/") && !f.startsWith(".init\\")).sort();
24
+ const hash = createHash("sha256");
25
+ for (const file of files) {
26
+ const content = readFileSync(resolve(composedDir, file));
27
+ hash.update(file);
28
+ hash.update("\0");
29
+ hash.update(content);
30
+ }
31
+ const result = hash.digest("hex");
32
+ hashCache.set(templateName, result);
33
+ return result;
34
+ } finally {
35
+ rmSync(composedDir, { recursive: true, force: true });
36
+ }
37
+ }
38
+
39
+ export {
40
+ computeTemplateHash
41
+ };