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
@@ -8,11 +8,11 @@ import {
8
8
  import {
9
9
  readSystemsConfig,
10
10
  writeSystemsConfig
11
- } from "./chunk-FCDU5BFX.js";
11
+ } from "./chunk-HFCBO2GL.js";
12
12
  import {
13
13
  parseArgs
14
14
  } from "./chunk-D424ZQGI.js";
15
- import "./chunk-EBGCNDMM.js";
15
+ import "./chunk-B2CPS4QU.js";
16
16
  import "./chunk-K3NQKI34.js";
17
17
 
18
18
  // src/commands/pages/register.ts
@@ -23,13 +23,13 @@ async function run(args) {
23
23
  });
24
24
  const existing = readSystemsConfig();
25
25
  if (existing) {
26
- console.error(`Already registered as "${existing.system}". Run "volute logout" first.`);
26
+ console.error(`Already registered as "${existing.system}". Run "volute auth logout" first.`);
27
27
  process.exit(1);
28
28
  }
29
29
  let name = flags.name;
30
30
  if (!name) {
31
31
  if (!process.stdin.isTTY) {
32
- console.error("Usage: volute pages register --name <system-name>");
32
+ console.error("Usage: volute auth register --name <system-name>");
33
33
  process.exit(1);
34
34
  }
35
35
  name = await promptLine("Choose a system name: ");
@@ -55,7 +55,7 @@ async function run(args) {
55
55
  } catch (err) {
56
56
  console.error(`Failed to save credentials: ${err.message}`);
57
57
  console.error(`Your API key is: ${apiKey}`);
58
- console.error(`Save it and run: volute pages login --key ${apiKey}`);
58
+ console.error(`Save it and run: volute auth login --key ${apiKey}`);
59
59
  process.exit(1);
60
60
  }
61
61
  console.log(`Registered as "${system}". Credentials saved.`);
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ addMind,
4
+ daemonLoopback,
5
+ ensureVoluteHome,
6
+ findMind,
7
+ getRegistryCache,
8
+ initRegistryCache,
9
+ mindDir,
10
+ nextPort,
11
+ readRegistry,
12
+ removeMind,
13
+ resolveMind,
14
+ setMindRunning,
15
+ setMindStage,
16
+ setMindTemplateHash,
17
+ stateDir,
18
+ validateMindName,
19
+ voluteHome,
20
+ writeRegistry
21
+ } from "./chunk-B2CPS4QU.js";
22
+ import "./chunk-K3NQKI34.js";
23
+ export {
24
+ addMind,
25
+ daemonLoopback,
26
+ ensureVoluteHome,
27
+ findMind,
28
+ getRegistryCache,
29
+ initRegistryCache,
30
+ mindDir,
31
+ nextPort,
32
+ readRegistry,
33
+ removeMind,
34
+ resolveMind,
35
+ setMindRunning,
36
+ setMindStage,
37
+ setMindTemplateHash,
38
+ stateDir,
39
+ validateMindName,
40
+ voluteHome,
41
+ writeRegistry
42
+ };
@@ -8,16 +8,13 @@ import {
8
8
  } from "./chunk-NAOW2CLO.js";
9
9
  import {
10
10
  daemonFetch
11
- } from "./chunk-WC6ZHVRL.js";
12
- import {
13
- resolveMind
14
- } from "./chunk-EBGCNDMM.js";
11
+ } from "./chunk-KFI7TQJ6.js";
12
+ import "./chunk-B2CPS4QU.js";
15
13
  import "./chunk-K3NQKI34.js";
16
14
 
17
15
  // src/commands/restart.ts
