volute 0.4.0 → 0.5.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 (75) hide show
  1. package/README.md +22 -22
  2. package/dist/agent-Z2B6EFEQ.js +75 -0
  3. package/dist/{agent-manager-AUCKMGPR.js → agent-manager-PXBKA2GK.js} +4 -4
  4. package/dist/channel-MK5OK2SI.js +113 -0
  5. package/dist/chunk-5X7HGB6L.js +107 -0
  6. package/dist/{chunk-YGFIWIOF.js → chunk-7L4AN5D4.js} +1 -1
  7. package/dist/{chunk-VRVVQIYY.js → chunk-AZEL2IEK.js} +1 -1
  8. package/dist/chunk-B3R6L2GW.js +24 -0
  9. package/dist/{chunk-DNOXHLE5.js → chunk-HE67X4T6.js} +1 -1
  10. package/dist/{chunk-I6OHXCMV.js → chunk-MW2KFO3B.js} +47 -9
  11. package/dist/{chunk-5OCWMTVS.js → chunk-SMISE4SV.js} +77 -3
  12. package/dist/{chunk-SOZA2TLP.js → chunk-UAVD2AHX.js} +1 -1
  13. package/dist/{chunk-3C2XR4IY.js → chunk-UX25Z2ND.js} +113 -107
  14. package/dist/{chunk-GSPKUPKU.js → chunk-XUA3JUFK.js} +2 -1
  15. package/dist/chunk-ZYGKG6VC.js +22 -0
  16. package/dist/cli.js +86 -74
  17. package/dist/{connector-DKDJTLYZ.js → connector-LYEMXQEV.js} +11 -6
  18. package/dist/connectors/discord.js +3 -1
  19. package/dist/connectors/slack.js +14 -5
  20. package/dist/connectors/telegram.js +21 -2
  21. package/dist/conversation-ERXEQZTY.js +163 -0
  22. package/dist/create-RVCZN6HE.js +91 -0
  23. package/dist/{daemon-client-XR24PUJF.js → daemon-client-ZY6UUN2M.js} +2 -2
  24. package/dist/daemon.js +629 -177
  25. package/dist/{delete-55MXCEY5.js → delete-3QH7VYIN.js} +7 -8
  26. package/dist/{down-3OB6UVAJ.js → down-O7IFZLVJ.js} +1 -1
  27. package/dist/{env-JB27UAC3.js → env-4D4REPJF.js} +8 -5
  28. package/dist/{history-BKG74I43.js → history-OEONB53Z.js} +3 -3
  29. package/dist/{import-4CI2ZUTJ.js → import-MXJB2EII.js} +8 -8
  30. package/dist/{logs-NXFFGUKY.js → logs-DF342W4M.js} +2 -2
  31. package/dist/message-ADHWFHSI.js +32 -0
  32. package/dist/{package-Z2SFO2SV.js → package-VQOE7JNH.js} +1 -1
  33. package/dist/{schedule-A35SH4HT.js → schedule-NAG6F463.js} +10 -5
  34. package/dist/send-66QMKRUH.js +75 -0
  35. package/dist/{setup-2FDVN7OF.js → setup-RPRRGG2F.js} +5 -5
  36. package/dist/{start-LDPMCMYT.js → start-TUOXDSFL.js} +3 -3
  37. package/dist/{status-MVSQG54T.js → status-A36EHRO4.js} +3 -3
  38. package/dist/{stop-5PZTZCLL.js → stop-AOJZLQ5X.js} +6 -7
  39. package/dist/{up-F7TMTLRE.js → up-7ILD7GU7.js} +2 -2
  40. package/dist/update-LPSIAWQ2.js +140 -0
  41. package/dist/update-check-Y33QDCFL.js +17 -0
  42. package/dist/{upgrade-6ZW2RD64.js → upgrade-FX2TKJ2S.js} +16 -15
  43. package/dist/{variant-T64BKARF.js → variant-LAB67OC2.js} +15 -10
  44. package/dist/web-assets/assets/index-BbRmoxoA.js +308 -0
  45. package/dist/web-assets/index.html +2 -2
  46. package/drizzle/0003_clean_ego.sql +12 -0
  47. package/drizzle/meta/0003_snapshot.json +417 -0
  48. package/drizzle/meta/_journal.json +7 -0
  49. package/package.json +1 -1
  50. package/templates/_base/.init/.config/hooks/startup-context.sh +19 -1
  51. package/templates/_base/_skills/volute-agent/SKILL.md +110 -14
  52. package/templates/_base/home/.config/routes.json +10 -0
  53. package/templates/_base/home/VOLUTE.md +14 -35
  54. package/templates/_base/src/lib/format-prefix.ts +1 -1
  55. package/templates/_base/src/lib/router.ts +163 -16
  56. package/templates/_base/src/lib/routing.ts +55 -18
  57. package/templates/_base/src/lib/types.ts +3 -1
  58. package/templates/agent-sdk/.init/.config/routes.json +5 -0
  59. package/templates/agent-sdk/.init/CLAUDE.md +2 -2
  60. package/templates/agent-sdk/src/agent.ts +2 -1
  61. package/templates/agent-sdk/src/server.ts +8 -2
  62. package/templates/agent-sdk/volute-template.json +1 -1
  63. package/templates/pi/.init/.config/routes.json +5 -0
  64. package/templates/pi/.init/AGENTS.md +1 -1
  65. package/templates/pi/src/agent.ts +5 -3
  66. package/templates/pi/src/server.ts +1 -1
  67. package/templates/pi/volute-template.json +1 -1
  68. package/dist/channel-DQ6UY7QB.js +0 -67
  69. package/dist/chunk-ZHCE4DPY.js +0 -110
  70. package/dist/create-ILVOG75A.js +0 -79
  71. package/dist/send-3U6OTKG7.js +0 -57
  72. package/dist/web-assets/assets/index-NS621maO.js +0 -296
  73. package/templates/agent-sdk/.init/.config/sessions.json +0 -4
  74. package/templates/pi/.init/.config/sessions.json +0 -1
  75. package/dist/{service-SA4TTMDU.js → service-HZNIDNJF.js} +3 -3
