volute 0.20.0 → 0.22.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 (114) hide show
  1. package/README.md +7 -7
  2. package/dist/{activity-events-OMXKXD5N.js → activity-events-3WHHCOBB.js} +3 -4
  3. package/dist/api.d.ts +4294 -0
  4. package/dist/{archive-ZCFOSTKB.js → archive-4ZQYK5MN.js} +4 -2
  5. package/dist/auth-HM2RSPY7.js +37 -0
  6. package/dist/{channel-PUQKGSQM.js → channel-BOOMFULW.js} +2 -2
  7. package/dist/{chunk-UU7A7KLB.js → chunk-A4S7H6G6.js} +5 -7
  8. package/dist/chunk-AKPFNL7L.js +148 -0
  9. package/dist/{chunk-EBGCNDMM.js → chunk-B2CPS4QU.js} +128 -114
  10. package/dist/chunk-G5KRTU2F.js +76 -0
  11. package/dist/{chunk-FCDU5BFX.js → chunk-HFCBO2GL.js} +2 -2
  12. package/dist/{chunk-GZ7DW4YL.js → chunk-HGCDWKSP.js} +2 -2
  13. package/dist/{chunk-7UFKREVW.js → chunk-JNFRY2WU.js} +2 -2
  14. package/dist/{chunk-DYZGP3EW.js → chunk-JTDFJWI2.js} +2 -1
  15. package/dist/{chunk-WC6ZHVRL.js → chunk-KFI7TQJ6.js} +2 -2
  16. package/dist/{chunk-AW7P4EVV.js → chunk-KTJGZ7M7.js} +55 -7
  17. package/dist/{chunk-OGXOMR65.js → chunk-NWPT4ASZ.js} +1 -1
  18. package/dist/{chunk-SCUDS4US.js → chunk-ON3FF5JA.js} +1 -1
  19. package/dist/chunk-OSFGKF2T.js +2651 -0
  20. package/dist/{chunk-TIWH32HP.js → chunk-PHHKNGA3.js} +3 -3
  21. package/dist/{chunk-VDWCHYTS.js → chunk-PHU4DEAJ.js} +1 -1
  22. package/dist/{chunk-7NO7EV5Z.js → chunk-QIXPN3OO.js} +2 -2
  23. package/dist/{chunk-O6ASDHFO.js → chunk-RK627D57.js} +40 -63
  24. package/dist/{chunk-NSE7VJQA.js → chunk-SGPEZ32F.js} +29 -1
  25. package/dist/{chunk-IKMY5X76.js → chunk-TFS25FIM.js} +12 -9
  26. package/dist/{chunk-PUVXOZ6T.js → chunk-VNVCRVYI.js} +118 -69
  27. package/dist/{chunk-32VR2EOH.js → chunk-VT5QODNE.js} +2 -2
  28. package/dist/{chunk-RHEGSQFJ.js → chunk-WSLPZF72.js} +1 -1
  29. package/dist/chunk-XLC342FO.js +29 -0
  30. package/dist/cli.js +57 -119
  31. package/dist/cloud-sync-C6WRYRVR.js +96 -0
  32. package/dist/{connector-JBVNZ7VK.js → connector-PYT5UOTZ.js} +6 -6
  33. package/dist/connectors/discord.js +2 -2
  34. package/dist/connectors/slack.js +2 -2
  35. package/dist/connectors/telegram.js +2 -2
  36. package/dist/{create-HP4OVVHF.js → create-WIDA3M4C.js} +1 -1
  37. package/dist/{daemon-client-ITWUCNFO.js → daemon-client-ZHCDL4RS.js} +2 -2
  38. package/dist/{daemon-restart-KPSWNYTH.js → daemon-restart-TPQ2XBRZ.js} +6 -6
  39. package/dist/daemon.js +2250 -1985
  40. package/dist/{delete-BSU7K3RY.js → delete-LOIANQGD.js} +1 -1
  41. package/dist/down-WSUASL5E.js +14 -0
  42. package/dist/{env-A3LMO777.js → env-4PHIHTF4.js} +2 -2
  43. package/dist/{export-6QBUOQGC.js → export-XD6PJBQP.js} +19 -8
  44. package/dist/{file-C57SK5DK.js → file-X4L5TTOL.js} +2 -2
  45. package/dist/{history-WNK3DFUM.js → history-HTEKRNID.js} +2 -2
  46. package/dist/{import-XEC34Y4Z.js → import-EAXTHHXL.js} +4 -3
  47. package/dist/{log-PPPZDVEF.js → log-SRO5Q6AD.js} +2 -2
  48. package/dist/{login-HNH3EUQV.js → login-UO6AOVEA.js} +4 -4
  49. package/dist/{logout-I5CB5UZS.js → logout-UKD5LA37.js} +2 -2
  50. package/dist/{logs-SF2IMJN4.js → logs-HNTNNBDW.js} +2 -2
  51. package/dist/{merge-33C237A4.js → merge-B6SYTGI7.js} +2 -2
  52. package/dist/message-delivery-WUS4K4ZC.js +21 -0
  53. package/dist/{mind-Z7CKD6DG.js → mind-BTXR5B3C.js} +35 -11
  54. package/dist/{mind-activity-tracker-624QLQLC.js → mind-activity-tracker-PGC3DBJ7.js} +4 -5
  55. package/dist/{mind-manager-3DMYKZPB.js → mind-manager-P5OBDUKI.js} +5 -6
  56. package/dist/mind-sleep-FWRBIFBS.js +41 -0
  57. package/dist/mind-wake-LJK2YU5X.js +36 -0
  58. package/dist/{package-4NHAVUUI.js → package-A7PEYJI2.js} +10 -1
  59. package/dist/{pages-4DGQT7ZA.js → pages-YSTRWJR4.js} +6 -6
  60. package/dist/{publish-TAJUET4I.js → publish-BZNHKUUK.js} +6 -6
  61. package/dist/{pull-XAEWQJ47.js → pull-GRQAXM2E.js} +2 -2
  62. package/dist/{register-VSPCMHKX.js → register-U2UO6TC4.js} +5 -5
  63. package/dist/registry-D2BSQ2X5.js +42 -0
  64. package/dist/{restart-IQKMCK5M.js → restart-CIDAKGG2.js} +3 -6
  65. package/dist/{schedule-FFZG23IW.js → schedule-NLR3LZLY.js} +2 -2
  66. package/dist/{seed-J43YDKXG.js → seed-3H2MRREW.js} +2 -2
  67. package/dist/{send-KVIZIGCE.js → send-RP2TA7SG.js} +132 -36
  68. package/dist/{service-LUR7WDO7.js → service-7BFXDI6J.js} +31 -13
  69. package/dist/{setup-52YRV7VP.js → setup-SSIIXQMI.js} +9 -34
  70. package/dist/{shared-KO35ZM44.js → shared-2OGT3NSL.js} +4 -4
  71. package/dist/{skill-BCVNI6TV.js → skill-Q2Y6PQ3L.js} +2 -2
  72. package/dist/skills/orientation/SKILL.md +2 -2
  73. package/dist/skills/volute-mind/SKILL.md +5 -5
  74. package/dist/sleep-manager-3RWUX2ZR.js +27 -0
  75. package/dist/{sprout-QN7Y4VVO.js → sprout-UKCYBGHK.js} +34 -30
  76. package/dist/{start-I5JYB65M.js → start-JR6CUUWF.js} +3 -6
  77. package/dist/{status-D7E5HHBV.js → status-5XDGYHKP.js} +2 -2
  78. package/dist/{status-4ESFLGH4.js → status-H2MKDN6L.js} +5 -5
  79. package/dist/{status-FU2PFVVF.js → status-LV34BG6G.js} +3 -3
  80. package/dist/{stop-NBVKEFQQ.js → stop-VKPGK25U.js} +2 -5
  81. package/dist/template-hash-BIMA4ILT.js +8 -0
  82. package/dist/{up-FS7CKM6V.js → up-JKGC7PPF.js} +5 -5
  83. package/dist/{update-FJIHDJKM.js → update-ELC6MEUT.js} +5 -5
  84. package/dist/{update-check-MWE5AH4U.js → update-check-F5Z3ALXX.js} +2 -2
  85. package/dist/{upgrade-AIT24B5I.js → upgrade-GXW2EQY3.js} +12 -3
  86. package/dist/{variant-63ZWO2W7.js → variant-A4I7PHXS.js} +16 -24
  87. package/dist/version-notify-5FGUAVSF.js +181 -0
  88. package/dist/web-assets/assets/index-DWBxl4LO.js +69 -0
  89. package/dist/web-assets/assets/index-ZqMd1mx1.css +1 -0
  90. package/dist/web-assets/index.html +2 -2
  91. package/package.json +10 -1
  92. package/templates/_base/.init/.config/prompts.json +1 -0
  93. package/templates/_base/home/.config/config.json.tmpl +4 -1
  94. package/templates/_base/src/lib/logger.ts +68 -23
  95. package/templates/_base/src/lib/startup.ts +12 -3
  96. package/templates/claude/src/agent.ts +150 -29
  97. package/templates/claude/src/lib/hooks/pre-compact.ts +18 -4
  98. package/templates/claude/src/lib/message-channel.ts +6 -0
  99. package/templates/claude/src/lib/stream-consumer.ts +7 -0
  100. package/templates/claude/src/server.ts +3 -1
  101. package/templates/pi/home/.config/config.json.tmpl +4 -1
  102. package/templates/pi/src/agent.ts +87 -0
  103. package/templates/pi/src/lib/event-handler.ts +13 -1
  104. package/templates/pi/src/server.ts +3 -1
  105. package/dist/chunk-5XNT2472.js +0 -36
  106. package/dist/chunk-FGSYHIS3.js +0 -891
  107. package/dist/chunk-UJ6GHNR7.js +0 -675
  108. package/dist/db-C2CJ46ZU.js +0 -10
  109. package/dist/delivery-manager-CSG7LXA4.js +0 -16
  110. package/dist/down-ZY35KMHR.js +0 -14
  111. package/dist/schema-GFH6RV3W.js +0 -26
  112. package/dist/variants-JAGWGBXG.js +0 -26
  113. package/dist/web-assets/assets/index-CUZTZzaW.js +0 -64
  114. package/dist/web-assets/assets/index-adVuCkqy.css +0 -1