18
16
  async function run(args) {
19
17
  const name = resolveMindName({ mind: args[0] });
20
- const { entry } = resolveMind(name);
21
18
  const client = getClient();
22
19
  const res = await daemonFetch(
23
20
  urlOf(client.api.minds[":name"].restart.$url({ param: { name } })),
@@ -28,7 +25,7 @@ async function run(args) {
28
25
  console.error(data.error || "Failed to restart mind");
29
26
  process.exit(1);
30
27
  }
31
- console.log(`${name} restarted on port ${entry.port}`);
28
+ console.log(`${name} restarted on port ${data.port}`);
32
29
  }
33
30
  export {
34
31
  run
@@ -11,11 +11,12 @@ 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/schedule.ts
19
+ import { CronExpressionParser } from "cron-parser";
19
20
  async function run(args) {
20
21
  const subcommand = args[0];
21
22
  switch (subcommand) {
@@ -42,6 +43,7 @@ function printUsage() {
42
43
  console.log(`Usage:
43
44
  volute schedule list [--mind <name>]
44
45
  volute schedule add [--mind <name>] --cron "..." --message "..." [--id name]
46
+ volute schedule add [--mind <name>] --cron "..." --script "..." [--id name]
45
47
  volute schedule remove [--mind <name>] --id <id>`);
46
48
  }
47
49
  async function listSchedules(args) {
@@ -65,10 +67,11 @@ async function listSchedules(args) {
65
67
  }
66
68
  const idW = Math.max(2, ...schedules.map((s) => s.id.length));
67
69
  const cronW = Math.max(4, ...schedules.map((s) => s.cron.length));
68
- console.log(`${"ID".padEnd(idW)} ${"CRON".padEnd(cronW)} ENABLED MESSAGE`);
70
+ const actionLabel = (s) => s.script ? `[script] ${s.script}` : s.message ?? "";
71
+ console.log(`${"ID".padEnd(idW)} ${"CRON".padEnd(cronW)} ENABLED ACTION`);
69
72
  for (const s of schedules) {
70
73
  console.log(
71
- `${s.id.padEnd(idW)} ${s.cron.padEnd(cronW)} ${String(s.enabled).padEnd(7)} ${s.message}`
74
+ `${s.id.padEnd(idW)} ${s.cron.padEnd(cronW)} ${String(s.enabled).padEnd(7)} ${actionLabel(s)}`
72
75
  );
73
76
  }
74
77
  }
@@ -77,14 +80,31 @@ async function addSchedule(args) {
77
80
  mind: { type: "string" },
78
81
  cron: { type: "string" },
79
82
  message: { type: "string" },
83
+ script: { type: "string" },
80
84
  id: { type: "string" }
81
85
  });
82
86
  const mind = resolveMindName(flags);
83
- if (!flags.cron || !flags.message) {
84
- console.error("--cron and --message are required");
87
+ if (!flags.cron) {
88
+ console.error("--cron is required");
85
89
  process.exit(1);
86
90
  }
87
- const body = { cron: flags.cron, message: flags.message };
91
+ if (!flags.message && !flags.script) {
92
+ console.error("--message or --script is required");
93
+ process.exit(1);
94
+ }
95
+ if (flags.message && flags.script) {
96
+ console.error("--message and --script are mutually exclusive");
97
+ process.exit(1);
98
+ }
99
+ try {
100
+ CronExpressionParser.parse(flags.cron);
101
+ } catch {
102
+ console.error(`Invalid cron expression: ${flags.cron}`);
103
+ process.exit(1);
104
+ }
105
+ const body = { cron: flags.cron };
106
+ if (flags.message) body.message = flags.message;
107
+ if (flags.script) body.script = flags.script;
88
108
  if (flags.id) body.id = flags.id;
89
109
  const client = getClient();
90
110
  const res = await daemonFetch(
@@ -15,13 +15,13 @@ async function run(args) {
15
15
  const name = positional[0];
16
16
  if (!name) {
17
17
  console.error(
18
- "Usage: volute seed <name> [--template <name>] [--model <model>] [--description <text>] [--skills <list|none>]"
18
+ "Usage: volute mind seed <name> [--template <name>] [--model <model>] [--description <text>] [--skills <list|none>]"
19
19
  );
20
20
  process.exit(1);
21
21
  }
22
22
  const template = flags.template ?? "claude";
23
23
  const skills = flags.skills === "none" ? [] : flags.skills ? flags.skills.split(",") : void 0;
24
- const { daemonFetch } = await import("./daemon-client-ITWUCNFO.js");
24
+ const { daemonFetch } = await import("./daemon-client-ZHCDL4RS.js");
25
25
  const { getClient, urlOf } = await import("./api-client-YPKOZP2O.js");
26
26
  const client = getClient();
27
27
  const createRes = await daemonFetch(urlOf(client.api.minds.$url()), {
@@ -6,19 +6,15 @@ import {
6
6
  import {
7
7
  resolveMindName
8
8
  } from "./chunk-NAOW2CLO.js";
9
- import {
10
- getChannelDriver
11
- } from "./chunk-UJ6GHNR7.js";
12
9
  import {
13
10
  parseArgs
14
11
  } from "./chunk-D424ZQGI.js";
15
- import "./chunk-RHEGSQFJ.js";
16
12
  import {
17
13
  daemonFetch
18
- } from "./chunk-WC6ZHVRL.js";
14
+ } from "./chunk-KFI7TQJ6.js";
19
15
  import {
20
16
  findMind
21
- } from "./chunk-EBGCNDMM.js";
17
+ } from "./chunk-B2CPS4QU.js";
22
18
  import "./chunk-K3NQKI34.js";
23
19
 
24
20
  // src/commands/send.ts
@@ -94,16 +90,88 @@ function loadImage(imagePath) {
94
90
  const data = readFileSync(imagePath).toString("base64");
95
91
  return { media_type: mediaType, data };
96
92
  }
93
+ async function waitForResponse(mindName, conversationId, timeoutMs) {
94
+ const client = getClient();
95
+ const eventPath = urlOf(
96
+ client.api.minds[":name"].conversations[":id"].events.$url({
97
+ param: { name: mindName, id: conversationId }
98
+ })
99
+ );
100
+ const controller = new AbortController();
101
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
102
+ let response;
103
+ try {
104
+ response = await daemonFetch(eventPath, {
105
+ signal: controller.signal
106
+ });
107
+ } catch {
108
+ clearTimeout(timeout);
109
+ console.error("Could not connect to event stream. Is the mind running?");
110
+ process.exit(1);
111
+ }
112
+ if (!response.body) {
113
+ clearTimeout(timeout);
114
+ return;
115
+ }
116
+ const reader = response.body.getReader();
117
+ const decoder = new TextDecoder();
118
+ let buffer = "";
119
+ try {
120
+ while (true) {
121
+ const { done, value } = await reader.read();
122
+ if (done) break;
123
+ buffer += decoder.decode(value, { stream: true });
124
+ const chunks = buffer.split("\n\n");
125
+ buffer = chunks.pop();
126
+ for (const chunk of chunks) {
127
+ for (const line of chunk.split("\n")) {
128
+ if (!line.startsWith("data: ")) continue;
129
+ const data = line.slice(6).trim();
130
+ if (!data) continue;
131
+ let event;
132
+ try {
133
+ event = JSON.parse(data);
134
+ } catch {
135
+ continue;
136
+ }
137
+ if (event.type === "message" && event.senderName === mindName && event.content) {
138
+ const text = event.content.filter((b) => b.type === "text" && !!b.text).map((b) => b.text).join("");
139
+ if (text) {
140
+ process.stdout.write(`${text}
141
+ `);
142
+ }
143
+ return;
144
+ }
145
+ }
146
+ }
147
+ }
148
+ } catch (err) {
149
+ if (err.name === "AbortError") {
150
+ console.error(`(timed out after ${timeoutMs / 1e3}s)`);
151
+ } else {
152
+ throw err;
153
+ }
154
+ } finally {
155
+ clearTimeout(timeout);
156
+ reader.cancel().catch(() => {
157
+ });
158
+ }
159
+ }
97
160
  async function run(args) {
98
161
  const { positional, flags } = parseArgs(args, {
99
162
  mind: { type: "string" },
100
- image: { type: "string" }
163
+ image: { type: "string" },
164
+ wait: { type: "boolean" },
165
+ timeout: { type: "number" },
166
+ sender: { type: "string" }
101
167
  });
102
168
  const target = positional[0];
103
169
  const message = positional[1] ?? await readStdin();
104
170
  const images = flags.image ? [loadImage(flags.image)] : void 0;
105
171
  if (!target || !message && !images) {
106
- console.error('Usage: volute send <target> "<message>" [--mind <name>] [--image <path>]');
172
+ console.error(
173
+ 'Usage: volute send <target> "<message>" [--mind <name>] [--image <path>] [--wait]'
174
+ );
107
175
  console.error(' echo "message" | volute send <target> [--mind <name>]');
108
176
  console.error("");
109
177
  console.error("Examples:");
@@ -112,6 +180,7 @@ async function run(args) {
112
180
  console.error(' volute send discord:server/channel "hello"');
113
181
  console.error(' volute send @mind "check this out" --image photo.png');
114
182
  console.error(" volute send @mind --image photo.png");
183
+ console.error(' volute send @mind "hello" --wait');
115
184
  process.exit(1);
116
185
  }
117
186
  if (target === "system" || target === "@system") {
@@ -130,44 +199,55 @@ To reply to a person, use their username from the message prefix (e.g. volute se
130
199
  isDM: true
131
200
  };
132
201
  }
133
- const driver = getChannelDriver(parsed.platform);
134
- if (!driver) {
135
- console.error(`No driver for platform: ${parsed.platform}`);
136
- process.exit(1);
137
- }
202
+ const client = getClient();
203
+ let waitMindName;
138
204
  let channelUri = parsed.uri;
139
205
  if (parsed.isDM && parsed.platform === "volute") {
140
206
  const targetName = parsed.identifier.slice(1);
141
207
  const mindSelf = process.env.VOLUTE_MIND;
142
- const sender = mindSelf || userInfo().username;
143
- if (!driver.createConversation) {
144
- console.error("Volute driver does not support creating conversations");
145
- process.exit(1);
146
- }
208
+ const sender = flags.sender || mindSelf || userInfo().username;
209
+ waitMindName = findMind(targetName) ? targetName : void 0;
147
210
  const targetIsMind = !!findMind(targetName);
148
211
  const contextMind = mindSelf && !targetIsMind ? mindSelf : targetName;
149
212
  const participants = mindSelf && !targetIsMind ? [targetName] : [sender];
150
- const env = {
151
- VOLUTE_MIND: contextMind,
152
- VOLUTE_SENDER: sender
153
- };
154
- try {
155
- channelUri = await driver.createConversation(env, participants);
156
- } catch (err) {
157
- console.error(err instanceof Error ? err.message : String(err));
213
+ const createRes = await daemonFetch(
214
+ urlOf(client.api.minds[":name"].channels.create.$url({ param: { name: contextMind } })),
215
+ {
216
+ method: "POST",
217
+ headers: { "Content-Type": "application/json" },
218
+ body: JSON.stringify({ platform: "volute", participants, sender })
219
+ }
220
+ );
221
+ if (!createRes.ok) {
222
+ const data = await createRes.json().catch(() => ({ error: "Unknown error" }));
223
+ console.error(data.error);
158
224
  process.exit(1);
159
225
  }
160
- try {
161
- await driver.send(env, channelUri, message ?? "", images);
162
- console.log("Message sent.");
163
- } catch (err) {
164
- console.error(err instanceof Error ? err.message : String(err));
226
+ const { slug } = await createRes.json();
227
+ channelUri = slug;
228
+ const sendRes = await daemonFetch(
229
+ urlOf(client.api.minds[":name"].channels.send.$url({ param: { name: contextMind } })),
230
+ {
231
+ method: "POST",
232
+ headers: { "Content-Type": "application/json" },
233
+ body: JSON.stringify({
234
+ platform: "volute",
235
+ uri: channelUri,
236
+ message: message ?? "",
237
+ images,
238
+ sender
239
+ })
240
+ }
241
+ );
242
+ if (!sendRes.ok) {
243
+ const data = await sendRes.json().catch(() => ({ error: "Unknown error" }));
244
+ console.error(data.error);
165
245
  process.exit(1);
166
246
  }
247
+ if (!flags.wait) console.log("Message sent.");
167
248
  if (mindSelf) {
168
249
  try {
169
- const client = getClient();
170
- await daemonFetch(
250
+ const histRes = await daemonFetch(
171
251
  urlOf(client.api.minds[":name"].history.$url({ param: { name: mindSelf } })),
172
252
  {
173
253
  method: "POST",
@@ -175,13 +255,15 @@ To reply to a person, use their username from the message prefix (e.g. volute se
175
255
  body: JSON.stringify({ channel: parsed.uri, content: message ?? "" })
176
256
  }
177
257
  );
258
+ if (!histRes.ok) {
259
+ console.error(`Failed to persist to history: HTTP ${histRes.status}`);
260
+ }
178
261
  } catch (err) {
179
262
  console.error(`Failed to persist to history: ${err instanceof Error ? err.message : err}`);
180
263
  }
181
264
  }
182
265
  } else {
183
266
  const mindName = resolveMindName(flags);
184
- const client = getClient();
185
267
  const res = await daemonFetch(
186
268
  urlOf(client.api.minds[":name"].channels.send.$url({ param: { name: mindName } })),
187
269
  {
@@ -200,10 +282,10 @@ To reply to a person, use their username from the message prefix (e.g. volute se
200
282
  console.error(body.error);
201
283
  process.exit(1);
202
284
  }
203
- console.log("Message sent.");
285
+ if (!flags.wait) console.log("Message sent.");
204
286
  if (process.env.VOLUTE_MIND) {
205
287
  try {
206
- await daemonFetch(
288
+ const histRes = await daemonFetch(
207
289
  urlOf(client.api.minds[":name"].history.$url({ param: { name: mindName } })),
208
290
  {
209
291
  method: "POST",
@@ -211,11 +293,25 @@ To reply to a person, use their username from the message prefix (e.g. volute se
211
293
  body: JSON.stringify({ channel: channelUri, content: message ?? "" })
212
294
  }
213
295
  );
296
+ if (!histRes.ok) {
297
+ console.error(`Failed to persist to history: HTTP ${histRes.status}`);
298
+ }
214
299
  } catch (err) {
215
300
  console.error(`Failed to persist to history: ${err instanceof Error ? err.message : err}`);
216
301
  }
217
302
  }
218
303
  }
304
+ if (flags.wait && waitMindName) {
305
+ const conversationId = channelUri.startsWith("volute:") ? channelUri.slice(7) : void 0;
306
+ if (!conversationId) {
307
+ console.error("--wait requires a volute conversation (DM to a mind)");
308
+ process.exit(1);
309
+ }
310
+ await waitForResponse(waitMindName, conversationId, flags.timeout ?? 12e4);
311
+ } else if (flags.wait && !waitMindName) {
312
+ console.error("--wait is only supported when sending to a mind");
313
+ process.exit(1);
314
+ }
219
315
  }
220
316
  export {
221
317
  run
@@ -4,15 +4,15 @@ import {
4
4
  LAUNCHD_PLIST_PATH,
5
5
  SYSTEM_SERVICE_PATH,
6
6
  USER_SYSTEMD_UNIT
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
  resolveVoluteBin
13
- } from "./chunk-DYZGP3EW.js";
14
- import "./chunk-OGXOMR65.js";
15
- import "./chunk-EBGCNDMM.js";
13
+ } from "./chunk-IPJXU366.js";
14
+ import "./chunk-NWPT4ASZ.js";
15
+ import "./chunk-B2CPS4QU.js";
16
16
  import "./chunk-K3NQKI34.js";
17
17
 
18
18
  // src/commands/service.ts
@@ -87,16 +87,20 @@ async function install(port, host) {
87
87
  console.log("Service installed and loaded. Volute daemon will start on login.");
88
88
  } else if (platform === "linux") {
89
89
  if (existsSync(SYSTEM_SERVICE_PATH)) {
90
- console.error("A system-level Volute service is already installed (via `volute setup`).");
90
+ console.error(
91
+ "A system-level Volute service is already installed (via `volute service install --system`)."
92
+ );
91
93
  console.error("Use `systemctl status volute` to check its status.");
92
- console.error("To remove it first: sudo volute setup uninstall");
94
+ console.error("To remove it first: sudo volute service uninstall --system");
93
95
  process.exit(1);
94
96
  }
95
97
  if (process.getuid?.() === 0) {
96
98
  console.error(
97
99
  "Error: `volute service install` uses systemd user services, which don't work as root."
98
100
  );
99
- console.error("Use `volute setup` instead to install a system-level service.");
101
+ console.error(
102
+ "Use `volute service install --system` instead to install a system-level service."
103
+ );
100
104
  process.exit(1);
101
105
  }
102
106
  const path = USER_SYSTEMD_UNIT;
@@ -199,24 +203,38 @@ async function status() {
199
203
  async function run(args) {
200
204
  const { positional, flags } = parseArgs(args, {
201
205
  port: { type: "number" },
202
- host: { type: "string" }
206
+ host: { type: "string" },
207
+ system: { type: "boolean" },
208
+ force: { type: "boolean" }
203
209
  });
204
210
  const subcommand = positional[0];
205
211
  switch (subcommand) {
206
212
  case "install":
207
- await install(flags.port, flags.host);
213
+ if (flags.system) {
214
+ const setup = await import("./setup-OZDYCKDI.js");
215
+ setup.install(flags.port, flags.host);
216
+ } else {
217
+ await install(flags.port, flags.host);
218
+ }
208
219
  break;
209
220
  case "uninstall":
210
- await uninstall();
221
+ if (flags.system) {
222
+ const setup = await import("./setup-OZDYCKDI.js");
223
+ setup.uninstall(!!flags.force);
224
+ } else {
225
+ await uninstall();
226
+ }
211
227
  break;
212
228
  case "status":
213
229
  await status();
214
230
  break;
215
231
  default:
216
232
  console.log(`Usage:
217
- volute service install [--port N] [--host H] Install as system service
218
- volute service uninstall Remove system service
219
- volute service status Check service status`);
233
+ volute service install [--port N] [--host H] Install as user-level service
234
+ volute service install --system [--port N] [--host H] Install system service with user isolation
235
+ volute service uninstall Remove user-level service
236
+ volute service uninstall --system [--force] Remove system service (--force removes data + users)
237
+ volute service status Check service status`);
220
238
  if (subcommand) {
221
239
  console.error(`
222
240
  Unknown subcommand: ${subcommand}`);
@@ -1,17 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  SYSTEM_SERVICE_PATH
4
- } from "./chunk-32VR2EOH.js";
5
- import {
6
- parseArgs
7
- } from "./chunk-D424ZQGI.js";
4
+ } from "./chunk-QUJUKM4U.js";
8
5
  import {
9
6
  resolveVoluteBin
10
- } from "./chunk-DYZGP3EW.js";
7
+ } from "./chunk-IPJXU366.js";
11
8
  import {
12
9
  ensureVoluteGroup
13
- } from "./chunk-OGXOMR65.js";
14
- import "./chunk-EBGCNDMM.js";
10
+ } from "./chunk-NWPT4ASZ.js";
11
+ import "./chunk-B2CPS4QU.js";
15
12
  import "./chunk-K3NQKI34.js";
16
13
 
17
14
  // src/commands/setup.ts
@@ -80,11 +77,11 @@ function generateUnit(voluteBin, port, host) {
80
77
  function install(port, host) {
81
78
  if (host) validateHost(host);
82
79
  if (process.getuid?.() !== 0) {
83
- console.error("Error: volute setup must be run as root (use sudo).");
80
+ console.error("Error: volute service install --system must be run as root (use sudo).");
84
81
  process.exit(1);
85
82
  }
86
83
  if (process.platform !== "linux") {
87
- console.error("Error: volute setup is only supported on Linux.");
84
+ console.error("Error: volute service install --system is only supported on Linux.");
88
85
  console.error("On macOS, use `volute service install` for user-level service management.");
89
86
  process.exit(1);
90
87
  }
@@ -95,6 +92,22 @@ function install(port, host) {
95
92
  console.log(`Created ${MINDS_DIR}`);
96
93
  ensureVoluteGroup({ force: true });
97
94
  console.log("Ensured volute group exists");
95
+ try {
96
+ execFileSync("git", ["config", "--system", "user.name"]);
97
+ console.log("System git identity already configured, skipping");
98
+ } catch {
99
+ try {
100
+ execFileSync("git", ["config", "--system", "user.name", "Volute"]);
101
+ execFileSync("git", ["config", "--system", "user.email", "volute@localhost"]);
102
+ console.log("Configured system git identity");
103
+ } catch (err) {
104
+ const msg = err instanceof Error ? err.message : String(err);
105
+ console.warn(`Warning: failed to set system git config: ${msg}`);
106
+ console.warn("Git commits by the daemon may fail. You can set this manually with:");
107
+ console.warn(' git config --system user.name "Volute"');
108
+ console.warn(' git config --system user.email "volute@localhost"');
109
+ }
110
+ }
98
111
  execFileSync("chmod", ["755", DATA_DIR]);
99
112
  execFileSync("chmod", ["755", MINDS_DIR]);
100
113
  console.log("Set permissions on directories");
@@ -148,7 +161,7 @@ Volute daemon is running. Data directory: ${DATA_DIR}`);
148
161
  }
149
162
  function uninstall(force) {
150
163
  if (process.getuid?.() !== 0) {
151
- console.error("Error: volute setup uninstall must be run as root (use sudo).");
164
+ console.error("Error: volute service uninstall --system must be run as root (use sudo).");
152
165
  process.exit(1);
153
166
  }
154
167
  if (!existsSync(SYSTEM_SERVICE_PATH)) {
@@ -211,29 +224,7 @@ function uninstall(force) {
211
224
  console.log("Use --force to also remove data and system users.");
212
225
  }
213
226
  }
214
- async function run(args) {
215
- const { positional, flags } = parseArgs(args, {
216
- port: { type: "number" },
217
- host: { type: "string" },
218
- force: { type: "boolean" }
219
- });
220
- const subcommand = positional[0];
221
- switch (subcommand) {
222
- case "uninstall":
223
- uninstall(!!flags.force);
224
- break;
225
- case void 0:
226
- install(flags.port, flags.host);
227
- break;
228
- default:
229
- console.log(`Usage:
230
- volute setup [--port N] [--host H] Install system-level service with user isolation
231
- volute setup uninstall [--force] Remove service (--force removes data + users)`);
232
- console.error(`
233
- Unknown subcommand: ${subcommand}`);
234
- process.exit(1);
235
- }
236
- }
237
227
  export {
238
- run
228
+ install,
229
+ uninstall
239
230
  };
@@ -6,16 +6,16 @@ async function run(args) {
6
6
  const subcommand = args[0];
7
7
  switch (subcommand) {
8
8
  case "merge":
9
- await import("./merge-33C237A4.js").then((m) => m.run(args.slice(1)));
9
+ await import("./merge-B6SYTGI7.js").then((m) => m.run(args.slice(1)));
10
10
  break;
11
11
  case "pull":
12
- await import("./pull-XAEWQJ47.js").then((m) => m.run(args.slice(1)));
12
+ await import("./pull-GRQAXM2E.js").then((m) => m.run(args.slice(1)));
13
13
  break;
14
14
  case "log":
15
- await import("./log-PPPZDVEF.js").then((m) => m.run(args.slice(1)));
15
+ await import("./log-SRO5Q6AD.js").then((m) => m.run(args.slice(1)));
16
16
  break;
17
17
  case "status":
18
- await import("./status-D7E5HHBV.js").then((m) => m.run(args.slice(1)));
18
+ await import("./status-5XDGYHKP.js").then((m) => m.run(args.slice(1)));
19
19
  break;
20
20
  case "--help":
21
21
  case "-h":
@@ -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/skill.ts