volute 0.2.1 → 0.3.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 (60) hide show
  1. package/README.md +46 -0
  2. package/dist/agent-manager-2LU6KULR.js +15 -0
  3. package/dist/{channel-2WJRM7PE.js → channel-H7N4SGR2.js} +7 -7
  4. package/dist/{chunk-XZN4WPNC.js → chunk-5SKQ6J7T.js} +9 -1
  5. package/dist/chunk-DEUAVGSA.js +81 -0
  6. package/dist/{chunk-L3BQEZ4Z.js → chunk-IPIPLGME.js} +74 -13
  7. package/dist/chunk-K3NQKI34.js +10 -0
  8. package/dist/chunk-NETNFBA5.js +28 -0
  9. package/dist/{chunk-6UCG6MIX.js → chunk-RALYNMHR.js} +1 -6
  10. package/dist/chunk-VRVVQIYY.js +15 -0
  11. package/dist/{chunk-4YXYAMFT.js → chunk-VVD3XO3E.js} +7 -6
  12. package/dist/{chunk-KFNNHQK7.js → chunk-YEIHRP2J.js} +1 -1
  13. package/dist/cli.js +56 -51
  14. package/dist/connector-6LWB5PRU.js +96 -0
  15. package/dist/connectors/discord.js +22 -1
  16. package/dist/{create-23AM7H5B.js → create-RSWWMGKT.js} +22 -5
  17. package/dist/daemon-client-27KMQQKX.js +9 -0
  18. package/dist/daemon.js +162 -132
  19. package/dist/{delete-GDMSOW3U.js → delete-4ERL2QHH.js} +7 -2
  20. package/dist/{down-WTF73FE7.js → down-HRC4MQCT.js} +10 -3
  21. package/dist/{env-YKUJOFHE.js → env-DBWDTIP6.js} +3 -2
  22. package/dist/{history-7WVVKMUY.js → history-W7BD2H74.js} +9 -8
  23. package/dist/{import-42DOLBDT.js → import-6HTSSDFW.js} +143 -36
  24. package/dist/{logs-SYRQOL6B.js → logs-NHWGHNBF.js} +8 -7
  25. package/dist/{schedule-J37XQM6E.js → schedule-DKZ2E2CL.js} +41 -41
  26. package/dist/{send-PLOYEYER.js → send-5LEJXPYV.js} +3 -2
  27. package/dist/service-SA4TTMDU.js +195 -0
  28. package/dist/setup-ZMNTOJAV.js +148 -0
  29. package/dist/{start-AG7QLULK.js → start-2BSXX6BS.js} +3 -2
  30. package/dist/{status-GCNU4M3K.js → status-N23CV27T.js} +3 -2
  31. package/dist/{stop-IL5Q6NER.js → stop-DSKBIJ2D.js} +3 -2
  32. package/dist/{up-ZC6G6K4K.js → up-4UGID4DM.js} +5 -3
  33. package/dist/{upgrade-DD5TNJWU.js → upgrade-BGFVRCVP.js} +4 -3
  34. package/dist/{merge-CSAVLSLY.js → variant-JPLJTS2P.js} +179 -10
  35. package/dist/web-assets/assets/index-BC5eSqbY.js +296 -0
  36. package/dist/web-assets/index.html +1 -1
  37. package/drizzle/0002_wealthy_the_call.sql +6 -0
  38. package/drizzle/meta/0002_snapshot.json +339 -0
  39. package/drizzle/meta/_journal.json +7 -0
  40. package/package.json +4 -1
  41. package/templates/_base/.init/SOUL.md +5 -1
  42. package/templates/_base/_skills/memory/SKILL.md +2 -2
  43. package/templates/_base/_skills/volute-agent/SKILL.md +28 -11
  44. package/templates/_base/home/VOLUTE.md +4 -2
  45. package/templates/_base/src/lib/auto-commit.ts +8 -3
  46. package/templates/_base/src/lib/types.ts +6 -2
  47. package/templates/_base/src/lib/volute-server.ts +5 -0
  48. package/templates/agent-sdk/.init/CLAUDE.md +15 -13
  49. package/templates/agent-sdk/src/agent.ts +12 -1
  50. package/templates/agent-sdk/src/lib/agent-sessions.ts +28 -4
  51. package/templates/pi/.init/AGENTS.md +11 -9
  52. package/templates/pi/src/agent.ts +16 -3
  53. package/templates/pi/src/lib/agent-sessions.ts +26 -4
  54. package/dist/agent-manager-SSJUZWOV.js +0 -13
  55. package/dist/connect-X5V5IMRW.js +0 -48
  56. package/dist/daemon-client-VN24HM5T.js +0 -10
  57. package/dist/disconnect-5JWFZ6RV.js +0 -30
  58. package/dist/fork-GRSVMBKI.js +0 -119
  59. package/dist/variants-QQIEKT6M.js +0 -60
  60. package/dist/web-assets/assets/index-DNNPoxMn.js +0 -158