@@ -3,13 +3,15 @@ import {
3
3
  addHistoryToArchive,
4
4
  createExportArchive,
5
5
  extractArchive,
6
+ isHomeOnlyArchive,
6
7
  readManifest
7
- } from "./chunk-AW7P4EVV.js";
8
- import "./chunk-EBGCNDMM.js";
8
+ } from "./chunk-KTJGZ7M7.js";
9
+ import "./chunk-B2CPS4QU.js";
9
10
  import "./chunk-K3NQKI34.js";
10
11
  export {
11
12
  addHistoryToArchive,
12
13
  createExportArchive,
13
14
  extractArchive,
15
+ isHomeOnlyArchive,
14
16
  readManifest
15
17
  };
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import "./chunk-K3NQKI34.js";
3
+
4
+ // src/commands/auth.ts
5
+ async function run(args) {
6
+ const subcommand = args[0];
7
+ switch (subcommand) {
8
+ case "register":
9
+ await import("./register-U2UO6TC4.js").then((m) => m.run(args.slice(1)));
10
+ break;
11
+ case "login":
12
+ await import("./login-UO6AOVEA.js").then((m) => m.run(args.slice(1)));
13
+ break;
14
+ case "logout":
15
+ await import("./logout-UKD5LA37.js").then((m) => m.run());
16
+ break;
17
+ case "--help":
18
+ case "-h":
19
+ case void 0:
20
+ printUsage();
21
+ break;
22
+ default:
23
+ printUsage();
24
+ console.error(`
25
+ Unknown subcommand: ${subcommand}`);
26
+ process.exit(1);
27
+ }
28
+ }
29
+ function printUsage() {
30
+ console.log(`Usage:
31
+ volute auth register [--name <name>] Register a system on volute.systems
32
+ volute auth login [--key <key>] Log in with an existing API key
33
+ volute auth logout Remove stored credentials`);
34
+ }
35
+ export {
36
+ run
37
+ };
@@ -11,8 +11,8 @@ import {
11
11
  } from "./chunk-D424ZQGI.js";