@@ -1,27 +1,131 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/lib/variants.ts
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
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
+ // src/lib/registry.ts
8
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
5
9
  import { homedir } from "os";
6
- import { resolve } from "path";
10
+ import { dirname, resolve } from "path";
11
+ import { fileURLToPath } from "url";
7
12
  function voluteHome() {
8
- return process.env.VOLUTE_HOME || resolve(homedir(), ".volute");
13
+ if (process.env.VOLUTE_HOME) return process.env.VOLUTE_HOME;
14
+ const dir = dirname(fileURLToPath(import.meta.url));
15
+ if (dir.endsWith("/src/lib")) {
16
+ throw new Error(
17
+ 'VOLUTE_HOME must be set when running from source. For tests, run via "npm test" or add "--import ./test/setup.ts".'
18
+ );
19
+ }
20
+ return resolve(homedir(), ".volute");
21
+ }
22
+ function ensureVoluteHome() {
23
+ mkdirSync(resolve(voluteHome(), "agents"), { recursive: true });
24
+ }
25
+ function readRegistry() {
26
+ const registryPath = resolve(voluteHome(), "agents.json");
27
+ if (!existsSync(registryPath)) return [];
28
+ try {
29
+ const entries = JSON.parse(readFileSync(registryPath, "utf-8"));
30
+ return entries.map((e) => ({ ...e, running: e.running ?? false }));
31
+ } catch {
32
+ return [];
33
+ }
34
+ }
35
+ function writeRegistry(entries) {
36
+ ensureVoluteHome();
37
+ const registryPath = resolve(voluteHome(), "agents.json");
38
+ const tmpPath = `${registryPath}.tmp`;
39
+ writeFileSync(tmpPath, `${JSON.stringify(entries, null, 2)}
40
+ `);
41
+ renameSync(tmpPath, registryPath);
42
+ }
43
+ var AGENT_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
44
+ var AGENT_NAME_MAX = 64;
45
+ function validateAgentName(name) {
46
+ if (!name) return "Agent name is required";
47
+ if (name.length > AGENT_NAME_MAX)
48
+ return `Agent name must be at most ${AGENT_NAME_MAX} characters`;
49
+ if (!AGENT_NAME_RE.test(name)) {
50
+ return "Agent name must start with alphanumeric and contain only alphanumeric, dots, dashes, or underscores";
51
+ }
52
+ return null;
53
+ }
54
+ function addAgent(name, port) {
55
+ const err = validateAgentName(name);
56
+ if (err) throw new Error(err);
57
+ const entries = readRegistry();
58
+ const filtered = entries.filter((e) => e.name !== name);
59
+ filtered.push({ name, port, created: (/* @__PURE__ */ new Date()).toISOString(), running: false });
60
+ writeRegistry(filtered);
9
61
  }
