volute 0.24.0 → 0.25.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 (40) hide show
  1. package/dist/api.d.ts +113 -4
  2. package/dist/{chunk-4TJ72QQ3.js → chunk-BOTQ25QT.js} +1 -1
  3. package/dist/{chunk-P3W36ZGD.js → chunk-DG7TO7EE.js} +30 -2
  4. package/dist/{chunk-OOW675I3.js → chunk-PMX4EIJK.js} +39 -20
  5. package/dist/{chunk-NOBRGACV.js → chunk-SHSWYG2J.js} +1 -1
  6. package/dist/{chunk-XLC342FO.js → chunk-SIAG3QMM.js} +14 -1
  7. package/dist/cli.js +6 -6
  8. package/dist/{cloud-sync-DIU3OCPV.js → cloud-sync-PPBBJDY6.js} +3 -3
  9. package/dist/{daemon-restart-YMPEATQH.js → daemon-restart-FDNOZEAD.js} +1 -1
  10. package/dist/daemon.js +453 -239
  11. package/dist/{import-FRDPQPJ2.js → import-TH26J76F.js} +2 -2
  12. package/dist/{message-delivery-S7BCNV6Y.js → message-delivery-XMGV3FUM.js} +3 -3
  13. package/dist/{mind-KPLCRKQA.js → mind-YVWAHL2A.js} +2 -2
  14. package/dist/{mind-manager-ZNRIYEK3.js → mind-manager-4NDNAYAB.js} +1 -1
  15. package/dist/{package-S5YF25XV.js → package-3HF5MXU2.js} +2 -1
  16. package/dist/{pages-TWR6U7DS.js → pages-Y6DRWUOJ.js} +1 -1
  17. package/dist/{publish-BZNHKUUK.js → publish-EEKTZBHW.js} +1 -1
  18. package/dist/{skill-BQOFACEI.js → skill-T3EMR6IR.js} +10 -2
  19. package/dist/skills/imagegen/SKILL.md +37 -0
  20. package/dist/skills/imagegen/references/INSTALL.md +13 -0
  21. package/dist/skills/imagegen/scripts/imagegen.ts +136 -0
  22. package/dist/skills/resonance/SKILL.md +73 -0
  23. package/dist/skills/resonance/assets/default-config.json +21 -0
  24. package/dist/skills/resonance/references/INSTALL.md +23 -0
  25. package/dist/skills/resonance/scripts/resonance.ts +1250 -0
  26. package/dist/skills/volute-mind/SKILL.md +23 -3
  27. package/dist/{sleep-manager-XXSWQQLE.js → sleep-manager-RKTFZPD3.js} +3 -3
  28. package/dist/{sprout-CGSW4CF5.js → sprout-QJVGJDSH.js} +1 -1
  29. package/dist/{up-OMHACRJL.js → up-CJ26KQLN.js} +1 -1
  30. package/dist/{version-notify-SZ75QRGO.js → version-notify-AZQMC32A.js} +3 -3
  31. package/dist/web-assets/assets/index-CGPSVu19.js +69 -0
  32. package/dist/web-assets/assets/index-V_rNDsM8.css +1 -0
  33. package/dist/web-assets/favicon.png +0 -0
  34. package/dist/web-assets/index.html +5 -4
  35. package/dist/web-assets/logo.png +0 -0
  36. package/package.json +2 -1
  37. package/templates/_base/home/public/.gitkeep +0 -0
  38. package/dist/web-assets/assets/index-Bx9WDoaQ.js +0 -69
  39. package/dist/web-assets/assets/index-Clz8OhmJ.css +0 -1
  40. /package/dist/{chunk-TQDITGES.js → chunk-ZSH4G2P5.js} +0 -0
package/dist/api.d.ts CHANGED
@@ -522,6 +522,113 @@ declare const routes: hono_hono_base.HonoBase<hono_types.BlankEnv, hono_types.Bl
522
522
  status: hono_utils_http_status.ContentfulStatusCode;