12
12
  import {
13
13
  daemonFetch
14
- } from "./chunk-WC6ZHVRL.js";
15
- import "./chunk-EBGCNDMM.js";
14
+ } from "./chunk-KFI7TQJ6.js";
15
+ import "./chunk-B2CPS4QU.js";
16
16
  import "./chunk-K3NQKI34.js";
17
17
 
18
18
  // src/commands/channel.ts
@@ -1,15 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- logger_default
4
- } from "./chunk-YUIHSKR6.js";
5
- import {
3
+ activity,
6
4
  getDb
7
- } from "./chunk-5XNT2472.js";
5
+ } from "./chunk-SGPEZ32F.js";
8
6
  import {
9
- activity
10
- } from "./chunk-NSE7VJQA.js";
7
+ logger_default
8
+ } from "./chunk-YUIHSKR6.js";
11
9
 
12
- // src/lib/activity-events.ts
10
+ // src/lib/events/activity-events.ts
13
11
  var subscribers = /* @__PURE__ */ new Set();
14
12
  function subscribe(callback) {
15
13
  subscribers.add(callback);
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/template-hash.ts
4
+ import { createHash } from "crypto";
5
+ import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync as rmSync2 } from "fs";
6
+ import { resolve as resolve2 } from "path";
7
+
8
+ // src/lib/template.ts
9
+ import {
10
+ cpSync,
11
+ existsSync,
12
+ mkdirSync,
13
+ readdirSync,
14
+ readFileSync,
15
+ renameSync,
16
+ rmSync,
17
+ statSync,
18
+ writeFileSync
19
+ } from "fs";
20
+ import { tmpdir } from "os";
21
+ import { dirname, join, relative, resolve } from "path";
22
+ function findTemplatesRoot() {
23
+ let dir = dirname(new URL(import.meta.url).pathname);
24
+ for (let i = 0; i < 5; i++) {
25
+ const candidate = resolve(dir, "templates");
26
+ if (existsSync(resolve(candidate, "_base"))) return candidate;
27
+ dir = dirname(dir);
28
+ }
29
+ console.error(
30
+ "Templates directory not found. Searched up from:",
31
+ dirname(new URL(import.meta.url).pathname)
32
+ );
33
+ process.exit(1);
34
+ }
35
+ function composeTemplate(templatesRoot, templateName) {
36
+ const baseDir = resolve(templatesRoot, "_base");
37
+ const templateDir = resolve(templatesRoot, templateName);
38
+ if (!existsSync(baseDir)) {
39
+ console.error("Base template not found:", baseDir);
40
+ process.exit(1);
41
+ }
42
+ if (!existsSync(templateDir)) {
43
+ console.error(`Template not found: ${templateName}`);
44
+ process.exit(1);
45
+ }
46
+ const composedDir = resolve(tmpdir(), `volute-template-${Date.now()}`);
47
+ mkdirSync(composedDir, { recursive: true });
48
+ cpSync(baseDir, composedDir, { recursive: true });
49
+ for (const file of listFiles(templateDir)) {
50
+ const src = resolve(templateDir, file);
51
+ const dest = resolve(composedDir, file);
52
+ mkdirSync(dirname(dest), { recursive: true });
53
+ cpSync(src, dest);
54
+ }
55
+ const manifestPath = resolve(composedDir, "volute-template.json");
56
+ if (!existsSync(manifestPath)) {
57
+ rmSync(composedDir, { recursive: true, force: true });
58
+ console.error(`Template manifest not found: ${templateName}/volute-template.json`);
59
+ process.exit(1);
60
+ }
61
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
62
+ rmSync(manifestPath);
63
+ return { composedDir, manifest };
64
+ }
65
+ function copyTemplateToDir(composedDir, destDir, mindName, manifest) {
66
+ cpSync(composedDir, destDir, { recursive: true });
67
+ for (const [from, to] of Object.entries(manifest.rename)) {
68
+ const fromPath = resolve(destDir, from);
69
+ if (existsSync(fromPath)) {
70
+ renameSync(fromPath, resolve(destDir, to));
71
+ }
72
+ }
73
+ for (const file of manifest.substitute) {
74
+ const path = resolve(destDir, file);
75
+ if (existsSync(path)) {
76
+ const content = readFileSync(path, "utf-8");
77
+ writeFileSync(path, content.replaceAll("{{name}}", mindName));
78
+ }
79
+ }
80
+ }
81
+ function applyInitFiles(destDir) {
82
+ const initDir = resolve(destDir, ".init");
83
+ if (!existsSync(initDir)) return;
84
+ const homeDir = resolve(destDir, "home");
85
+ for (const file of listFiles(initDir)) {
86
+ const src = resolve(initDir, file);
87
+ const dest = resolve(homeDir, file);
88
+ const parent = dirname(dest);
89
+ if (!existsSync(parent)) {
90
+ mkdirSync(parent, { recursive: true });
91
+ }
92
+ cpSync(src, dest);
93
+ }
94
+ rmSync(initDir, { recursive: true, force: true });
95
+ }
96
+ function listFiles(dir) {
97
+ const results = [];
98
+ function walk(current) {
99
+ for (const entry of readdirSync(current)) {
100
+ const full = join(current, entry);
101
+ if (statSync(full).isDirectory()) {
102
+ if (entry === ".git") continue;
103
+ walk(full);
104
+ } else {
105
+ results.push(relative(dir, full));
106
+ }
107
+ }
108
+ }
109
+ walk(dir);
110
+ return results;
111
+ }
112
+
113
+ // src/lib/template-hash.ts
114
+ var hashCache = /* @__PURE__ */ new Map();
115
+ function computeTemplateHash(templateName) {
116
+ const cached = hashCache.get(templateName);
117
+ if (cached) return cached;
118
+ const templatesRoot = findTemplatesRoot();
119
+ const baseDir = resolve2(templatesRoot, "_base");
120
+ const templateDir = resolve2(templatesRoot, templateName);
121
+ if (!existsSync2(baseDir)) throw new Error(`Base template not found: ${baseDir}`);
122
+ if (!existsSync2(templateDir)) throw new Error(`Template not found: ${templateName}`);
123
+ const { composedDir } = composeTemplate(templatesRoot, templateName);
124
+ try {
125
+ const files = listFiles(composedDir).filter((f) => !f.startsWith(".init/") && !f.startsWith(".init\\")).sort();
126
+ const hash = createHash("sha256");
127
+ for (const file of files) {
128
+ const content = readFileSync2(resolve2(composedDir, file));
129
+ hash.update(file);
130
+ hash.update("\0");
131
+ hash.update(content);
132
+ }
133
+ const result = hash.digest("hex");
134
+ hashCache.set(templateName, result);
135
+ return result;
136
+ } finally {
137
+ rmSync2(composedDir, { recursive: true, force: true });
138
+ }
139
+ }
140
+
141
+ export {
142
+ findTemplatesRoot,
143
+ composeTemplate,
144
+ copyTemplateToDir,
145
+ applyInitFiles,
146
+ listFiles,
147
+ computeTemplateHash
148
+ };
@@ -1,23 +1,122 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/lib/variants.ts
4
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
5
- import { resolve as resolve2 } from "path";
6
-
7
3
  // src/lib/registry.ts
