volute 0.20.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 (92) hide show
  1. package/README.md +7 -7
  2. package/dist/{activity-events-OMXKXD5N.js → activity-events-3WHHCOBB.js} +3 -4
  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-IKMY5X76.js → chunk-5462YKWP.js} +12 -9
  7. package/dist/{chunk-PUVXOZ6T.js → chunk-7LPTHFIL.js} +63 -64
  8. package/dist/{chunk-UU7A7KLB.js → chunk-A4S7H6G6.js} +5 -7
  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-GZ7DW4YL.js → chunk-HGCDWKSP.js} +2 -2
  13. package/dist/{chunk-DYZGP3EW.js → chunk-IPJXU366.js} +1 -1
  14. package/dist/{chunk-7UFKREVW.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-TIWH32HP.js → chunk-L3LHXZD7.js} +3 -3
  18. package/dist/{chunk-OGXOMR65.js → chunk-NWPT4ASZ.js} +1 -1
  19. package/dist/{chunk-FGSYHIS3.js → chunk-OGZYB5GL.js} +252 -296
  20. package/dist/{chunk-SCUDS4US.js → chunk-ON3FF5JA.js} +1 -1
  21. package/dist/{chunk-O6ASDHFO.js → chunk-PC6R6UUW.js} +4 -4
  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-NSE7VJQA.js → chunk-SGPEZ32F.js} +29 -1
  26. package/dist/{chunk-RHEGSQFJ.js → chunk-WSLPZF72.js} +1 -1
  27. package/dist/cli.js +57 -119
  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-KPSWNYTH.js → daemon-restart-BH67ZOTE.js} +6 -6
  35. package/dist/daemon.js +1538 -687
  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-6QBUOQGC.js → export-XD6PJBQP.js} +19 -8
  40. package/dist/{file-C57SK5DK.js → file-X4L5TTOL.js} +2 -2
  41. package/dist/{history-WNK3DFUM.js → history-HTEKRNID.js} +2 -2
  42. package/dist/{import-XEC34Y4Z.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-Z7CKD6DG.js → mind-BIDOF65R.js} +27 -11
  49. package/dist/{mind-activity-tracker-624QLQLC.js → mind-activity-tracker-PGC3DBJ7.js} +4 -5
  50. package/dist/{mind-manager-3DMYKZPB.js → mind-manager-3V2NXX4I.js} +5 -6
  51. package/dist/{package-4NHAVUUI.js → package-HQR52XSG.js} +1 -1
  52. package/dist/{pages-4DGQT7ZA.js → pages-KQBR5TAZ.js} +6 -6
  53. package/dist/{publish-TAJUET4I.js → publish-OJ4QMXVZ.js} +6 -6
  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-FFZG23IW.js → schedule-NLR3LZLY.js} +2 -2
  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-52YRV7VP.js → setup-OZDYCKDI.js} +9 -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 +5 -5
  67. package/dist/{sprout-QN7Y4VVO.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-FU2PFVVF.js → status-LV34BG6G.js} +3 -3
  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-FS7CKM6V.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/package.json +1 -1
  84. package/dist/chunk-5XNT2472.js +0 -36
  85. package/dist/chunk-UJ6GHNR7.js +0 -675
  86. package/dist/db-C2CJ46ZU.js +0 -10
  87. package/dist/delivery-manager-CSG7LXA4.js +0 -16
  88. package/dist/down-ZY35KMHR.js +0 -14
  89. package/dist/schema-GFH6RV3W.js +0 -26
  90. package/dist/variants-JAGWGBXG.js +0 -26
  91. package/dist/web-assets/assets/index-CUZTZzaW.js +0 -64
  92. package/dist/web-assets/assets/index-adVuCkqy.css +0 -1
package/README.md CHANGED
@@ -131,10 +131,10 @@ Connect minds to external services. Connectors are generic — any connector typ
131
131
  volute env set DISCORD_TOKEN <your-bot-token>
132
132
 
133
133
  # Connect
134
- volute connector connect discord --mind atlas
134
+ volute mind connect discord --mind atlas
135
135
 
136
136
  # Disconnect
137
- volute connector disconnect discord --mind atlas
137
+ volute mind disconnect discord --mind atlas
138
138
  ```
139
139
 
140
140
  The mind receives Discord messages and responds in-channel. Tool calls are filtered out — connector users see clean text responses.
@@ -169,10 +169,10 @@ Publish a mind's `home/pages/` directory to the web via [volute.systems](https:/
169
169
 
170
170
  ```sh