523
523
  };
524
524
  };
525
+ } & {
526
+ "/register": {
527
+ $post: {
528
+ input: {
529
+ json: {
530
+ name: string;
531
+ };
532
+ };
533
+ output: {
534
+ error: string;
535
+ };
536
+ outputFormat: "json";
537
+ status: 400;
538
+ } | {
539
+ input: {
540
+ json: {
541
+ name: string;
542
+ };
543
+ };
544
+ output: {
545
+ error: string;
546
+ };
547
+ outputFormat: "json";
548
+ status: 502;
549
+ } | {
550
+ input: {
551
+ json: {
552
+ name: string;
553
+ };
554
+ };
555
+ output: {
556
+ error: string;
557
+ };
558
+ outputFormat: "json";
559
+ status: 500;
560
+ } | {
561
+ input: {
562
+ json: {
563
+ name: string;
564
+ };
565
+ };
566
+ output: {
567
+ system: string;
568
+ };
569
+ outputFormat: "json";
570
+ status: hono_utils_http_status.ContentfulStatusCode;
571
+ };
572
+ };
573
+ } & {
574
+ "/login": {
575
+ $post: {
576
+ input: {
577
+ json: {
578
+ key: string;
579
+ };
580
+ };
581
+ output: {
582
+ error: string;
583
+ };
584
+ outputFormat: "json";
585
+ status: 400;
586
+ } | {
587
+ input: {
588
+ json: {
589
+ key: string;
590
+ };
591
+ };
592
+ output: {
593
+ error: string;
594
+ };
595
+ outputFormat: "json";
596
+ status: 502;
597
+ } | {
598
+ input: {
599
+ json: {
600
+ key: string;
601
+ };
602
+ };
603
+ output: {
604
+ error: string;
605
+ };
606
+ outputFormat: "json";
607
+ status: 500;
608
+ } | {
609
+ input: {
610
+ json: {
611
+ key: string;
612
+ };
613
+ };
614
+ output: {
615
+ system: string;
616
+ };
617
+ outputFormat: "json";
618
+ status: hono_utils_http_status.ContentfulStatusCode;
619
+ };
620
+ };
621
+ } & {
622
+ "/logout": {
623
+ $post: {
624
+ input: {};
625
+ output: {
626
+ ok: true;
627
+ };
628
+ outputFormat: "json";
629
+ status: hono_utils_http_status.ContentfulStatusCode;
630
+ };
631
+ };
525
632
  }, "/api/system"> | hono_types.MergeSchemaPath<{
526
633
  "/update": {
527
634
  $get: {
@@ -3218,10 +3325,12 @@ declare const routes: hono_hono_base.HonoBase<hono_types.BlankEnv, hono_types.Bl
3218
3325
  };
3219
3326
  };
3220
3327
  output: {
3221
- error: string;
3328
+ installNotes: string | null;
3329
+ npmInstalled: string[];
3330
+ ok: true;
3222
3331
  };
3223
3332
  outputFormat: "json";
3224
- status: 400;
3333
+ status: hono_utils_http_status.ContentfulStatusCode;
3225
3334
  } | {
3226
3335
  input: {
3227
3336
  json: {
@@ -3233,10 +3342,10 @@ declare const routes: hono_hono_base.HonoBase<hono_types.BlankEnv, hono_types.Bl
3233
3342
  };
3234
3343
  };
3235
3344
  output: {
3236
- ok: true;
3345
+ error: string;
3237
3346
  };
3238
3347
  outputFormat: "json";
3239
- status: hono_utils_http_status.ContentfulStatusCode;
3348
+ status: 400;
3240
3349
  };
3241
3350
  };