8
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
4
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
9
5
  import { homedir } from "os";
10
- import { dirname, resolve } from "path";
6
+ import { dirname, resolve as resolve2 } from "path";
11
7
  import { fileURLToPath } from "url";
8
+
9
+ // src/lib/variants.ts
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
11
+ import { resolve } from "path";
12
+ function variantsPath() {
13
+ return resolve(voluteHome(), "variants.json");
14
+ }
15
+ function readAllVariants() {
16
+ const path = variantsPath();
17
+ if (!existsSync(path)) return {};
18
+ try {
19
+ return JSON.parse(readFileSync(path, "utf-8"));
20
+ } catch {
21
+ return {};
22
+ }
23
+ }
24
+ function writeAllVariants(all) {
25
+ mkdirSync(voluteHome(), { recursive: true });
26
+ writeFileSync(variantsPath(), `${JSON.stringify(all, null, 2)}
27
+ `);
28
+ }
29
+ function readVariants(mindName) {
30
+ return readAllVariants()[mindName] ?? [];
31
+ }
32
+ function writeVariants(mindName, variants) {
33
+ const all = readAllVariants();
34
+ if (variants.length === 0) {
35
+ delete all[mindName];
36
+ } else {
37
+ all[mindName] = variants;
38
+ }
39
+ writeAllVariants(all);
40
+ }
41
+ function addVariant(mindName, variant) {
42
+ const variants = readVariants(mindName);
43
+ const filtered = variants.filter((v) => v.name !== variant.name);
44
+ filtered.push(variant);
45
+ writeVariants(mindName, filtered);
46
+ }
47
+ function removeVariant(mindName, name) {
48
+ const variants = readVariants(mindName);
49
+ writeVariants(
50
+ mindName,
51
+ variants.filter((v) => v.name !== name)
52
+ );
53
+ }
54
+ function findVariant(mindName, name) {
55
+ return readVariants(mindName).find((v) => v.name === name);
56
+ }
57
+ function setVariantRunning(mindName, variantName, running) {
58
+ const all = readAllVariants();
59
+ const variants = all[mindName] ?? [];
60
+ const variant = variants.find((v) => v.name === variantName);
61
+ if (variant) {
62
+ variant.running = running;
63
+ all[mindName] = variants;
64
+ writeAllVariants(all);
65
+ }
66
+ }
67
+ function getAllRunningVariants() {
68
+ const all = readAllVariants();
69
+ const result = [];
70
+ for (const [mindName, variants] of Object.entries(all)) {
71
+ for (const variant of variants) {
72
+ if (variant.running) {
73
+ result.push({ mindName, variant });
74
+ }
75
+ }
76
+ }
77
+ return result;
78
+ }
79
+ function removeAllVariants(mindName) {
80
+ const all = readAllVariants();
81
+ delete all[mindName];
82
+ writeAllVariants(all);
83
+ }
84
+ async function checkHealth(port) {
85
+ try {
86
+ const res = await fetch(`http://127.0.0.1:${port}/health`, {
87
+ signal: AbortSignal.timeout(2e3)
88
+ });
89
+ if (!res.ok) return { ok: false };
90
+ const data = await res.json();
91
+ return { ok: true, name: data.name };
92
+ } catch {
93
+ return { ok: false };
94
+ }
95
+ }
96
+ var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
97
+ function validateBranchName(branch) {
98
+ if (!SAFE_BRANCH_RE.test(branch)) {
99
+ return `Invalid branch name: ${branch}. Only alphanumeric, '.', '_', '-', '/' allowed.`;
100
+ }
101
+ if (branch.includes("..")) {
102
+ return `Invalid branch name: ${branch}. '..' not allowed.`;
103
+ }
104
+ return null;
105
+ }
106
+
107
+ // src/lib/registry.ts
12
108
  var registryCache = null;