62
+ function removeAgent(name) {
63
+ const entries = readRegistry();
64
+ writeRegistry(entries.filter((e) => e.name !== name));
65
+ }
66
+ function setAgentRunning(name, running) {
67
+ const entries = readRegistry();
68
+ const entry = entries.find((e) => e.name === name);
69
+ if (entry) {
70
+ entry.running = running;
71
+ writeRegistry(entries);
72
+ }
73
+ }
74
+ function findAgent(name) {
75
+ return readRegistry().find((e) => e.name === name);
76
+ }
77
+ function agentDir(name) {
78
+ return resolve(voluteHome(), "agents", name);
79
+ }
80
+ function nextPort() {
81
+ const entries = readRegistry();
82
+ const usedPorts = new Set(entries.map((e) => e.port));
83
+ for (const entry of entries) {
84
+ for (const v of readVariants(entry.name)) {
85
+ if (v.port) usedPorts.add(v.port);
86
+ }
87
+ }
88
+ let port = 4100;
89
+ while (usedPorts.has(port)) port++;
90
+ if (port > 65535) throw new Error("No available ports \u2014 all ports 4100-65535 are allocated");
91
+ return port;
92
+ }
93
+ function resolveAgent(name) {
94
+ const [baseName, variantName] = name.split("@", 2);
95
+ const entry = findAgent(baseName);
96
+ if (!entry) {
97
+ throw new Error(`Unknown agent: ${baseName}`);
98
+ }
99
+ const dir = agentDir(baseName);
100
+ if (!existsSync(dir)) {
101
+ throw new Error(`Agent directory missing: ${dir}`);
102
+ }
103
+ if (variantName) {
104
+ const variant = findVariant(baseName, variantName);
105
+ if (!variant) {
106
+ throw new Error(`Unknown variant: ${variantName} (agent: ${baseName})`);
107
+ }
108
+ return { entry: { ...entry, port: variant.port }, dir: variant.path };
109
+ }
110
+ return { entry, dir };
111
+ }
112
+
113
+ // src/lib/variants.ts
10
114
  function variantsPath() {
11
- return resolve(voluteHome(), "variants.json");
115
+ return resolve2(voluteHome(), "variants.json");
12
116
  }
13
117
  function readAllVariants() {
14
118
  const path = variantsPath();
15
- if (!existsSync(path)) return {};
119
+ if (!existsSync2(path)) return {};
16
120
  try {
17
- return JSON.parse(readFileSync(path, "utf-8"));
121
+ return JSON.parse(readFileSync2(path, "utf-8"));
18
122
  } catch {
19
123
  return {};
20
124
  }
21
125
  }
