volute 0.19.0 → 0.21.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 (104) hide show
  1. package/README.md +68 -68
  2. package/dist/activity-events-3WHHCOBB.js +15 -0
  3. package/dist/{archive-ZCFOSTKB.js → archive-4ZQYK5MN.js} +4 -2
  4. package/dist/auth-HM2RSPY7.js +37 -0
  5. package/dist/{channel-PUQKGSQM.js → channel-BOOMFULW.js} +2 -2
  6. package/dist/{chunk-OTWLI7F4.js → chunk-5462YKWP.js} +12 -9
  7. package/dist/{chunk-2TJGRJ4O.js → chunk-7LPTHFIL.js} +64 -59
  8. package/dist/chunk-A4S7H6G6.js +56 -0
  9. package/dist/chunk-AKPFNL7L.js +148 -0
  10. package/dist/{chunk-EBGCNDMM.js → chunk-B2CPS4QU.js} +128 -114
  11. package/dist/{chunk-FCDU5BFX.js → chunk-HFCBO2GL.js} +2 -2
  12. package/dist/chunk-HGCDWKSP.js +97 -0
  13. package/dist/{chunk-DYZGP3EW.js → chunk-IPJXU366.js} +1 -1
  14. package/dist/{chunk-VE4D3GOP.js → chunk-J5A3DF2U.js} +2 -2
  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-4KPUF5JD.js → chunk-L3LHXZD7.js} +18 -5
  18. package/dist/{chunk-OGXOMR65.js → chunk-NWPT4ASZ.js} +1 -1
  19. package/dist/{chunk-FGV2H4TX.js → chunk-OGZYB5GL.js} +312 -268
  20. package/dist/{chunk-SCUDS4US.js → chunk-ON3FF5JA.js} +1 -1
  21. package/dist/{chunk-EMQSAY3B.js → chunk-PC6R6UUW.js} +6 -5
  22. package/dist/{chunk-VDWCHYTS.js → chunk-PHU4DEAJ.js} +1 -1
  23. package/dist/{chunk-7NO7EV5Z.js → chunk-Q7AITQ44.js} +2 -2
  24. package/dist/{chunk-32VR2EOH.js → chunk-QUJUKM4U.js} +2 -2
  25. package/dist/{chunk-VQWDC6UK.js → chunk-SGPEZ32F.js} +46 -1
  26. package/dist/{chunk-RHEGSQFJ.js → chunk-WSLPZF72.js} +1 -1
  27. package/dist/cli.js +59 -111
  28. package/dist/{connector-JBVNZ7VK.js → connector-PYT5UOTZ.js} +6 -6
  29. package/dist/connectors/discord.js +2 -2
  30. package/dist/connectors/slack.js +2 -2
  31. package/dist/connectors/telegram.js +2 -2
  32. package/dist/{create-HP4OVVHF.js → create-WIDA3M4C.js} +1 -1
  33. package/dist/{daemon-client-ITWUCNFO.js → daemon-client-ZHCDL4RS.js} +2 -2
  34. package/dist/{daemon-restart-JMZM3QY4.js → daemon-restart-BH67ZOTE.js} +8 -8
  35. package/dist/daemon.js +2872 -1301
  36. package/dist/{delete-BSU7K3RY.js → delete-LOIANQGD.js} +1 -1
  37. package/dist/down-LIOQ5JDH.js +14 -0
  38. package/dist/{env-A3LMO777.js → env-4PHIHTF4.js} +2 -2
  39. package/dist/{export-GCDNQCF3.js → export-XD6PJBQP.js} +19 -8
  40. package/dist/file-X4L5TTOL.js +204 -0
  41. package/dist/{history-WNK3DFUM.js → history-HTEKRNID.js} +2 -2
  42. package/dist/{import-M63VIUJ5.js → import-E433B4KG.js} +3 -3
  43. package/dist/{log-PPPZDVEF.js → log-SRO5Q6AD.js} +2 -2
  44. package/dist/{login-HNH3EUQV.js → login-UO6AOVEA.js} +4 -4
  45. package/dist/{logout-I5CB5UZS.js → logout-UKD5LA37.js} +2 -2
  46. package/dist/{logs-SF2IMJN4.js → logs-HNTNNBDW.js} +2 -2
  47. package/dist/{merge-33C237A4.js → merge-B6SYTGI7.js} +2 -2
  48. package/dist/{mind-PQ5NCPSU.js → mind-BIDOF65R.js} +27 -11
  49. package/dist/mind-activity-tracker-PGC3DBJ7.js +18 -0
  50. package/dist/{mind-manager-RVCFROAY.js → mind-manager-3V2NXX4I.js} +5 -6
  51. package/dist/{package-MYE2ZJLV.js → package-HQR52XSG.js} +1 -1
  52. package/dist/{pages-AXCOSY3P.js → pages-KQBR5TAZ.js} +6 -6
  53. package/dist/{publish-YB377JB7.js → publish-OJ4QMXVZ.js} +12 -9
  54. package/dist/{pull-XAEWQJ47.js → pull-GRQAXM2E.js} +2 -2
  55. package/dist/{register-VSPCMHKX.js → register-U2UO6TC4.js} +5 -5
  56. package/dist/registry-D2BSQ2X5.js +42 -0
  57. package/dist/{restart-IQKMCK5M.js → restart-CIDAKGG2.js} +3 -6
  58. package/dist/{schedule-LMX7GAQZ.js → schedule-NLR3LZLY.js} +27 -7
  59. package/dist/{seed-J43YDKXG.js → seed-3H2MRREW.js} +2 -2
  60. package/dist/{send-KVIZIGCE.js → send-RP2TA7SG.js} +132 -36
  61. package/dist/{service-LUR7WDO7.js → service-TVNEORO7.js} +31 -13
  62. package/dist/{setup-OH3PJUJO.js → setup-OZDYCKDI.js} +25 -34
  63. package/dist/{shared-KO35ZM44.js → shared-DCQ2UXOM.js} +4 -4
  64. package/dist/{skill-BCVNI6TV.js → skill-Q2Y6PQ3L.js} +2 -2
  65. package/dist/skills/orientation/SKILL.md +2 -2
  66. package/dist/skills/volute-mind/SKILL.md +38 -8
  67. package/dist/{sprout-VBEX63LX.js → sprout-6Z6C42YM.js} +34 -30
  68. package/dist/{start-I5JYB65M.js → start-JR6CUUWF.js} +3 -6
  69. package/dist/{status-D7E5HHBV.js → status-5XDGYHKP.js} +2 -2
  70. package/dist/{status-JCJAOXTW.js → status-LV34BG6G.js} +6 -5
  71. package/dist/{status-4ESFLGH4.js → status-Z7NAFMBI.js} +5 -5
  72. package/dist/{stop-NBVKEFQQ.js → stop-VKPGK25U.js} +2 -5
  73. package/dist/template-hash-BIMA4ILT.js +8 -0
  74. package/dist/{up-WG65SWJU.js → up-7BGDMFRT.js} +5 -5
  75. package/dist/{update-FJIHDJKM.js → update-4WT7VWHW.js} +5 -5
  76. package/dist/{update-check-MWE5AH4U.js → update-check-F5Z3ALXX.js} +2 -2
  77. package/dist/{upgrade-AIT24B5I.js → upgrade-ZEC2GGFO.js} +1 -1
  78. package/dist/{variant-63ZWO2W7.js → variant-A4I7PHXS.js} +16 -24
  79. package/dist/version-notify-TFS2U5CF.js +173 -0
  80. package/dist/web-assets/assets/index-BR3gtK3E.css +1 -0
  81. package/dist/web-assets/assets/index-CWmrZRQd.js +64 -0
  82. package/dist/web-assets/index.html +2 -2
  83. package/drizzle/0012_activity.sql +11 -0
  84. package/drizzle/meta/0012_snapshot.json +7 -0
  85. package/drizzle/meta/_journal.json +7 -0
  86. package/package.json +1 -1
  87. package/templates/_base/home/.config/routes.json +2 -2
  88. package/templates/_base/home/VOLUTE.md +1 -1
  89. package/templates/_base/src/lib/daemon-client.ts +22 -0
  90. package/templates/_base/src/lib/transparency.ts +1 -1
  91. package/templates/claude/.init/.config/routes.json +7 -1
  92. package/templates/pi/.init/.config/routes.json +7 -1
  93. package/templates/pi/src/agent.ts +11 -5
  94. package/templates/pi/src/lib/session-context-extension.ts +6 -4
  95. package/templates/pi/src/server.ts +2 -0
  96. package/dist/chunk-UJ6GHNR7.js +0 -675
  97. package/dist/chunk-Z524RFCJ.js +0 -36
  98. package/dist/db-5ZVC6MQF.js +0 -10
  99. package/dist/delivery-manager-ISTJMZDW.js +0 -16
  100. package/dist/down-ZY35KMHR.js +0 -14
  101. package/dist/schema-5BW7DFZI.js +0 -24
  102. package/dist/variants-JAGWGBXG.js +0 -26
  103. package/dist/web-assets/assets/index-BAbuRsVF.css +0 -1
  104. package/dist/web-assets/assets/index-CiQhSKi_.js +0 -63
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ activity,
4
+ getDb
5
+ } from "./chunk-SGPEZ32F.js";
6
+ import {
7
+ logger_default
8
+ } from "./chunk-YUIHSKR6.js";
9
+
10
+ // src/lib/events/activity-events.ts
11
+ var subscribers = /* @__PURE__ */ new Set();
12
+ function subscribe(callback) {
13
+ subscribers.add(callback);
14
+ return () => {
15
+ subscribers.delete(callback);
16
+ };
17
+ }
18
+ async function publish(event) {
19
+ const created_at = event.created_at ?? (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
20
+ let id = 0;
21
+ try {
22
+ const db = await getDb();
23
+ const result = await db.insert(activity).values({
24
+ type: event.type,
25
+ mind: event.mind,
26
+ summary: event.summary,
27
+ metadata: event.metadata ? JSON.stringify(event.metadata) : null,
28
+ created_at
29
+ });
30
+ id = Number(result.lastInsertRowid);
31
+ } catch (err) {
32
+ logger_default.error("[activity-events] failed to persist activity", logger_default.errorData(err));
33
+ }
34
+ const full = { ...event, id, created_at };
35
+ notify(full);
36
+ }
37
+ function broadcast(event) {
38
+ const created_at = event.created_at ?? (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
39
+ notify({ ...event, id: 0, created_at });
40
+ }
41
+ function notify(event) {
42
+ for (const cb of subscribers) {
43
+ try {
44
+ cb(event);
45
+ } catch (err) {
46
+ logger_default.error("[activity-events] subscriber threw:", logger_default.errorData(err));
47
+ subscribers.delete(cb);
48
+ }
49
+ }
50
+ }
51
+
52
+ export {
53
+ subscribe,
54
+ publish,
55
+ broadcast
56
+ };
@@ -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,
@@ -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;
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ publish
4
+ } from "./chunk-A4S7H6G6.js";
5
+ import {
6
+ logger_default
7
+ } from "./chunk-YUIHSKR6.js";
8
+
9
+ // src/lib/events/mind-activity-tracker.ts
10
+ var IDLE_TIMEOUT_MS = 2 * 60 * 1e3;
11
+ var minds = /* @__PURE__ */ new Map();
12
+ function getState(mind) {
13
+ let state = minds.get(mind);
14
+ if (!state) {
15
+ state = { active: false, idleTimer: null };
16
+ minds.set(mind, state);
17
+ }
18
+ return state;
19
+ }
20
+ var IGNORED_EVENTS = /* @__PURE__ */ new Set(["done", "usage", "log"]);
21
+ function onMindEvent(mind, type, channel) {
22
+ const state = getState(mind);
23
+ if (type === "done") {
24
+ state.active = false;
25
+ if (state.idleTimer) {
26
+ clearTimeout(state.idleTimer);
27
+ }
28
+ state.idleTimer = setTimeout(() => {
29
+ state.idleTimer = null;
30
+ if (!state.active) {
31
+ publish({
32
+ type: "mind_idle",
33
+ mind,
34
+ summary: `${mind} is idle`
35
+ }).catch((err) => {
36
+ logger_default.error("[mind-activity] failed to publish mind_idle", logger_default.errorData(err));
37
+ });
38
+ }
39
+ }, IDLE_TIMEOUT_MS);
40
+ } else if (!IGNORED_EVENTS.has(type)) {
41
+ if (state.idleTimer) {
42
+ clearTimeout(state.idleTimer);
43
+ state.idleTimer = null;
44
+ }
45
+ if (!state.active) {
46
+ state.active = true;
47
+ state.channel = channel;
48
+ publish({
49
+ type: "mind_active",
50
+ mind,
51
+ summary: `${mind} is active`,
52
+ metadata: channel ? { channel } : void 0
53
+ }).catch((err) => {
54
+ logger_default.error("[mind-activity] failed to publish mind_active", logger_default.errorData(err));
55
+ });
56
+ }
57
+ }
58
+ }
59
+ function markIdle(mind) {
60
+ const state = minds.get(mind);
61
+ if (!state) return;
62
+ if (state.idleTimer) {
63
+ clearTimeout(state.idleTimer);
64
+ state.idleTimer = null;
65
+ }
66
+ if (state.active) {
67
+ state.active = false;
68
+ publish({
69
+ type: "mind_idle",
70
+ mind,
71
+ summary: `${mind} is idle`
72
+ }).catch((err) => {
73
+ logger_default.error("[mind-activity] failed to publish mind_idle", logger_default.errorData(err));
74
+ });
75
+ }
76
+ minds.delete(mind);
77
+ }
78
+ function getActiveMinds() {
79
+ const result = [];
80
+ for (const [mind, state] of minds) {
81
+ if (state.active) result.push(mind);
82
+ }
83
+ return result;
84
+ }
85
+ function stopAll() {
86
+ for (const [, state] of minds) {
87
+ if (state.idleTimer) clearTimeout(state.idleTimer);
88
+ }
89
+ minds.clear();
90
+ }
91
+
92
+ export {
93
+ onMindEvent,
94
+ markIdle,
95
+ getActiveMinds,
96
+ stopAll
97
+ };
@@ -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";
@@ -4,13 +4,13 @@ import {
4
4
  modeLabel,
5
5
  pollHealth,
6
6
  startService
7
- } from "./chunk-32VR2EOH.js";
7
+ } from "./chunk-QUJUKM4U.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
  voluteHome
4
- } from "./chunk-EBGCNDMM.js";
4
+ } from "./chunk-B2CPS4QU.js";
5
5
 
6
6
  // src/lib/daemon-client.ts
7
7
  import { existsSync, readFileSync } from "fs";
@@ -11,7 +11,7 @@ function readDaemonConfig() {
11
11
  if (!existsSync(configPath)) {
12
12
  if (existsSync("/etc/systemd/system/volute.service") && !process.env.VOLUTE_HOME) {
13
13
  console.error("Volute is running as a system service but VOLUTE_HOME is not set.");
14
- console.error("Re-run setup to update the CLI wrapper: sudo volute setup");
14
+ console.error("Re-run setup to update the CLI wrapper: sudo volute service install --system");
15
15
  console.error("Then start a new shell or run: source /etc/profile.d/volute.sh");
16
16
  } else {
17
17
  console.error("Volute is not running. Start with: volute up");