13
109
  function initRegistryCache() {
14
110
  registryCache = readRegistryFromDisk();
15
111
  }
112
+ function getRegistryCache() {
113
+ return registryCache;
114
+ }
16
115
  function readRegistryFromDisk() {
17
- const registryPath = resolve(voluteHome(), "minds.json");
18
- if (!existsSync(registryPath)) return [];
116
+ const registryPath = resolve2(voluteHome(), "minds.json");
117
+ if (!existsSync2(registryPath)) return [];
19
118
  try {
20
- const entries = JSON.parse(readFileSync(registryPath, "utf-8"));
119
+ const entries = JSON.parse(readFileSync2(registryPath, "utf-8"));
21
120
  return entries.map((e) => ({
22
121
  ...e,
23
122
  running: e.running ?? false,
@@ -35,11 +134,11 @@ function voluteHome() {
35
134
  'VOLUTE_HOME must be set when running from source. For tests, run via "npm test" or add "--import ./test/setup.ts".'
36
135
  );
37
136
  }
38
- return resolve(homedir(), ".volute");
137
+ return resolve2(homedir(), ".volute");
39
138
  }
40
139
  function ensureVoluteHome() {
41
- const mindsBase = process.env.VOLUTE_MINDS_DIR ?? resolve(voluteHome(), "minds");
42
- mkdirSync(mindsBase, { recursive: true });
140
+ const mindsBase = process.env.VOLUTE_MINDS_DIR ?? resolve2(voluteHome(), "minds");
141
+ mkdirSync2(mindsBase, { recursive: true });
43
142
  }
44
143
  function readRegistry() {
45
144
  if (registryCache) return registryCache;
@@ -48,9 +147,9 @@ function readRegistry() {
48
147
  function writeRegistry(entries) {
49
148
  if (registryCache) registryCache = entries;
50
149
  ensureVoluteHome();
51
- const registryPath = resolve(voluteHome(), "minds.json");
150
+ const registryPath = resolve2(voluteHome(), "minds.json");
52
151
  const tmpPath = `${registryPath}.tmp`;
53
- writeFileSync(tmpPath, `${JSON.stringify(entries, null, 2)}
152
+ writeFileSync2(tmpPath, `${JSON.stringify(entries, null, 2)}
54
153
  `);
55
154
  renameSync(tmpPath, registryPath);
56
155
  }
@@ -92,17 +191,25 @@ function setMindStage(name, stage) {
92
191
  writeRegistry(entries);
93
192
  }
94
193
  }
194
+ function setMindTemplateHash(name, hash) {
195
+ const entries = readRegistry();
196
+ const entry = entries.find((e) => e.name === name);
197
+ if (entry) {
198
+ entry.templateHash = hash;
199
+ writeRegistry(entries);
200
+ }
201
+ }
95
202
  function findMind(name) {
96
203
  return readRegistry().find((e) => e.name === name);
97
204
  }
98
205
  function mindDir(name) {
99
206
  if (process.env.VOLUTE_MINDS_DIR) {
100
- return resolve(process.env.VOLUTE_MINDS_DIR, name);
207
+ return resolve2(process.env.VOLUTE_MINDS_DIR, name);
101
208
  }
102
- return resolve(voluteHome(), "minds", name);
209
+ return resolve2(voluteHome(), "minds", name);
103
210
  }
104
211
  function stateDir(name) {
105
- return resolve(voluteHome(), "state", name);
212
+ return resolve2(voluteHome(), "state", name);
106
213
  }
107
214
  function nextPort() {
108
215
  const entries = readRegistry();
@@ -131,7 +238,7 @@ function resolveMind(name) {
131
238
  throw new Error(`Unknown mind: ${baseName}`);
132
239
  }
133
240
  const dir = mindDir(baseName);
134
- if (!existsSync(dir)) {
241
+ if (!existsSync2(dir)) {
135
242
  throw new Error(`Mind directory missing: ${dir}`);
136
243
  }
137
244
  if (variantName) {
@@ -144,102 +251,6 @@ function resolveMind(name) {
144
251
  return { entry, dir };
145
252
  }
146
253
 
147
- // src/lib/variants.ts
148
- function variantsPath() {
149
- return resolve2(voluteHome(), "variants.json");
150
- }
151
- function readAllVariants() {
152
- const path = variantsPath();
153
- if (!existsSync2(path)) return {};
154
- try {
155
- return JSON.parse(readFileSync2(path, "utf-8"));
156
- } catch {
157
- return {};
158
- }
159
- }
160
- function writeAllVariants(all) {
161
- mkdirSync2(voluteHome(), { recursive: true });
162
- writeFileSync2(variantsPath(), `${JSON.stringify(all, null, 2)}
163
- `);
164
- }
165
- function readVariants(mindName) {
166
- return readAllVariants()[mindName] ?? [];
167
- }
168
- function writeVariants(mindName, variants) {
169
- const all = readAllVariants();
170
- if (variants.length === 0) {
171
- delete all[mindName];
172
- } else {
173
- all[mindName] = variants;
174
- }
175
- writeAllVariants(all);
176
- }
177
- function addVariant(mindName, variant) {
178
- const variants = readVariants(mindName);
179
- const filtered = variants.filter((v) => v.name !== variant.name);
180
- filtered.push(variant);
181
- writeVariants(mindName, filtered);
182
- }
183
- function removeVariant(mindName, name) {
184
- const variants = readVariants(mindName);
185
- writeVariants(
186
- mindName,
187
- variants.filter((v) => v.name !== name)
188
- );
189
- }
190
- function findVariant(mindName, name) {
191
- return readVariants(mindName).find((v) => v.name === name);
192
- }
193
- function setVariantRunning(mindName, variantName, running) {
194
- const all = readAllVariants();
195
- const variants = all[mindName] ?? [];
196
- const variant = variants.find((v) => v.name === variantName);
197
- if (variant) {
198
- variant.running = running;
199
- all[mindName] = variants;
200
- writeAllVariants(all);
201
- }
202
- }
203
- function getAllRunningVariants() {
204
- const all = readAllVariants();
205
- const result = [];
206
- for (const [mindName, variants] of Object.entries(all)) {
207
- for (const variant of variants) {
208
- if (variant.running) {
209
- result.push({ mindName, variant });
210
- }
211
- }
212
- }
213
- return result;
214
- }
215
- function removeAllVariants(mindName) {
216
- const all = readAllVariants();
217
- delete all[mindName];
218
- writeAllVariants(all);
219
- }
220
- async function checkHealth(port) {
221
- try {
222
- const res = await fetch(`http://127.0.0.1:${port}/health`, {
223
- signal: AbortSignal.timeout(2e3)
224
- });
225
- if (!res.ok) return { ok: false };
226
- const data = await res.json();
227
- return { ok: true, name: data.name };
228
- } catch {
229
- return { ok: false };
230
- }
231
- }
232
- var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
233
- function validateBranchName(branch) {
234
- if (!SAFE_BRANCH_RE.test(branch)) {
235
- return `Invalid branch name: ${branch}. Only alphanumeric, '.', '_', '-', '/' allowed.`;
236
- }
237
- if (branch.includes("..")) {
238
- return `Invalid branch name: ${branch}. '..' not allowed.`;
239
- }
240
- return null;
241
- }
242
-
243
254
  export {
244
255
  readVariants,
245
256
  writeVariants,
@@ -252,14 +263,17 @@ export {
252
263
  checkHealth,
253
264
  validateBranchName,
254
265
  initRegistryCache,
266
+ getRegistryCache,
255
267
  voluteHome,
256
268
  ensureVoluteHome,
257
269
  readRegistry,
270
+ writeRegistry,
258
271
  validateMindName,
259
272
  addMind,
260
273
  removeMind,
261
274
  setMindRunning,
262
275
  setMindStage,
276
+ setMindTemplateHash,
263
277
  findMind,
264
278
  mindDir,
265
279
  stateDir,
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ subscribe
4
+ } from "./chunk-A4S7H6G6.js";
5
+ import {
6
+ logger_default
7
+ } from "./chunk-YUIHSKR6.js";
8
+
9
+ // src/lib/webhook.ts
10
+ var slog = logger_default.child("webhook");
11
+ function getWebhookUrl() {
12
+ return process.env.VOLUTE_WEBHOOK_URL;
13
+ }
14
+ function getAuthHeaders() {
15
+ const headers = { "Content-Type": "application/json" };
16
+ const secret = process.env.VOLUTE_WEBHOOK_SECRET;
17
+ if (secret) headers.Authorization = `Bearer ${secret}`;
18
+ return headers;
19
+ }
20
+ function fireWebhook(event) {
21
+ try {
22
+ const url = getWebhookUrl();
23
+ if (!url) return;
24
+ const payload = { ...event, timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString() };
25
+ fetch(url, {
26
+ method: "POST",
27
+ headers: getAuthHeaders(),
28
+ body: JSON.stringify(payload)
29
+ }).then((res) => {
30
+ if (!res.ok) {
31
+ slog.warn(`webhook ${event.event} returned HTTP ${res.status}`);
32
+ }
33
+ }).catch((err) => {
34
+ slog.warn(`webhook delivery failed for ${event.event}`, logger_default.errorData(err));
35
+ });
36
+ } catch (err) {
37
+ slog.error(`webhook ${event.event} failed to serialize`, logger_default.errorData(err));
38
+ }
39
+ }
40
+ function initWebhook() {
41
+ const url = getWebhookUrl();
42
+ if (!url) return () => {
43
+ };
44
+ try {
45
+ const parsed = new URL(url);
46
+ if (!["http:", "https:"].includes(parsed.protocol)) {
47
+ slog.error(`VOLUTE_WEBHOOK_URL has unsupported protocol: ${parsed.protocol}`);
48
+ return () => {
49
+ };
50
+ }
51
+ } catch {
52
+ slog.error(`VOLUTE_WEBHOOK_URL is not a valid URL`);
53
+ return () => {
54
+ };
55
+ }
56
+ slog.info("webhook enabled");
57
+ return subscribe((event) => {
58
+ try {
59
+ fireWebhook({
60
+ event: event.type,
61
+ mind: event.mind,
62
+ data: { summary: event.summary, ...event.metadata },
63
+ timestamp: event.created_at
64
+ });
65
+ } catch (err) {
66
+ slog.error(`failed to fire webhook for ${event.type}`, logger_default.errorData(err));
67
+ }
68
+ });
69
+ }
70
+
71
+ export {
72
+ getWebhookUrl,
73
+ getAuthHeaders,
74
+ fireWebhook,
75
+ initWebhook
76
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  voluteHome
4
- } from "./chunk-EBGCNDMM.js";
4
+ } from "./chunk-B2CPS4QU.js";
5
5
 
6
6
  // src/lib/systems-config.ts
7
7
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
@@ -18,7 +18,7 @@ function readSystemsConfig() {
18
18
  try {
19
19
  data = JSON.parse(raw);
20
20
  } catch {
21
- console.error(`Warning: ${path} contains invalid JSON. Run "volute logout" and re-login.`);
21
+ console.error(`Warning: ${path} contains invalid JSON. Run "volute auth logout" and re-login.`);
22
22
  return null;
23
23
  }
24
24
  if (!data.apiKey || !data.system) return null;
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  publish
4
- } from "./chunk-UU7A7KLB.js";
4
+ } from "./chunk-A4S7H6G6.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-YUIHSKR6.js";
8
8
 
9
- // src/lib/mind-activity-tracker.ts
9
+ // src/lib/events/mind-activity-tracker.ts
10
10
  var IDLE_TIMEOUT_MS = 2 * 60 * 1e3;
11
11
  var minds = /* @__PURE__ */ new Map();
12
12
  function getState(mind) {
@@ -4,13 +4,13 @@ import {
4
4
  modeLabel,
5
5
  pollHealth,
6
6
  startService
7
- } from "./chunk-32VR2EOH.js";
7
+ } from "./chunk-VT5QODNE.js";
8
8
  import {
9
9
  parseArgs
10
10
  } from "./chunk-D424ZQGI.js";
11
11
  import {
12
12
  voluteHome
13
- } from "./chunk-EBGCNDMM.js";
13
+ } from "./chunk-B2CPS4QU.js";
14
14
 
15
15
  // src/commands/up.ts
16
16
  import { spawn } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  wrapForIsolation
4
- } from "./chunk-OGXOMR65.js";
4
+ } from "./chunk-NWPT4ASZ.js";
5
5
 
6
6
  // src/lib/exec.ts
7
7
  import { execFile as execFileCb, execFileSync, spawn } from "child_process";
@@ -15,6 +15,7 @@ function exec(cmd, args, options) {
15
15
  (err, stdout, stderr) => {
16
16
  if (err) {
17
17
  err.stderr = stderr;
18
+ err.stdout = stdout;
18
19
  reject(err);
19
20
  } else {
20
21
  resolve(stdout);