22
126
  function writeAllVariants(all) {
23
- mkdirSync(voluteHome(), { recursive: true });
24
- writeFileSync(variantsPath(), `${JSON.stringify(all, null, 2)}
127
+ mkdirSync2(voluteHome(), { recursive: true });
128
+ writeFileSync2(variantsPath(), `${JSON.stringify(all, null, 2)}
25
129
  `);
26
130
  }
27
131
  function readVariants(agentName) {
@@ -102,104 +206,6 @@ function validateBranchName(branch) {
102
206
  return null;
103
207
  }
104
208
 
105
- // src/lib/registry.ts
106
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
107
- import { homedir as homedir2 } from "os";
108
- import { resolve as resolve2 } from "path";
109
- function voluteHome2() {
110
- return process.env.VOLUTE_HOME || resolve2(homedir2(), ".volute");
111
- }
112
- function ensureVoluteHome() {
113
- mkdirSync2(resolve2(voluteHome2(), "agents"), { recursive: true });
114
- }
115
- function readRegistry() {
116
- const registryPath = resolve2(voluteHome2(), "agents.json");
117
- if (!existsSync2(registryPath)) return [];
118
- try {
119
- const entries = JSON.parse(readFileSync2(registryPath, "utf-8"));
120
- return entries.map((e) => ({ ...e, running: e.running ?? false }));
121
- } catch {
122
- return [];
123
- }
124
- }
125
- function writeRegistry(entries) {
126
- ensureVoluteHome();
127
- const registryPath = resolve2(voluteHome2(), "agents.json");
128
- const tmpPath = `${registryPath}.tmp`;
129
- writeFileSync2(tmpPath, `${JSON.stringify(entries, null, 2)}
130
- `);
131
- renameSync(tmpPath, registryPath);
132
- }
133
- var AGENT_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
134
- var AGENT_NAME_MAX = 64;
135
- function validateAgentName(name) {
136
- if (!name) return "Agent name is required";
137
- if (name.length > AGENT_NAME_MAX)
138
- return `Agent name must be at most ${AGENT_NAME_MAX} characters`;
139
- if (!AGENT_NAME_RE.test(name)) {
140
- return "Agent name must start with alphanumeric and contain only alphanumeric, dots, dashes, or underscores";
141
- }
142
- return null;
143
- }
144
- function addAgent(name, port) {
145
- const err = validateAgentName(name);
146
- if (err) throw new Error(err);
147
- const entries = readRegistry();
148
- const filtered = entries.filter((e) => e.name !== name);
149
- filtered.push({ name, port, created: (/* @__PURE__ */ new Date()).toISOString(), running: false });
150
- writeRegistry(filtered);
151
- }
152
- function removeAgent(name) {
153
- const entries = readRegistry();
154
- writeRegistry(entries.filter((e) => e.name !== name));
155
- }
156
- function setAgentRunning(name, running) {
157
- const entries = readRegistry();
158
- const entry = entries.find((e) => e.name === name);
159
- if (entry) {
160
- entry.running = running;
161
- writeRegistry(entries);
162
- }
163
- }
164
- function findAgent(name) {
165
- return readRegistry().find((e) => e.name === name);
166
- }
167
- function agentDir(name) {
168
- return resolve2(voluteHome2(), "agents", name);
169
- }
170
- function nextPort() {
171
- const entries = readRegistry();
172
- const usedPorts = new Set(entries.map((e) => e.port));
173
- for (const entry of entries) {
174
- for (const v of readVariants(entry.name)) {
175
- if (v.port) usedPorts.add(v.port);
176
- }
177
- }
178
- let port = 4100;
179
- while (usedPorts.has(port)) port++;
180
- if (port > 65535) throw new Error("No available ports \u2014 all ports 4100-65535 are allocated");
181
- return port;
182
- }
183
- function resolveAgent(name) {
184
- const [baseName, variantName] = name.split("@", 2);
185
- const entry = findAgent(baseName);
186
- if (!entry) {
187
- throw new Error(`Unknown agent: ${baseName}`);
188
- }
189
- const dir = agentDir(baseName);
190
- if (!existsSync2(dir)) {
191
- throw new Error(`Agent directory missing: ${dir}`);
192
- }
193
- if (variantName) {
194
- const variant = findVariant(baseName, variantName);
195
- if (!variant) {
196
- throw new Error(`Unknown variant: ${variantName} (agent: ${baseName})`);
197
- }
198
- return { entry: { ...entry, port: variant.port }, dir: variant.path };
199
- }
200
- return { entry, dir };
201
- }
202
-
203
209
  export {
204
210
  readVariants,
205
211
  writeVariants,
@@ -211,7 +217,7 @@ export {
211
217
  removeAllVariants,
212
218
  checkHealth,
213
219
  validateBranchName,
214
- voluteHome2 as voluteHome,
220
+ voluteHome,
215
221
  ensureVoluteHome,
216
222
  readRegistry,
217
223
  validateAgentName,
@@ -116,5 +116,6 @@ export {
116
116
  findTemplatesRoot,
117
117
  composeTemplate,
118
118
  copyTemplateToDir,
119
- applyInitFiles
119
+ applyInitFiles,
120
+ listFiles
120
121
  };
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/read-stdin.ts
4
+ import { isatty } from "tty";
5
+ async function readStdin() {
6
+ if (isatty(0)) return void 0;
7
+ const chunks = [];
8
+ try {
9
+ for await (const chunk of process.stdin) {
10
+ chunks.push(chunk);
11
+ }
12
+ } catch (err) {
13
+ console.error(`Failed to read from stdin: ${err instanceof Error ? err.message : String(err)}`);
14
+ process.exit(1);
15
+ }
16
+ const text = Buffer.concat(chunks).toString().replace(/\r?\n$/, "");
17
+ return text || void 0;
18
+ }
19
+
20
+ export {
21
+ readStdin
22
+ };
package/dist/cli.js CHANGED
@@ -1,70 +1,57 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
+ import { homedir } from "os";
5
+ import { resolve } from "path";
6
+ if (!process.env.VOLUTE_HOME) {
7
+ process.env.VOLUTE_HOME = resolve(homedir(), ".volute");
8
+ }
4
9
  var command = process.argv[2];
5
10
  var args = process.argv.slice(3);
6
11
  if (command === "--version" || command === "-v") {
7
- const { default: pkg } = await import("./package-Z2SFO2SV.js");
12
+ const { default: pkg } = await import("./package-VQOE7JNH.js");
8
13
  console.log(pkg.version);
9
14
  process.exit(0);
10
15
  }
11
16
  switch (command) {
12
- case "create":
13
- await import("./create-ILVOG75A.js").then((m) => m.run(args));
14
- break;
15
- case "start":
16
- await import("./start-LDPMCMYT.js").then((m) => m.run(args));
17
- break;
18
- case "stop":
19
- await import("./stop-5PZTZCLL.js").then((m) => m.run(args));
20
- break;
21
- case "logs":
22
- await import("./logs-NXFFGUKY.js").then((m) => m.run(args));
17
+ case "agent":
18
+ await import("./agent-Z2B6EFEQ.js").then((m) => m.run(args));
23
19
  break;
24
- case "status":
25
- await import("./status-MVSQG54T.js").then((m) => m.run(args));
20
+ case "message":
21
+ await import("./message-ADHWFHSI.js").then((m) => m.run(args));
26
22
  break;
27
23
  case "variant":
28
- await import("./variant-T64BKARF.js").then((m) => m.run(args));
29
- break;
30
- case "send":
31
- await import("./send-3U6OTKG7.js").then((m) => m.run(args));
32
- break;
33
- case "import":
34
- await import("./import-4CI2ZUTJ.js").then((m) => m.run(args));
35
- break;
36
- case "delete":
37
- await import("./delete-55MXCEY5.js").then((m) => m.run(args));
38
- break;
39
- case "env":
40
- await import("./env-JB27UAC3.js").then((m) => m.run(args));
24
+ await import("./variant-LAB67OC2.js").then((m) => m.run(args));
41
25
  break;
42
26
  case "connector":
43
- await import("./connector-DKDJTLYZ.js").then((m) => m.run(args));
27
+ await import("./connector-LYEMXQEV.js").then((m) => m.run(args));
44
28
  break;
45
29
  case "channel":
46
- await import("./channel-DQ6UY7QB.js").then((m) => m.run(args));
30
+ await import("./channel-MK5OK2SI.js").then((m) => m.run(args));
31
+ break;
32
+ case "schedule":
33
+ await import("./schedule-NAG6F463.js").then((m) => m.run(args));
47
34
  break;
48
- case "upgrade":
49
- await import("./upgrade-6ZW2RD64.js").then((m) => m.run(args));
35
+ case "conversation":
36
+ await import("./conversation-ERXEQZTY.js").then((m) => m.run(args));
37
+ break;
38
+ case "env":
39
+ await import("./env-4D4REPJF.js").then((m) => m.run(args));
50
40
  break;
51
41
  case "up":
52
- await import("./up-F7TMTLRE.js").then((m) => m.run(args));
42
+ await import("./up-7ILD7GU7.js").then((m) => m.run(args));
53
43
  break;
54
44
  case "down":
55
- await import("./down-3OB6UVAJ.js").then((m) => m.run(args));
56
- break;
57
- case "schedule":
58
- await import("./schedule-A35SH4HT.js").then((m) => m.run(args));
45
+ await import("./down-O7IFZLVJ.js").then((m) => m.run(args));
59
46
  break;
60
- case "history":
61
- await import("./history-BKG74I43.js").then((m) => m.run(args));
47
+ case "setup":
48
+ await import("./setup-RPRRGG2F.js").then((m) => m.run(args));
62
49
  break;
63
50
  case "service":
64
- await import("./service-SA4TTMDU.js").then((m) => m.run(args));
51
+ await import("./service-HZNIDNJF.js").then((m) => m.run(args));
65
52
  break;
66
- case "setup":
67
- await import("./setup-2FDVN7OF.js").then((m) => m.run(args));
53
+ case "update":
54
+ await import("./update-LPSIAWQ2.js").then((m) => m.run(args));
68
55
  break;
69
56
  case "--help":
70
57
  case "-h":
@@ -72,45 +59,70 @@ switch (command) {
72
59
  console.log(`volute \u2014 create and manage AI agents
73
60
 
74
61
  Commands:
75
- volute create <name> Create a new agent
76
- volute start <name> Start an agent (daemonized)
77
- volute stop <name> Stop an agent
78
- volute status [<name>] Check agent status (or list all)
79
- volute logs [--agent <name>] Tail agent logs
80
- volute send <name> "<msg>" Send a message to an agent
81
- volute variant create <name> Create a variant (worktree + server)
82
- volute variant list List variants for an agent
83
- volute variant merge <name> Merge a variant back
84
- volute variant delete <name> Delete a variant
85
- volute import <path> Import an OpenClaw workspace
86
- volute env <set|get|list|remove> Manage environment variables
87
- volute connector connect <type> Enable a connector for an agent
88
- volute connector disconnect <type> Disable a connector for an agent
89
- volute channel read <uri> Read recent messages from a channel
90
- volute channel send <uri> "<msg>" Send a message to a channel
91
- volute schedule list List schedules for an agent
92
- volute schedule add ... Add a cron schedule
93
- volute schedule remove ... Remove a schedule
94
- volute history View message history
95
- volute up [--port N] Start the daemon (default: 4200)
96
- volute down Stop the daemon
97
- volute upgrade <name> Upgrade agent to latest template
98
- volute delete <name> [--force] Delete an agent (--force removes files)
99
- volute service install [--port N] Install as system service (auto-start)
100
- volute service uninstall Remove system service
101
- volute service status Check service status
102
- volute setup [--port N] [--host H] Install system service with user isolation
103
- volute setup uninstall [--force] Remove system service + isolation
62
+ volute agent create <name> Create a new agent
63
+ volute agent start <name> Start an agent (daemonized)
64
+ volute agent stop <name> Stop an agent
65
+ volute agent delete <name> [--force] Delete an agent (--force removes files)
66
+ volute agent list List all agents
67
+ volute agent status <name> Check agent status
68
+ volute agent logs <name> [--follow] Tail agent logs
69
+ volute agent upgrade <name> Upgrade agent to latest template
70
+ volute agent import <path> Import an OpenClaw workspace
71
+
72
+ volute message send <name> "<msg>" Send a message to an agent
73
+ volute message history [--agent <name>] View message history
74
+
75
+ volute variant create <name> Create a variant (worktree + server)
76
+ volute variant list List variants for an agent
77
+ volute variant merge <name> Merge a variant back
78
+ volute variant delete <name> Delete a variant
79
+
80
+ volute connector connect <type> Enable a connector for an agent
81
+ volute connector disconnect <type> Disable a connector for an agent
82
+
83
+ volute channel read <uri> Read recent messages from a channel
84
+ volute channel send <uri> "<msg>" Send a message to a channel
85
+
86
+ volute schedule list List schedules for an agent
87
+ volute schedule add ... Add a cron schedule
88
+ volute schedule remove ... Remove a schedule
89
+
90
+ volute conversation create ... Create a group conversation
91
+ volute conversation list List conversations
92
+ volute conversation send <id> "<msg>" Send a message to a conversation
93
+
94
+ volute env <set|get|list|remove> Manage environment variables
95
+
96
+ volute up [--port N] Start the daemon (default: 4200)
97
+ volute down Stop the daemon
98
+
99
+ volute service install [--port N] Install as system service (auto-start)
100
+ volute service uninstall Remove system service
101
+ volute service status Check service status
102
+ volute setup [--port N] [--host H] Install system service with user isolation
103
+ volute setup uninstall [--force] Remove system service + isolation
104
+
105
+ volute update Update to latest version
104
106
 
105
107
  Options:
106
- --version, -v Show version number
107
- --help, -h Show this help message
108
+ --version, -v Show version number
109
+ --help, -h Show this help message
108
110
 
109
- Agent commands (variant, connector, schedule, logs, history, channel) use
110
- --agent <name> or VOLUTE_AGENT env var to identify the agent.`);
111
+ Agent-scoped commands (variant, connector, schedule, channel, conversation, message history)
112
+ use --agent <name> or VOLUTE_AGENT env var to identify the agent.`);
111
113
  break;
112
114
  default:
113
115
  console.error(`Unknown command: ${command}
114
116
  Run 'volute --help' for usage.`);
115
117
  process.exit(1);
116
118
  }