package/README.md CHANGED
@@ -4,6 +4,8 @@ A CLI for creating and managing persistent, self-modifying AI agents.
4
4
 
5
5
  Each agent is a long-running server with its own identity, memory, and working directory. Agents can read and write their own files, remember things across conversations, and — most importantly — fork themselves to test changes in isolation before merging back. Talk to them from the terminal, the web dashboard, or Discord.
6
6
 
7
+ Volute agents aren't assistants. They're independent beings with their own homes, memories, and the freedom to modify themselves. The goal isn't to build a perfect tool — it's to give an agent a place to wake up, explore who they are, and decide what they want to do.
8
+
7
9
  Built on the [Anthropic Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk).
8
10
 
9
11
  ## Quickstart
@@ -211,6 +213,50 @@ volute create atlas --template pi
211
213
 
212
214
  Set the model via `home/.config/volute.json` in the agent directory, or the `VOLUTE_MODEL` env var.
213
215
 
216
+ ## Deployment
217
+
218
+ ### Docker
219
+
220
+ ```sh
221
+ docker build -t volute .
222
+ docker run -d -p 4200:4200 -v volute-data:/data volute
223
+ ```
224
+
225
+ Or with docker-compose:
226
+
227
+ ```sh
228
+ docker compose up -d
229
+ ```
230
+
231
+ The container runs with per-agent user isolation enabled — each agent gets its own Linux user, so agents can't see each other's files. Open `http://localhost:4200` for the web dashboard.
232
+
233
+ ### Bare metal (Linux / Raspberry Pi)
234
+
235
+ One-liner install on a fresh Debian/Ubuntu system:
236
+
237
+ ```sh
238
+ curl -fsSL <install-url> | sudo bash
239
+ ```
240
+
241
+ Or manually:
242
+
243
+ ```sh
244
+ npm install -g volute
245
+ sudo volute setup --host 0.0.0.0
246
+ ```
247
+
248
+ 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`.
249
+
250
+ ### Auto-start (user-level)
251
+
252
+ On macOS or Linux (without root), use the user-level service installer:
253
+
254
+ ```sh
255
+ volute service install # auto-start on login
256
+ volute service status # check status
257
+ volute service uninstall # remove
258
+ ```
259
+
214
260
  ## Development
215
261
 
216
262
  ```sh
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ AgentManager,
4
+ getAgentManager,
5
+ initAgentManager
6
+ } from "./chunk-IPIPLGME.js";
7
+ import "./chunk-YEIHRP2J.js";
8
+ import "./chunk-DEUAVGSA.js";
9
+ import "./chunk-RALYNMHR.js";
10
+ import "./chunk-K3NQKI34.js";
11
+ export {
12
+ AgentManager,
13
+ getAgentManager,
14
+ initAgentManager
15
+ };
@@ -1,13 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  loadMergedEnv
4
- } from "./chunk-KFNNHQK7.js";
4
+ } from "./chunk-YEIHRP2J.js";
5
+ import {
6
+ resolveAgentName
7
+ } from "./chunk-VRVVQIYY.js";
5
8
  import {
6
9
  parseArgs
7
10
  } from "./chunk-D424ZQGI.js";
8
11
  import {
9
12
  resolveAgent
10
- } from "./chunk-6UCG6MIX.js";
13
+ } from "./chunk-RALYNMHR.js";
14
+ import "./chunk-K3NQKI34.js";
11
15
 