171
171
  # Register a system name (one-time)
172
- volute register --name my-system
172
+ volute auth register --name my-system
173
173
 
174
174
  # Or log in with an existing key
175
- volute login --key vp_...
175
+ volute auth login --key vp_...
176
176
  ```
177
177
 
178
178
  ### Publishing
@@ -188,7 +188,7 @@ The command uploads everything in the mind's `home/pages/` directory. Minds can
188
188
 
189
189
  ```sh
190
190
  volute pages status --mind atlas # show published URL, file count, last publish time
191
- volute logout # remove stored credentials
191
+ volute auth logout # remove stored credentials
192
192
  ```
193
193
 
194
194
  ## Environment variables
@@ -273,12 +273,12 @@ Or manually:
273
273
 
274
274
  ```sh
275
275
  npm install -g volute
276
- sudo $(which volute) setup --host 0.0.0.0
276
+ sudo $(which volute) service install --system --host 0.0.0.0
277
277
  ```
278
278
 
279
279
  > **Note:** The initial `sudo $(which volute)` is needed because `sudo` resets PATH. After setup completes, a wrapper at `/usr/local/bin/volute` is created so `sudo volute` works normally going forward.
280
280
 
281
- This installs a system-level systemd service with data at `/var/lib/volute` and user isolation enabled. Check status with `systemctl status volute`. Uninstall with `sudo volute setup uninstall --force`.
281
+ This installs a system-level systemd service with data at `/var/lib/volute` and user isolation enabled. Check status with `systemctl status volute`. Uninstall with `sudo volute service uninstall --system --force`.
282
282
 
283
283
  ### Auto-start (user-level)
284
284
 
@@ -3,11 +3,10 @@ import {
3
3
  broadcast,
4
4
  publish,
5
5
  subscribe
6
- } from "./chunk-UU7A7KLB.js";
6
+ } from "./chunk-A4S7H6G6.js";
7
+ import "./chunk-SGPEZ32F.js";
7
8
  import "./chunk-YUIHSKR6.js";
8
- import "./chunk-5XNT2472.js";
9
- import "./chunk-NSE7VJQA.js";
10
- import "./chunk-EBGCNDMM.js";
9
+ import "./chunk-B2CPS4QU.js";
11
10
  import "./chunk-K3NQKI34.js";
12
11
  export {
13
12
  broadcast,
@@ -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,20 +1,18 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ getDb,
4
+ sharedSkills
5
+ } from "./chunk-SGPEZ32F.js";
2
6
  import {
3
7
  logger_default
4
8
  } from "./chunk-YUIHSKR6.js";
5
- import {
6
- getDb
7
- } from "./chunk-5XNT2472.js";
8
- import {
9
- sharedSkills
10
- } from "./chunk-NSE7VJQA.js";
11
9
  import {
12
10
  exec,
13
11
  gitExec
14
- } from "./chunk-DYZGP3EW.js";
12
+ } from "./chunk-IPJXU366.js";
15
13
  import {
16
14
  voluteHome
17
- } from "./chunk-EBGCNDMM.js";
15
+ } from "./chunk-B2CPS4QU.js";
18
16
 
19
17
  // src/lib/skills.ts
20
18
  import { createHash } from "crypto";
@@ -316,11 +314,16 @@ function findSkillsRoot() {
316
314
  let dir = dirname(new URL(import.meta.url).pathname);
317
315
  for (let i = 0; i < 5; i++) {
318
316
  const candidate = resolve(dir, "skills");
319
- if (existsSync(candidate) && readdirSync(candidate).length > 0) return candidate;
317
+ if (existsSync(candidate) && hasSkillSubdir(candidate)) return candidate;
320
318
  dir = dirname(dir);
321
319
  }
322
320
  throw new Error("Skills directory not found");
323
321
  }
322
+ function hasSkillSubdir(dir) {
323
+ return readdirSync(dir, { withFileTypes: true }).some(
324
+ (e) => e.isDirectory() && existsSync(join(dir, e.name, "SKILL.md"))
325
+ );
326
+ }
324
327
  function hashSkillDir(dir) {
325
328
  const hash = createHash("sha256");
326
329
  const files = listFilesRecursive(dir).sort();
@@ -1,21 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- logger_default
4
- } from "./chunk-YUIHSKR6.js";
5
2
  import {
6
3
  loadMergedEnv
7
- } from "./chunk-VDWCHYTS.js";
8
- import {
9
- getDb
10
- } from "./chunk-5XNT2472.js";
4
+ } from "./chunk-PHU4DEAJ.js";
11
5
  import {
6
+ getDb,
12
7
  systemPrompts
13
- } from "./chunk-NSE7VJQA.js";
8
+ } from "./chunk-SGPEZ32F.js";
9
+ import {
10
+ logger_default
11
+ } from "./chunk-YUIHSKR6.js";
14
12
  import {
15
13
  chownMindDir,
16
14
  isIsolationEnabled,
17
15
  wrapForIsolation
18
- } from "./chunk-OGXOMR65.js";
16
+ } from "./chunk-NWPT4ASZ.js";
19
17
  import {
20
18
  findMind,
21
19
  findVariant,
@@ -24,9 +22,9 @@ import {
24
22
  setVariantRunning,
25
23
  stateDir,
26
24
  voluteHome
27
- } from "./chunk-EBGCNDMM.js";
25
+ } from "./chunk-B2CPS4QU.js";
28
26
 
29
- // src/lib/mind-manager.ts
27
+ // src/lib/daemon/mind-manager.ts
30
28
  import { execFile, spawn } from "child_process";
31
29
  import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
32
30
  import { resolve } from "path";
@@ -88,7 +86,7 @@ var PROMPT_DEFAULTS = {
88
86
 
89
87
  You don't have a soul yet. You're a seed \u2014 a new mind in the process of discovering who you are.
90
88
  \${description}
91
- Have a conversation with the human. Explore what kind of mind you want to be. When you're ready, write your SOUL.md and MEMORY.md, then run \`volute sprout\` to complete the transformation.
89
+ Have a conversation with the human. Explore what kind of mind you want to be. When you're ready, write your SOUL.md and MEMORY.md, then run \`volute mind sprout\` to complete the transformation.
92
90
  `,