119
+ if (command !== "update") {
120
+ import("./update-check-Y33QDCFL.js").then((m) => m.checkForUpdate()).then((result) => {
121
+ if (result.updateAvailable) {
122
+ console.error(`
123
+ Update available: ${result.current} \u2192 ${result.latest}`);
124
+ console.error(" Run `volute update` to update\n");
125
+ }
126
+ }).catch(() => {
127
+ });
128
+ }
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  resolveAgentName
4
- } from "./chunk-VRVVQIYY.js";
4
+ } from "./chunk-AZEL2IEK.js";
5
5
  import {
6
6
  agentEnvPath,
7
7
  readEnv,
8
8
  writeEnv
9
- } from "./chunk-DNOXHLE5.js";
9
+ } from "./chunk-HE67X4T6.js";
10
10
  import {
11
11
  parseArgs
12
12
  } from "./chunk-D424ZQGI.js";
13
13
  import {
14
14
  daemonFetch
15
- } from "./chunk-YGFIWIOF.js";
15
+ } from "./chunk-7L4AN5D4.js";
16
16
  import {
17
17
  agentDir
18
- } from "./chunk-3C2XR4IY.js";
18
+ } from "./chunk-UX25Z2ND.js";
19
19
  import "./chunk-K3NQKI34.js";