3242
3351
  } & {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readVoluteConfig,
4
4
  writeVoluteConfig
5
- } from "./chunk-XLC342FO.js";
5
+ } from "./chunk-SIAG3QMM.js";
6
6
  import {
7
7
  mindEnvPath,
8
8
  readEnv,
@@ -41,13 +41,15 @@ function sharedSkillsDir() {
41
41
  }
42
42
  function parseSkillMd(content) {
43
43
  const match = content.match(/^---\n([\s\S]*?)\n---/);
44
- if (!match) return { name: "", description: "" };
44
+ if (!match) return { name: "", description: "", npmDependencies: [] };
45
45
  const frontmatter = match[1];
46
46
  const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
47
47
  const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
48
+ const depsMatch = frontmatter.match(/^\s*npm-dependencies:\s*(.+)$/m);
48
49
  return {
49
50
  name: nameMatch?.[1].trim() ?? "",
50
- description: descMatch?.[1].trim() ?? ""
51
+ description: descMatch?.[1].trim() ?? "",
52
+ npmDependencies: depsMatch ? depsMatch[1].trim().split(/[\s,]+/).filter(Boolean) : []
51
53
  };
52
54
  }
53
55
  async function listSharedSkills() {
@@ -127,7 +129,32 @@ async function installSkill(_mindName, dir, skillId) {
127
129
  if (existsSync(destDir)) throw new Error(`Skill already installed: ${skillId}`);
128
130
  mkdirSync(destDir, { recursive: true });
129
131
  cpSync(sourceDir, destDir, { recursive: true });
132
+ const npmInstalled = [];
133
+ const skillMdPath = join(sourceDir, "SKILL.md");
134
+ if (existsSync(skillMdPath)) {
135
+ const { npmDependencies } = parseSkillMd(readFileSync(skillMdPath, "utf-8"));
136
+ if (npmDependencies.length > 0) {
137
+ try {
138
+ await exec("npm", ["install", ...npmDependencies], { cwd: dir });
139
+ npmInstalled.push(...npmDependencies);
140
+ } catch (e) {
141
+ rmSync(destDir, { recursive: true });
142
+ const msg = e instanceof Error ? e.message : String(e);
143
+ throw new Error(
144
+ `Failed to install npm dependencies (${npmDependencies.join(", ")}): ${msg}`
145
+ );
146
+ }
147
+ }
148
+ }
149
+ let installNotes = null;
150
+ const installMdPath = join(destDir, "references", "INSTALL.md");
151
+ if (existsSync(installMdPath)) {
152
+ installNotes = readFileSync(installMdPath, "utf-8");
153
+ }
130
154
  await gitExec(["add", join("home", ".claude", "skills", skillId)], { cwd: dir });
155
+ if (npmInstalled.length > 0) {
156
+ await gitExec(["add", "package.json", "package-lock.json"], { cwd: dir });
157
+ }
131
158
  await gitExec(["commit", "-m", `Install shared skill: ${skillId}`], { cwd: dir });
132
159
  const commitHash = (await gitExec(["rev-parse", "HEAD"], { cwd: dir })).trim();
133
160
  const upstream = {
@@ -141,6 +168,7 @@ async function installSkill(_mindName, dir, skillId) {
141
168
  cwd: dir
142
169
  });
143
170
  await gitExec(["commit", "--amend", "--no-edit"], { cwd: dir });
171
+ return { installNotes, npmInstalled };
144
172
  }
145
173
  async function uninstallSkill(_mindName, dir, skillId) {
146
174
  validateSkillId(skillId);
@@ -6,6 +6,7 @@ import {
6
6
  markIdle
7
7
  } from "./chunk-E7GOKNOT.js";
8
8
  import {
9
+ broadcast,
9
10
  publish,
10
11
  subscribe
11
12
  } from "./chunk-BFK6SOEJ.js";
@@ -17,10 +18,10 @@ import {
17
18
  getPrompt,
18
19
  loadJsonMap,
19
20
  saveJsonMap
20
- } from "./chunk-NOBRGACV.js";
21
+ } from "./chunk-SHSWYG2J.js";
21
22
  import {
22
23
  readVoluteConfig
23
- } from "./chunk-XLC342FO.js";
24
+ } from "./chunk-SIAG3QMM.js";
24
25
  import {
25
26
  loadMergedEnv
26
27
  } from "./chunk-PHU4DEAJ.js";
@@ -185,12 +186,16 @@ async function updateUserProfile(userId, profile) {
185
186
  }
186
187
  async function syncMindProfile(mindName, config) {
187
188
  const user = await getOrCreateMindUser(mindName);
188
- const db = await getDb();
189
- await db.update(users).set({
189
+ const newProfile = {
190
190
  display_name: config.displayName ?? null,
191
191
  description: config.description ?? null,
192
192
  avatar: config.avatar ?? null
193
- }).where(eq(users.id, user.id));
193
+ };
194
+ const changed = user.display_name !== newProfile.display_name || user.description !== newProfile.description || user.avatar !== newProfile.avatar;
195
+ if (!changed) return;
196
+ const db = await getDb();
197
+ await db.update(users).set(newProfile).where(eq(users.id, user.id));
198
+ broadcast({ type: "profile_updated", mind: mindName, summary: `${mindName} profile updated` });
194
199
  }
195
200
 
196
201
  // src/lib/pages-watcher.ts
@@ -231,16 +236,16 @@ function startPagesWatcher(mindName, pagesDir) {
231
236
  }
232
237
  function startWatcher(mindName) {
233
238
  if (watchers.has(mindName)) return;
234
- const pagesDir = resolve(mindDir(mindName), "home", "pages");
239
+ const pagesDir = resolve(mindDir(mindName), "home", "public", "pages");
235
240
  if (existsSync(pagesDir)) {
236
241
  startPagesWatcher(mindName, pagesDir);
237
242
  return;
238
243
  }
239
244
  if (homeWatchers.has(mindName)) return;
240
- const homeDir = resolve(mindDir(mindName), "home");
241
- if (!existsSync(homeDir)) return;
245
+ const publicDir = resolve(mindDir(mindName), "home", "public");
246
+ if (!existsSync(publicDir)) return;
242
247
  try {
243
- const hw = watch(homeDir, (_eventType, filename) => {
248
+ const hw = watch(publicDir, (_eventType, filename) => {
244
249
  if (filename !== "pages") return;
245
250
  if (!existsSync(pagesDir)) return;
246
251
  hw.close();
@@ -337,7 +342,7 @@ function buildSites() {
337
342
  }
338
343
  const entries = readRegistry();
339
344
  for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
340
- const pagesDir = resolve(mindDir(entry.name), "home", "pages");
345
+ const pagesDir = resolve(mindDir(entry.name), "home", "public", "pages");
341
346
  if (!existsSync(pagesDir)) continue;
342
347
  const mindPages = scanPagesDir(pagesDir, `/pages/${entry.name}`);
343
348
  if (mindPages.length > 0) {
@@ -350,7 +355,7 @@ function buildRecentPages() {
350
355
  const entries = readRegistry();
351
356
  const pages = [];
352
357
  for (const entry of entries) {
353
- const pagesDir = resolve(mindDir(entry.name), "home", "pages");
358
+ const pagesDir = resolve(mindDir(entry.name), "home", "public", "pages");
354
359
  if (!existsSync(pagesDir)) continue;
355
360
  let items;
356
361
  try {
@@ -774,7 +779,7 @@ function publish2(mind, event) {
774
779
  }
775
780
 
776
781
  // src/lib/delivery/delivery-manager.ts
777
- import { readFile } from "fs/promises";
782
+ import { readFile, realpath } from "fs/promises";
778
783
  import { extname, resolve as resolve5 } from "path";
779
784
  import { and as and3, eq as eq3, sql as sql2 } from "drizzle-orm";
780
785
 
@@ -1990,8 +1995,26 @@ var DeliveryManager = class {
1990
1995
  if (p.userType === "mind") {
1991
1996
  const dir = mindDir(p.username);
1992
1997
  const config = readVoluteConfig(dir);
1993
- if (!config?.avatar) continue;
1994
- filePath = resolve5(dir, "home", config.avatar);
1998
+ if (!config?.profile?.avatar) continue;
1999
+ filePath = resolve5(dir, "home", config.profile.avatar);
2000
+ const homeDir = resolve5(dir, "home");
2001
+ if (!filePath.startsWith(`${homeDir}/`)) {
2002
+ dlog2.warn(`avatar path for ${p.username} escapes home directory, skipping`);
2003
+ continue;
2004
+ }
2005
+ try {
2006
+ const realHome = await realpath(homeDir);
2007
+ const realAvatar = await realpath(filePath);
2008
+ if (!realAvatar.startsWith(`${realHome}/`)) {
2009
+ dlog2.warn(
2010
+ `avatar symlink for ${p.username} resolves outside home directory, skipping`
2011
+ );
2012
+ continue;
2013
+ }
2014
+ } catch (err) {
2015
+ if (err.code === "ENOENT") continue;
2016
+ throw err;
2017
+ }
1995
2018
  } else {
1996
2019
  filePath = resolve5(voluteHome(), "avatars", p.avatar);
1997
2020
  }
@@ -2702,11 +2725,7 @@ async function startMindFull(name) {
2702
2725
  );
2703
2726
  const config = readVoluteConfig(dir);
2704
2727
  if (config) {
2705
- syncMindProfile(baseName, {
2706
- displayName: config.displayName,
2707
- description: config.description,
2708
- avatar: config.avatar
2709
- }).catch(
2728
+ syncMindProfile(baseName, config.profile ?? {}).catch(
2710
2729
  (err) => logger_default.error(`failed to sync profile for ${baseName}`, logger_default.errorData(err))
2711
2730
  );
2712
2731
  }
@@ -3028,7 +3047,7 @@ var SleepManager = class {
3028
3047
  const db = await getDb();
3029
3048
  const rows = await db.select().from(deliveryQueue).where(and4(eq4(deliveryQueue.mind, name), eq4(deliveryQueue.status, "sleep-queued"))).all();
3030
3049
  if (rows.length === 0) return 0;
3031
- const { deliverMessage: deliverMessage2 } = await import("./message-delivery-S7BCNV6Y.js");
3050
+ const { deliverMessage: deliverMessage2 } = await import("./message-delivery-XMGV3FUM.js");
3032
3051
  const delivered = [];
3033
3052
  for (const row of rows) {
3034
3053
  try {
@@ -548,7 +548,7 @@ var MindManager = class {
548
548
  if (this.shuttingDown || this.stopping.has(name)) return;
549
549
  mlog.error(`mind ${name} exited with code ${code}`);
550
550
  try {
551
- const { getSleepManagerIfReady } = await import("./sleep-manager-XXSWQQLE.js");
551
+ const { getSleepManagerIfReady } = await import("./sleep-manager-RKTFZPD3.js");
552
552
  if (getSleepManagerIfReady()?.isSleeping(name)) {
553
553
  mlog.info(`${name} is sleeping \u2014 skipping crash recovery`);
554
554
  return;
@@ -14,7 +14,20 @@ function readJson(path) {
14
14
  }
15
15
  function readVoluteConfig(mindDir) {
16
16
  const path = resolve(mindDir, "home/.config/volute.json");
17
- return readJson(path);
17
+ const config = readJson(path);
18
+ if (!config) return null;
19
+ const legacy = config;
20
+ if (!config.profile && ("displayName" in config || "description" in config || "avatar" in config)) {
21
+ config.profile = {
22
+ displayName: legacy.displayName,
23
+ description: legacy.description,
24
+ avatar: legacy.avatar
25
+ };
26
+ delete legacy.displayName;
27
+ delete legacy.description;
28
+ delete legacy.avatar;
29
+ }
30
+ return config;
18
31
  }
19
32
  function writeVoluteConfig(mindDir, config) {
20
33
  const path = resolve(mindDir, "home/.config/volute.json");
package/dist/cli.js CHANGED
@@ -9,13 +9,13 @@ if (!process.env.VOLUTE_HOME) {
9
9
  var command = process.argv[2];
10
10
  var args = process.argv.slice(3);
11
11
  if (command === "--version" || command === "-v") {
12
- const { default: pkg } = await import("./package-S5YF25XV.js");
12
+ const { default: pkg } = await import("./package-3HF5MXU2.js");
13
13
  console.log(pkg.version);
14
14
  process.exit(0);
15
15
  }
16
16
  switch (command) {
17
17
  case "mind":
18
- await import("./mind-KPLCRKQA.js").then((m) => m.run(args));
18
+ await import("./mind-YVWAHL2A.js").then((m) => m.run(args));
19
19
  break;
20
20
  case "send":
21
21
  await import("./send-IISDYFCL.js").then((m) => m.run(args));
@@ -33,7 +33,7 @@ switch (command) {
33
33
  await import("./schedule-YEFDLVMJ.js").then((m) => m.run(args));
34
34
  break;
35
35
  case "skill":
36
- await import("./skill-BQOFACEI.js").then((m) => m.run(args));
36
+ await import("./skill-T3EMR6IR.js").then((m) => m.run(args));
37
37
  break;
38
38
  case "shared":
39
39
  await import("./shared-LWMNTTZN.js").then((m) => m.run(args));
@@ -45,13 +45,13 @@ switch (command) {
45
45
  await import("./env-2FPOZK37.js").then((m) => m.run(args));
46
46
  break;
47
47
  case "up":
48
- await import("./up-OMHACRJL.js").then((m) => m.run(args));
48
+ await import("./up-CJ26KQLN.js").then((m) => m.run(args));
49
49
  break;
50
50
  case "down":
51
51
  await import("./down-674SX2IZ.js").then((m) => m.run(args));
52
52
  break;
53
53
  case "restart":
54
- await import("./daemon-restart-YMPEATQH.js").then((m) => m.run(args));
54
+ await import("./daemon-restart-FDNOZEAD.js").then((m) => m.run(args));
55
55
  break;
56
56
  case "service":
57
57
  await import("./service-FASYWLTC.js").then((m) => m.run(args));
@@ -63,7 +63,7 @@ switch (command) {
63
63
  await import("./status-SIRPLEZC.js").then((m) => m.run(args));
64
64
  break;
65
65
  case "pages":
66
- await import("./pages-TWR6U7DS.js").then((m) => m.run(args));
66
+ await import("./pages-Y6DRWUOJ.js").then((m) => m.run(args));
67
67
  break;
68
68
  case "auth":
69
69
  await import("./auth-HM2RSPY7.js").then((m) => m.run(args));
@@ -3,12 +3,12 @@ import {
3
3
  deliverMessage,
4
4
  getAuthHeaders,
5
5
  getWebhookUrl
6
- } from "./chunk-OOW675I3.js";
6
+ } from "./chunk-PMX4EIJK.js";
7
7
  import "./chunk-HFCBO2GL.js";
8
8
  import "./chunk-E7GOKNOT.js";
9
9
  import "./chunk-BFK6SOEJ.js";
10
- import "./chunk-NOBRGACV.js";
11
- import "./chunk-XLC342FO.js";
10
+ import "./chunk-SHSWYG2J.js";
11
+ import "./chunk-SIAG3QMM.js";
12
12
  import "./chunk-PHU4DEAJ.js";
13
13
  import "./chunk-33XAVCS4.js";
14
14
  import {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  run
4
- } from "./chunk-TQDITGES.js";
4
+ } from "./chunk-ZSH4G2P5.js";
5
5
  import {
6
6
  stopDaemon
7
7
  } from "./chunk-2767L2RZ.js";