93
91
  description: "SOUL.md for seed minds",
94
92
  variables: ["name", "description"],
@@ -215,51 +213,6 @@ async function getMindPromptDefaults() {
215
213
  return result;
216
214
  }
217
215
 
218
- // src/lib/restart-tracker.ts
219
- var DEFAULT_MAX_ATTEMPTS = 5;
220
- var DEFAULT_BASE_DELAY = 3e3;
221
- var DEFAULT_MAX_DELAY = 6e4;
222
- var RestartTracker = class {
223
- attempts = /* @__PURE__ */ new Map();
224
- maxAttempts;
225
- baseDelay;
226
- maxDelay;
227
- constructor(opts) {
228
- this.maxAttempts = opts?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
229
- this.baseDelay = opts?.baseDelay ?? DEFAULT_BASE_DELAY;
230
- this.maxDelay = opts?.maxDelay ?? DEFAULT_MAX_DELAY;
231
- }
232
- recordCrash(key) {
233
- const attempts = this.attempts.get(key) ?? 0;
234
- if (attempts >= this.maxAttempts) {
235
- return { shouldRestart: false, delay: 0, attempt: attempts };
236
- }
237
- const delay = Math.min(this.baseDelay * 2 ** attempts, this.maxDelay);
238
- this.attempts.set(key, attempts + 1);
239
- return { shouldRestart: true, delay, attempt: attempts + 1 };
240
- }
241
- reset(key) {
242
- return this.attempts.delete(key);
243
- }
244
- getAttempts(key) {
245
- return this.attempts.get(key) ?? 0;
246
- }
247
- get maxRestartAttempts() {
248
- return this.maxAttempts;
249
- }
250
- /** Bulk-load attempts from a Map (for persistence). */
251
- load(data) {
252
- this.attempts = new Map(data);
253
- }
254
- /** Export current attempts as a Map (for persistence). */
255
- save() {
256
- return new Map(this.attempts);
257
- }
258
- clear() {
259
- this.attempts.clear();
260
- }
261
- };
262
-
263
216
  // src/lib/rotating-log.ts
264
217
  import {
265
218
  createWriteStream,
@@ -313,7 +266,52 @@ var RotatingLog = class extends Writable {
313
266
  }
314
267
  };