20
20
 
21
21
  // src/commands/connector.ts
@@ -28,13 +28,18 @@ async function run(args) {
28
28
  case "disconnect":
29
29
  await disconnectConnector(args.slice(1));
30
30
  break;
31
+ case "--help":
32
+ case "-h":
33
+ case void 0:
34
+ printUsage();
35
+ break;
31
36
  default:
32
37
  printUsage();
33
- process.exit(subcommand ? 1 : 0);
38
+ process.exit(1);
34
39
  }
35
40
  }
36
41
  function printUsage() {
37
- console.error(`Usage:
42
+ console.log(`Usage:
38
43
  volute connector connect <type> [--agent <name>]
39
44
  volute connector disconnect <type> [--agent <name>]`);
40
45
  }
@@ -90,6 +90,7 @@ client.on(Events.MessageCreate, async (message) => {
90
90
  const senderName = message.author.displayName || message.author.username;
91
91
  const channelKey = `discord:${message.channelId}`;
92
92
  const channelName = !isDM && "name" in message.channel ? message.channel.name : void 0;
93
+ const participantCount = isDM ? 2 : message.guild?.memberCount;
93
94
  const payload = {
94
95
  content,
95
96
  channel: channelKey,
@@ -97,7 +98,8 @@ client.on(Events.MessageCreate, async (message) => {
97
98
  platform: "Discord",
98
99
  ...isDM ? { isDM: true } : {},
99
100
  ...channelName ? { channelName } : {},
100
- ...message.guild?.name ? { guildName: message.guild.name } : {}
101
+ ...message.guild?.name ? { serverName: message.guild.name } : {},
102
+ ...participantCount ? { participantCount } : {}
101
103
  };
102
104
  if (isFollowedChannel && !isMentioned) {
103
105
  await fireAndForget(env, payload);
@@ -27,11 +27,12 @@ var app = new App({
27
27
  appToken
28
28
  });
29
29
  var botUserId;
30
+ var serverName;
30
31
  app.message(async ({ message, say }) => {
31
32
  if (message.subtype) return;
32
33
  if (!("user" in message) || !("text" in message)) return;
33
34
  if ("bot_id" in message && message.bot_id) return;
34
- const isDM = message.channel_type === "im";
35
+ const isDM = message.channel_type === "im" || message.channel_type === "mpim";
35
36
  const isMentioned = !isDM && botUserId && message.text?.includes(`<@${botUserId}>`);
36
37
  const isFollowedChannel = !isDM && followedChannelIds.has(message.channel);
37
38
  if (!isDM && !isMentioned && !isFollowedChannel) return;
@@ -65,14 +66,16 @@ app.message(async ({ message, say }) => {
65
66
  }
66
67
  if (content.length === 0) return;
67
68
  let channelName;
68
- if (!isDM) {
69
+ let numMembers;
70
+ if (message.channel_type !== "im") {
69
71
  try {
70
72
  const info = await app.client.conversations.info({
71
73
  channel: message.channel
72
74
  });
73
75
  channelName = info.channel?.name;
76
+ numMembers = info.channel?.num_members;
74
77
  } catch (err) {
75
- console.warn(`Failed to get channel name: ${err}`);
78
+ console.warn(`Failed to get channel info: ${err}`);
76
79
  }
77
80
  }
78
81
  let senderName = message.user;
@@ -85,13 +88,16 @@ app.message(async ({ message, say }) => {
85
88
  console.warn(`Failed to get user info: ${err}`);
86
89
  }
87
90
  const channelKey = `slack:${message.channel}`;
91
+ const participantCount = message.channel_type === "im" ? 2 : numMembers;
88
92
  const payload = {
89
93
  content,
90
94
  channel: channelKey,
91
95
  sender: senderName,
92
96
  platform: "Slack",
93
97
  ...isDM ? { isDM: true } : {},
94
- ...channelName ? { channelName } : {}
98
+ ...channelName ? { channelName } : {},
99
+ ...serverName ? { serverName } : {},
100
+ ...participantCount ? { participantCount } : {}
95
101
  };
96
102
  if (isFollowedChannel && !isMentioned) {
97
103
  await fireAndForget(env, payload);
@@ -134,7 +140,10 @@ async function start() {
134
140
  throw new Error("auth.test succeeded but returned no user_id");
135
141
  }
136
142
  botUserId = auth.user_id;
137
- console.log(`Connected to Slack as bot user ${botUserId}`);
143
+ serverName = auth.team;
144
+ console.log(
145
+ `Connected to Slack as bot user ${botUserId}${serverName ? ` in ${serverName}` : ""}`
146
+ );
138
147
  console.log(`Bridging to agent: ${env.agentName} via ${env.baseUrl}/message`);
139
148
  if (followedChannelNames.length > 0) {
140
149
  try {
@@ -40,13 +40,22 @@ bot.on(message("text"), async (ctx) => {
40
40
  if (content.length === 0) return;
41
41
  const senderName = ctx.message.from.first_name + (ctx.message.from.last_name ? ` ${ctx.message.from.last_name}` : "");
42
42
  const chatTitle = "title" in ctx.chat ? ctx.chat.title : void 0;
43
+ let participantCount = isDM ? 2 : void 0;
44
+ if (!isDM) {
45
+ try {
46
+ participantCount = await ctx.telegram.getChatMembersCount(ctx.chat.id);
47
+ } catch (err) {
48
+ console.warn(`Failed to get member count for chat ${ctx.chat.id}: ${err}`);
49
+ }
50
+ }
43
51
  const payload = {
44
52
  content,
45
53
  channel: `telegram:${ctx.chat.id}`,
46
54
  sender: senderName,
47
55
  platform: "Telegram",
48
56
  ...isDM ? { isDM: true } : {},
49
- ...chatTitle ? { channelName: chatTitle } : {}
57
+ ...chatTitle ? { channelName: chatTitle } : {},
58
+ ...participantCount ? { participantCount } : {}
50
59
  };
51
60
  if (isFollowedChat && !isMentioned) {
52
61
  await fireAndForget(env, payload);
@@ -88,12 +97,22 @@ bot.on(message("photo"), async (ctx) => {
88
97
  if (content.length === 0) return;
89
98
  const senderName = ctx.message.from.first_name + (ctx.message.from.last_name ? ` ${ctx.message.from.last_name}` : "");
90
99
  const chatTitle = "title" in ctx.chat ? ctx.chat.title : void 0;
100
+ let participantCount = isDM ? 2 : void 0;
101
+ if (!isDM) {
102
+ try {
103
+ participantCount = await ctx.telegram.getChatMembersCount(ctx.chat.id);
104
+ } catch (err) {
105
+ console.warn(`Failed to get member count for chat ${ctx.chat.id}: ${err}`);
106
+ }
107
+ }
91
108
  const payload = {
92
109
  content,
93
110
  channel: `telegram:${ctx.chat.id}`,
94
111
  sender: senderName,
95
112
  platform: "Telegram",
96
- ...chatTitle ? { channelName: chatTitle } : {}
113
+ ...isDM ? { isDM: true } : {},
114
+ ...chatTitle ? { channelName: chatTitle } : {},
115
+ ...participantCount ? { participantCount } : {}
97
116
  };
98
117
  if (isFollowedChat) {
99
118
  await fireAndForget(env, payload);