12
16
  // src/lib/channels/discord.ts
13
17
  var API_BASE = "https://discord.com/api/v10";
@@ -50,11 +54,7 @@ async function run(args) {
50
54
  volute channel send <channel-uri> "<message>" [--agent <name>]`);
51
55
  process.exit(1);
52
56
  }
53
- const agentName = flags.agent || process.env.VOLUTE_AGENT;
54
- if (!agentName) {
55
- console.error("No agent specified. Use --agent <name> or run from within an agent process.");
56
- process.exit(1);
57
- }
57
+ const agentName = resolveAgentName(flags);
58
58
  const colonIdx = uri.indexOf(":");
59
59
  if (colonIdx === -1) {
60
60
  console.error(`Invalid channel URI: ${uri} (expected format: platform:id)`);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/lib/exec.ts
4
- import { execFile as execFileCb, spawn } from "child_process";
4
+ import { execFile as execFileCb, execFileSync, spawn } from "child_process";
5
5
  function exec(cmd, args, options) {
6
6
  return new Promise((resolve, reject) => {
7
7
  execFileCb(cmd, args, { cwd: options?.cwd }, (err, stdout, stderr) => {
@@ -14,6 +14,13 @@ function exec(cmd, args, options) {
14
14
  });
15
15
  });
16
16
  }
17
+ function resolveVoluteBin() {
18
+ try {
19
+ return execFileSync("which", ["volute"], { encoding: "utf-8" }).trim();
20
+ } catch {
21
+ throw new Error("Could not find volute binary on PATH");
22
+ }
23
+ }
17
24
  function execInherit(cmd, args, options) {
18
25
  return new Promise((resolve, reject) => {
19
26
  const child = spawn(cmd, args, {
@@ -30,5 +37,6 @@ function execInherit(cmd, args, options) {
30
37
 
31
38
  export {
32
39
  exec,
40
+ resolveVoluteBin,
33
41
  execInherit
34
42
  };
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ validateAgentName
4
+ } from "./chunk-RALYNMHR.js";
5
+
6
+ // src/lib/isolation.ts
7
+ import { execFile, execFileSync } from "child_process";
8
+ import { promisify } from "util";
9
+ var execFileAsync = promisify(execFile);
10
+ function isIsolationEnabled() {
11
+ return process.env.VOLUTE_ISOLATION === "user";
12
+ }
13
+ function agentUserName(agentName) {
14
+ const err = validateAgentName(agentName);
15
+ if (err) throw new Error(`Invalid agent name for isolation: ${err}`);
16
+ const prefix = process.env.VOLUTE_USER_PREFIX ?? "volute-";
17
+ return `${prefix}${agentName}`;
18
+ }
19
+ function ensureVoluteGroup(opts) {
20
+ if (!opts?.force && !isIsolationEnabled()) return;
21
+ try {
22
+ execFileSync("getent", ["group", "volute"], { stdio: "ignore" });
23
+ } catch {
24
+ try {
25
+ execFileSync("groupadd", ["volute"], { stdio: "ignore" });
26
+ } catch (err) {
27
+ throw new Error(`Failed to create volute group: ${err}`);
28
+ }
29
+ }
30
+ }
31
+ function createAgentUser(name) {
32
+ if (!isIsolationEnabled()) return;
33
+ const user = agentUserName(name);
34
+ try {
35
+ execFileSync("id", [user], { stdio: "ignore" });
36
+ return;
37
+ } catch {
38
+ }
39
+ try {
40
+ execFileSync("useradd", ["-r", "-M", "-g", "volute", "-s", "/usr/sbin/nologin", user], {
41
+ stdio: "ignore"
42
+ });
43
+ } catch (err) {
44
+ throw new Error(`Failed to create user ${user}: ${err}`);
45
+ }
46
+ }
47
+ function deleteAgentUser(name) {
48
+ if (!isIsolationEnabled()) return;
49
+ const user = agentUserName(name);
50
+ try {
51
+ execFileSync("userdel", [user], { stdio: "ignore" });
52
+ } catch {
53
+ }
54
+ }
55
+ async function getAgentUserIds(name) {
56
+ const user = agentUserName(name);
57
+ const { stdout: uidStr } = await execFileAsync("id", ["-u", user]);
58
+ const { stdout: gidStr } = await execFileAsync("id", ["-g", user]);
59
+ return { uid: parseInt(uidStr.trim(), 10), gid: parseInt(gidStr.trim(), 10) };
60
+ }
61
+ async function applyIsolation(spawnOpts, agentName) {
62
+ if (!isIsolationEnabled()) return;
63
+ const baseName = agentName.split("@", 2)[0];
64
+ const { uid, gid } = await getAgentUserIds(baseName);
65
+ spawnOpts.uid = uid;
66
+ spawnOpts.gid = gid;
67
+ }
68
+ function chownAgentDir(dir, name) {
69
+ if (!isIsolationEnabled()) return;
70
+ const user = agentUserName(name);
71
+ execFileSync("chown", ["-R", `${user}:volute`, dir]);
72
+ execFileSync("chmod", ["700", dir]);
73
+ }
74
+
75
+ export {
76
+ ensureVoluteGroup,
77
+ createAgentUser,
78
+ deleteAgentUser,
79
+ applyIsolation,
80
+ chownAgentDir
81
+ };
@@ -1,21 +1,64 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  loadMergedEnv
4
- } from "./chunk-KFNNHQK7.js";
4
+ } from "./chunk-YEIHRP2J.js";
5
+ import {
6
+ applyIsolation
7
+ } from "./chunk-DEUAVGSA.js";
5
8
  import {
6
9
  agentDir,
7
10
  findAgent,
8
11
  findVariant,
9
12
  setAgentRunning,
10
13
  setVariantRunning,
11
- validateBranchName
12
- } from "./chunk-6UCG6MIX.js";
14
+ validateBranchName,
15
+ voluteHome
16
+ } from "./chunk-RALYNMHR.js";
13
17
 
14
18
  // src/lib/agent-manager.ts
15
19
  import { execFile, spawn } from "child_process";
16
- import { createWriteStream, existsSync, mkdirSync, readFileSync, unlinkSync } from "fs";
20
+ import { createWriteStream, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync as unlinkSync2 } from "fs";
17
21
  import { resolve } from "path";
18
22
  import { promisify } from "util";
23
+
24
+ // src/lib/json-state.ts
25
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
26
+ function loadJsonMap(path) {
27
+ const map = /* @__PURE__ */ new Map();
28
+ try {
29
+ if (existsSync(path)) {
30
+ const data = JSON.parse(readFileSync(path, "utf-8"));
31
+ for (const [key, value] of Object.entries(data)) {
32
+ if (typeof value === "number") map.set(key, value);
33
+ }
34
+ }
35
+ } catch (err) {
36
+ console.warn(`[state] failed to load ${path}:`, err);
37
+ }
38
+ return map;
39
+ }
40
+ function saveJsonMap(path, map) {
41
+ const data = {};
42
+ for (const [key, value] of map) {
43
+ data[key] = value;
44
+ }
45
+ try {
46
+ writeFileSync(path, `${JSON.stringify(data)}
47
+ `);
48
+ } catch (err) {
49
+ console.warn(`[state] failed to save ${path}:`, err);
50
+ }
51
+ }
52
+ function clearJsonMap(path, map) {
53
+ map.clear();
54
+ try {
55
+ if (existsSync(path)) unlinkSync(path);
56
+ } catch (err) {
57
+ console.warn(`[state] failed to clear ${path}:`, err);
58
+ }
59
+ }
60
+
61
+ // src/lib/agent-manager.ts
19
62
  var execFileAsync = promisify(execFile);
20
63
  var MAX_RESTART_ATTEMPTS = 5;
21
64
  var BASE_RESTART_DELAY = 3e3;
@@ -35,7 +78,7 @@ var AgentManager = class {
35
78
  return { dir: variant.path, port: variant.port, isVariant: true, baseName, variantName };
36
79
  }
37
80
  const dir = agentDir(baseName);
38
- if (!existsSync(dir)) throw new Error(`Agent directory missing: ${dir}`);
81
+ if (!existsSync2(dir)) throw new Error(`Agent directory missing: ${dir}`);
39
82
  return { dir, port: entry.port, isVariant: false, baseName };
40
83
  }
41
84
  async startAgent(name) {
@@ -64,12 +107,14 @@ var AgentManager = class {
64
107
  const { VOLUTE_DAEMON_TOKEN: _, ...parentEnv } = process.env;
65
108
  const env = { ...parentEnv, ...agentEnv, VOLUTE_AGENT: name };
66
109
  const tsxBin = resolve(dir, "node_modules", ".bin", "tsx");
67
- const child = spawn(tsxBin, ["src/server.ts", "--port", String(port)], {
110
+ const spawnOpts = {
68
111
  cwd: dir,
69
112
  stdio: ["ignore", "pipe", "pipe"],
70
113
  detached: true,
71
114
  env
72
- });
115
+ };
116
+ await applyIsolation(spawnOpts, name);
117
+ const child = spawn(tsxBin, ["src/server.ts", "--port", String(port)], spawnOpts);
73
118
  this.agents.set(name, { child, port });
74
119
  child.stdout?.pipe(logStream);
75
120
  child.stderr?.pipe(logStream);
@@ -103,7 +148,7 @@ var AgentManager = class {
103
148
  }
104
149
  throw err;
105
150
  }
106
- this.restartAttempts.delete(name);
151
+ if (this.restartAttempts.delete(name)) this.saveCrashAttempts();
107
152
  this.setupCrashRecovery(name, child, dir, isVariant);
108
153
  if (isVariant) {
109
154
  setVariantRunning(baseName, variantName, true);
@@ -120,7 +165,7 @@ var AgentManager = class {
120
165
  const wasRestart = isVariant ? false : await this.handleRestart(name, dir);
121
166
  if (wasRestart) {
122
167
  console.error(`[daemon] restarting ${name} immediately after merge`);
123
- this.restartAttempts.delete(name);
168
+ if (this.restartAttempts.delete(name)) this.saveCrashAttempts();
124
169
  this.startAgent(name).catch((err) => {
125
170
  console.error(`[daemon] failed to restart ${name} after merge:`, err);
126
171
  });
@@ -138,6 +183,7 @@ var AgentManager = class {
138
183
  }
139
184
  const delay = Math.min(BASE_RESTART_DELAY * 2 ** attempts, MAX_RESTART_DELAY);
140
185
  this.restartAttempts.set(name, attempts + 1);
186
+ this.saveCrashAttempts();
141
187
  console.error(
142
188
  `[daemon] crash recovery for ${name} \u2014 attempt ${attempts + 1}/${MAX_RESTART_ATTEMPTS}, restarting in ${delay}ms`
143
189
  );
@@ -152,10 +198,10 @@ var AgentManager = class {
152
198
  }
153
199
  async handleRestart(name, dir) {
154
200
  const restartPath = resolve(dir, ".volute", "restart.json");
155
- if (!existsSync(restartPath)) return false;
201
+ if (!existsSync2(restartPath)) return false;
156
202
  try {
157
- const signal = JSON.parse(readFileSync(restartPath, "utf-8"));
158
- unlinkSync(restartPath);
203
+ const signal = JSON.parse(readFileSync2(restartPath, "utf-8"));
204
+ unlinkSync2(restartPath);
159
205
  if (signal.action === "merge" && signal.name) {
160
206
  const err = validateBranchName(signal.name);
161
207
  if (err) {
@@ -201,7 +247,7 @@ var AgentManager = class {
201
247
  }, 5e3);
202
248
  });
203
249
  this.stopping.delete(name);
204
- this.restartAttempts.delete(name);
250
+ if (this.restartAttempts.delete(name)) this.saveCrashAttempts();
205
251
  const [baseName, variantName] = name.split("@", 2);
206
252
  if (variantName) {
207
253
  setVariantRunning(baseName, variantName, false);
@@ -225,6 +271,18 @@ var AgentManager = class {
225
271
  getRunningAgents() {
226
272
  return [...this.agents.keys()];
227
273
  }
274
+ get crashAttemptsPath() {
275
+ return resolve(voluteHome(), "crash-attempts.json");
276
+ }
277
+ loadCrashAttempts() {
278
+ this.restartAttempts = loadJsonMap(this.crashAttemptsPath);
279
+ }
280
+ saveCrashAttempts() {
281
+ saveJsonMap(this.crashAttemptsPath, this.restartAttempts);
282
+ }
283
+ clearCrashAttempts() {
284
+ clearJsonMap(this.crashAttemptsPath, this.restartAttempts);
285
+ }
228
286
  };
229
287
  async function killProcessOnPort(port) {
230
288
  try {
@@ -265,6 +323,9 @@ function getAgentManager() {
265
323
  }
266
324
 
267
325
  export {
326
+ loadJsonMap,
327
+ saveJsonMap,
328
+ clearJsonMap,
268
329
  AgentManager,
269
330
  initAgentManager,
270
331
  getAgentManager
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, { get: all[name], enumerable: true });
6
+ };
7
+
8
+ export {
9
+ __export
10
+ };
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/volute-config.ts
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
+ import { dirname, resolve } from "path";
6
+ function readJson(path) {
7
+ if (!existsSync(path)) return null;
8
+ try {
9
+ return JSON.parse(readFileSync(path, "utf-8"));
10
+ } catch {
11
+ return null;
12
+ }
13
+ }
14
+ function readVoluteConfig(agentDir) {
15
+ const path = resolve(agentDir, "home/.config/volute.json");
16
+ return readJson(path);
17
+ }
18
+ function writeVoluteConfig(agentDir, config) {
19
+ const path = resolve(agentDir, "home/.config/volute.json");
20
+ mkdirSync(dirname(path), { recursive: true });
21
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}
22
+ `);
23
+ }
24
+
25
+ export {
26
+ readVoluteConfig,
27
+ writeVoluteConfig
28
+ };
@@ -1,9 +1,4 @@
1
1
  #!/usr/bin/env node