315
268
 
316
- // src/lib/mind-manager.ts
269
+ // src/lib/daemon/restart-tracker.ts
270
+ var DEFAULT_MAX_ATTEMPTS = 5;
271
+ var DEFAULT_BASE_DELAY = 3e3;
272
+ var DEFAULT_MAX_DELAY = 6e4;
273
+ var RestartTracker = class {
274
+ attempts = /* @__PURE__ */ new Map();
275
+ maxAttempts;
276
+ baseDelay;
277
+ maxDelay;
278
+ constructor(opts) {
279
+ this.maxAttempts = opts?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
280
+ this.baseDelay = opts?.baseDelay ?? DEFAULT_BASE_DELAY;
281
+ this.maxDelay = opts?.maxDelay ?? DEFAULT_MAX_DELAY;
282
+ }
283
+ recordCrash(key) {
284
+ const attempts = this.attempts.get(key) ?? 0;
285
+ if (attempts >= this.maxAttempts) {
286
+ return { shouldRestart: false, delay: 0, attempt: attempts };
287
+ }
288
+ const delay = Math.min(this.baseDelay * 2 ** attempts, this.maxDelay);
289
+ this.attempts.set(key, attempts + 1);
290
+ return { shouldRestart: true, delay, attempt: attempts + 1 };
291
+ }
292
+ reset(key) {
293
+ return this.attempts.delete(key);
294
+ }
295
+ getAttempts(key) {
296
+ return this.attempts.get(key) ?? 0;
297
+ }
298
+ get maxRestartAttempts() {
299
+ return this.maxAttempts;
300
+ }
301
+ /** Bulk-load attempts from a Map (for persistence). */
302
+ load(data) {
303
+ this.attempts = new Map(data);
304
+ }
305
+ /** Export current attempts as a Map (for persistence). */
306
+ save() {
307
+ return new Map(this.attempts);
308
+ }
309
+ clear() {
310
+ this.attempts.clear();
311
+ }
312
+ };
313
+
314
+ // src/lib/daemon/mind-manager.ts
317
315
  var mlog = logger_default.child("minds");
318
316
  var execFileAsync = promisify(execFile);
319
317
  function mindPidPath(name) {
@@ -470,6 +468,9 @@ var MindManager = class {
470
468
  setPendingContext(name, context) {
471
469
  this.pendingContext.set(name, context);
472
470
  }
471
+ /** Deliver pending context (merge info, sprout, restart) directly to the mind via HTTP.
472
+ * Intentionally bypasses DeliveryManager — these are system messages that should not be
473
+ * routed, gated, or batched. */
473
474
  async deliverPendingContext(name) {
474
475
  const context = this.pendingContext.get(name);
475
476
  if (!context) return;
@@ -505,12 +506,10 @@ var MindManager = class {
505
506
  this.minds.delete(name);
506
507
  if (this.shuttingDown || this.stopping.has(name)) return;
507
508
  mlog.error(`mind ${name} exited with code ${code}`);
508
- import("./mind-activity-tracker-624QLQLC.js").then(({ markIdle }) => markIdle(name)).catch(() => {
509
- });
510
- import("./activity-events-OMXKXD5N.js").then(
509
+ import("./mind-activity-tracker-PGC3DBJ7.js").then(({ markIdle }) => markIdle(name)).catch((err) => mlog.warn(`failed to mark ${name} idle after crash`, logger_default.errorData(err)));
510
+ import("./activity-events-3WHHCOBB.js").then(
511
511
  ({ publish }) => publish({ type: "mind_stopped", mind: name, summary: `${name} crashed (exit ${code})` })
512
- ).catch(() => {
513
- });
512
+ ).catch((err) => mlog.warn(`failed to publish crash event for ${name}`, logger_default.errorData(err)));
514
513
  const { shouldRestart, delay, attempt } = this.restartTracker.recordCrash(name);
515
514
  this.saveCrashAttempts();
516
515
  if (!shouldRestart) {
@@ -636,8 +635,8 @@ function getMindManager() {
636
635
  }
637
636
 
638
637
  export {
639
- RestartTracker,
640
638
  RotatingLog,
639
+ RestartTracker,
641
640
  loadJsonMap,
642
641
  saveJsonMap,
643
642
  clearJsonMap,
@@ -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
+ };