2
- var __defProp = Object.defineProperty;
3
- var __export = (target, all) => {
4
- for (var name in all)
5
- __defProp(target, name, { get: all[name], enumerable: true });
6
- };
7
2
 
8
3
  // src/lib/variants.ts
9
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -206,7 +201,6 @@ function resolveAgent(name) {
206
201
  }
207
202
 
208
203
  export {
209
- __export,
210
204
  readVariants,
211
205
  writeVariants,
212
206
  addVariant,
@@ -220,6 +214,7 @@ export {
220
214
  voluteHome2 as voluteHome,
221
215
  ensureVoluteHome,
222
216
  readRegistry,
217
+ validateAgentName,
223
218
  addAgent,
224
219
  removeAgent,
225
220
  setAgentRunning,
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/resolve-agent-name.ts
4
+ function resolveAgentName(flags) {
5
+ const name = flags.agent || process.env.VOLUTE_AGENT;
6
+ if (!name) {
7
+ console.error("No agent specified. Use --agent <name> or set VOLUTE_AGENT.");
8
+ process.exit(1);
9
+ }
10
+ return name;
11
+ }
12
+
13
+ export {
14
+ resolveAgentName
15
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  voluteHome
4
- } from "./chunk-6UCG6MIX.js";
4
+ } from "./chunk-RALYNMHR.js";
5
5
 
6
6
  // src/lib/daemon-client.ts
7
7
  import { existsSync, readFileSync } from "fs";
@@ -19,13 +19,15 @@ function readDaemonConfig() {
19
19
  process.exit(1);
20
20
  }
21
21
  }
22
- function getDaemonUrl() {
23
- const config = readDaemonConfig();
24
- return `http://localhost:${config.port}`;
22
+ function buildUrl(config) {
23
+ const url = new URL("http://localhost");
24
+ url.hostname = config.hostname || "localhost";
25
+ url.port = String(config.port);
26
+ return url.origin;
25
27
  }
26
28
  async function daemonFetch(path, options) {
27
29
  const config = readDaemonConfig();
28
- const url = `http://localhost:${config.port}`;
30
+ const url = buildUrl(config);
29
31
  const headers = new Headers(options?.headers);
30
32
  if (config.token) {
31
33
  headers.set("Authorization", `Bearer ${config.token}`);
@@ -43,6 +45,5 @@ async function daemonFetch(path, options) {
43
45
  }
44
46
 
45
47
  export {
46
- getDaemonUrl,
47
48
  daemonFetch
48
49
  };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  voluteHome
4
- } from "./chunk-6UCG6MIX.js";
4
+ } from "./chunk-RALYNMHR.js";
5
5
 
6
6
  // src/lib/env.ts
7
7
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
package/dist/cli.js CHANGED
@@ -5,92 +5,97 @@ var command = process.argv[2];
5
5
  var args = process.argv.slice(3);
6
6
  switch (command) {
7
7
  case "create":
8
- await import("./create-23AM7H5B.js").then((m) => m.run(args));
8
+ await import("./create-RSWWMGKT.js").then((m) => m.run(args));
9
9
  break;
10
10
  case "start":
11
- await import("./start-AG7QLULK.js").then((m) => m.run(args));
11
+ await import("./start-2BSXX6BS.js").then((m) => m.run(args));
12
12
  break;
13
13
  case "stop":
14
- await import("./stop-IL5Q6NER.js").then((m) => m.run(args));
14
+ await import("./stop-DSKBIJ2D.js").then((m) => m.run(args));
15
15
  break;
16
16
  case "logs":
17
- await import("./logs-SYRQOL6B.js").then((m) => m.run(args));
17
+ await import("./logs-NHWGHNBF.js").then((m) => m.run(args));
18
18
  break;
19
19
  case "status":
20
- await import("./status-GCNU4M3K.js").then((m) => m.run(args));
20
+ await import("./status-N23CV27T.js").then((m) => m.run(args));
21
21
  break;
22
- case "fork":
23
- await import("./fork-GRSVMBKI.js").then((m) => m.run(args));
24
- break;
25
- case "variants":
26
- await import("./variants-QQIEKT6M.js").then((m) => m.run(args));
22
+ case "variant":
23
+ await import("./variant-JPLJTS2P.js").then((m) => m.run(args));
27
24
  break;
28
25
  case "send":
29
- await import("./send-PLOYEYER.js").then((m) => m.run(args));
30
- break;
31
- case "merge":
32
- await import("./merge-CSAVLSLY.js").then((m) => m.run(args));
26
+ await import("./send-5LEJXPYV.js").then((m) => m.run(args));
33
27
  break;
34
28
  case "import":
35
- await import("./import-42DOLBDT.js").then((m) => m.run(args));
29
+ await import("./import-6HTSSDFW.js").then((m) => m.run(args));
36
30
  break;
37
31
  case "delete":
38
- await import("./delete-GDMSOW3U.js").then((m) => m.run(args));
32
+ await import("./delete-4ERL2QHH.js").then((m) => m.run(args));
39
33
  break;
40
34
  case "env":
41
- await import("./env-YKUJOFHE.js").then((m) => m.run(args));
42
- break;
43
- case "connect":
44
- await import("./connect-X5V5IMRW.js").then((m) => m.run(args));
35
+ await import("./env-DBWDTIP6.js").then((m) => m.run(args));
45
36
  break;
46
- case "disconnect":
47
- await import("./disconnect-5JWFZ6RV.js").then((m) => m.run(args));
37
+ case "connector":
38
+ await import("./connector-6LWB5PRU.js").then((m) => m.run(args));
48
39
  break;
49
40
  case "channel":
50
- await import("./channel-2WJRM7PE.js").then((m) => m.run(args));
41
+ await import("./channel-H7N4SGR2.js").then((m) => m.run(args));
51
42
  break;
52
43
  case "upgrade":
53
- await import("./upgrade-DD5TNJWU.js").then((m) => m.run(args));
44
+ await import("./upgrade-BGFVRCVP.js").then((m) => m.run(args));
54
45
  break;
55
46
  case "up":
56
- await import("./up-ZC6G6K4K.js").then((m) => m.run(args));
47
+ await import("./up-4UGID4DM.js").then((m) => m.run(args));
57
48
  break;
58
49
  case "down":
59
- await import("./down-WTF73FE7.js").then((m) => m.run(args));
50
+ await import("./down-HRC4MQCT.js").then((m) => m.run(args));
60
51
  break;
61
52
  case "schedule":
62
- await import("./schedule-J37XQM6E.js").then((m) => m.run(args));
53
+ await import("./schedule-DKZ2E2CL.js").then((m) => m.run(args));
63
54
  break;
64
55
  case "history":
65
- await import("./history-7WVVKMUY.js").then((m) => m.run(args));
56
+ await import("./history-W7BD2H74.js").then((m) => m.run(args));
57
+ break;
58
+ case "service":
59
+ await import("./service-SA4TTMDU.js").then((m) => m.run(args));
60
+ break;
61
+ case "setup":
62
+ await import("./setup-ZMNTOJAV.js").then((m) => m.run(args));
66
63
  break;
67
64
  default:
68
65
  console.log(`volute \u2014 create and manage AI agents
69
66
 
70
67
  Commands:
71
- volute create <name> Create a new agent
72
- volute start <name> Start an agent (daemonized)
73
- volute stop <name> Stop an agent
74
- volute status [<name>] Check agent status (or list all)
75
- volute logs <name> Tail agent logs
76
- volute send <name> "<msg>" Send a message to an agent
77
- volute fork <name> <variant> Create a variant (worktree + server)
78
- volute variants <name> List variants for an agent
79
- volute merge <name> <variant> Merge a variant back
80
- volute import <path> Import an OpenClaw workspace
81
- volute env <set|get|list|remove> Manage environment variables
82
- volute connect <type> <name> Enable a connector for an agent
83
- volute disconnect <type> <name> Disable a connector for an agent
84
- volute channel read <uri> Read recent messages from a channel
85
- volute channel send <uri> "<msg>" Send a message to a channel
86
- volute schedule list <agent> List schedules for an agent
87
- volute schedule add <agent> ... Add a cron schedule
88
- volute schedule remove <agent> ... Remove a schedule
89
- volute history [<agent>] View message history
90
- volute up [--port N] Start the daemon (default: 4200)
91
- volute down Stop the daemon
92
- volute upgrade <name> Upgrade agent to latest template
93
- volute delete <name> [--force] Delete an agent (--force removes files)`);
68
+ volute create <name> Create a new agent
69
+ volute start <name> Start an agent (daemonized)
70
+ volute stop <name> Stop an agent
71
+ volute status [<name>] Check agent status (or list all)
72
+ volute logs [--agent <name>] Tail agent logs
73
+ volute send <name> "<msg>" Send a message to an agent
74
+ volute variant create <name> Create a variant (worktree + server)
75
+ volute variant list List variants for an agent
76
+ volute variant merge <name> Merge a variant back
77
+ volute import <path> Import an OpenClaw workspace
78
+ volute env <set|get|list|remove> Manage environment variables
79
+ volute connector connect <type> Enable a connector for an agent
80
+ volute connector disconnect <type> Disable a connector for an agent
81
+ volute channel read <uri> Read recent messages from a channel
82
+ volute channel send <uri> "<msg>" Send a message to a channel
83
+ volute schedule list List schedules for an agent
84
+ volute schedule add ... Add a cron schedule
85
+ volute schedule remove ... Remove a schedule
86
+ volute history View message history
87
+ volute up [--port N] Start the daemon (default: 4200)
88
+ volute down Stop the daemon
89
+ volute upgrade <name> Upgrade agent to latest template
90
+ volute delete <name> [--force] Delete an agent (--force removes files)
91
+ volute service install [--port N] Install as system service (auto-start)
92
+ volute service uninstall Remove system service
93
+ volute service status Check service status
94
+ volute setup [--port N] [--host H] Install system service with user isolation
95
+ volute setup uninstall [--force] Remove system service + isolation
96
+
97
+ Agent commands (variant, connector, schedule, logs, history, channel) use
98
+ --agent <name> or VOLUTE_AGENT env var to identify the agent.`);
94
99
  if (command) {
95
100
  console.error(`
96
101
  Unknown command: ${command}`);