volute 0.8.0 → 0.8.2

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.
package/README.md CHANGED
@@ -242,9 +242,11 @@ Or manually:
242
242
 
243
243
  ```sh
244
244
  npm install -g volute
245
- sudo volute setup --host 0.0.0.0
245
+ sudo $(which volute) setup --host 0.0.0.0
246
246
  ```
247
247
 
248
+ > **Note:** The initial `sudo $(which volute)` is needed because `sudo` resets PATH. After setup completes, a wrapper at `/usr/local/bin/volute` is created so `sudo volute` works normally going forward.
249
+
248
250
  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
251
 
250
252
  ### Auto-start (user-level)
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ 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-5UCKNK6J.js");
12
+ const { default: pkg } = await import("./package-TWWKVRRL.js");
13
13
  console.log(pkg.version);
14
14
  process.exit(0);
15
15
  }
@@ -48,10 +48,10 @@ switch (command) {
48
48
  await import("./daemon-restart-CPBLMMRI.js").then((m) => m.run(args));
49
49
  break;
50
50
  case "setup":
51
- await import("./setup-JXDCJX7W.js").then((m) => m.run(args));
51
+ await import("./setup-2JDBGU7Y.js").then((m) => m.run(args));
52
52
  break;
53
53
  case "service":
54
- await import("./service-R4MCNBOA.js").then((m) => m.run(args));
54
+ await import("./service-XCADRKIS.js").then((m) => m.run(args));
55
55
  break;
56
56
  case "update":
57
57
  await import("./update-EUCZ7XGG.js").then((m) => m.run(args));
@@ -4,7 +4,7 @@ import "./chunk-K3NQKI34.js";
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "volute",
7
- version: "0.8.0",
7
+ version: "0.8.2",
8
8
  description: "CLI for creating and managing self-modifying AI agents powered by the Claude Agent SDK",
9
9
  type: "module",
10
10
  license: "MIT",
@@ -82,6 +82,13 @@ async function install(port, host) {
82
82
  await execFileAsync("launchctl", ["load", path]);
83
83
  console.log("Service installed and loaded. Volute daemon will start on login.");
84
84
  } else if (platform === "linux") {
85
+ if (process.getuid?.() === 0) {
86
+ console.error(
87
+ "Error: `volute service install` uses systemd user services, which don't work as root."
88
+ );
89
+ console.error("Use `volute setup` instead to install a system-level service.");
90
+ process.exit(1);
91
+ }
85
92
  const path = unitPath();
86
93
  mkdirSync(resolve(homedir(), ".config", "systemd", "user"), { recursive: true });
87
94
  writeFileSync(path, generateUnit(voluteBin, port, host));
@@ -14,8 +14,12 @@ import "./chunk-K3NQKI34.js";
14
14
  // src/commands/setup.ts
15
15
  import { execFileSync } from "child_process";
16
16
  import { existsSync, mkdirSync, rmSync, unlinkSync, writeFileSync } from "fs";
17
+ import { homedir } from "os";
18
+ import { dirname, resolve } from "path";
17
19
  var SERVICE_NAME = "volute.service";
18
20
  var SERVICE_PATH = `/etc/systemd/system/${SERVICE_NAME}`;
21
+ var PROFILE_PATH = "/etc/profile.d/volute.sh";
22
+ var WRAPPER_PATH = "/usr/local/bin/volute";
19
23
  var DATA_DIR = "/var/lib/volute";
20
24
  var AGENTS_DIR = "/agents";
21
25
  var HOST_RE = /^[a-zA-Z0-9.:_-]+$/;
@@ -24,31 +28,51 @@ function validateHost(host) {
24
28
  throw new Error(`Invalid host: ${host}`);
25
29
  }
26
30
  }
31
+ function buildServicePath(voluteBin) {
32
+ const binDir = dirname(voluteBin);
33
+ const standardPaths = [
34
+ "/usr/local/sbin",
35
+ "/usr/local/bin",
36
+ "/usr/sbin",
37
+ "/usr/bin",
38
+ "/sbin",
39
+ "/bin"
40
+ ];
41
+ const parts = standardPaths.includes(binDir) ? standardPaths : [binDir, ...standardPaths];
42
+ return parts.join(":");
43
+ }
27
44
  function generateUnit(voluteBin, port, host) {
28
45
  const args = ["up", "--foreground"];
29
46
  if (port != null) args.push("--port", String(port));
30
47
  if (host) args.push("--host", host);
31
- return `[Unit]
32
- Description=Volute Agent Manager
33
- After=network.target
34
-
35
- [Service]
36
- Type=exec
37
- ExecStart=${voluteBin} ${args.join(" ")}
38
- Environment=VOLUTE_HOME=${DATA_DIR}
39
- Environment=VOLUTE_AGENTS_DIR=${AGENTS_DIR}
40
- Environment=VOLUTE_ISOLATION=user
41
- Restart=on-failure
42
- RestartSec=5
43
- ProtectSystem=strict
44
- ReadWritePaths=${DATA_DIR} ${AGENTS_DIR}
45
- PrivateTmp=yes
46
- ProtectHome=yes
47
- RestrictSUIDSGID=yes
48
-
49
- [Install]
50
- WantedBy=multi-user.target
51
- `;
48
+ const home = homedir();
49
+ const binUnderHome = voluteBin.startsWith(`${home}/`);
50
+ const lines = [
51
+ "[Unit]",
52
+ "Description=Volute Agent Manager",
53
+ "After=network.target",
54
+ "",
55
+ "[Service]",
56
+ "Type=exec",
57
+ `ExecStart=${voluteBin} ${args.join(" ")}`,
58
+ `Environment=PATH=${buildServicePath(voluteBin)}`,
59
+ `Environment=VOLUTE_HOME=${DATA_DIR}`,
60
+ `Environment=VOLUTE_AGENTS_DIR=${AGENTS_DIR}`,
61
+ "Environment=VOLUTE_ISOLATION=user",
62
+ "Restart=on-failure",
63
+ "RestartSec=5",
64
+ "ProtectSystem=strict",
65
+ `ReadWritePaths=${DATA_DIR} ${AGENTS_DIR}`,
66
+ "PrivateTmp=yes"
67
+ ];
68
+ if (!binUnderHome) {
69
+ lines.push("ProtectHome=yes");
70
+ } else {
71
+ console.warn(`Warning: ProtectHome=yes omitted because volute binary is under ${home}.`);
72
+ console.warn("Consider installing Node.js system-wide for stronger sandboxing.");
73
+ }
74
+ lines.push("RestrictSUIDSGID=yes", "", "[Install]", "WantedBy=multi-user.target", "");
75
+ return lines.join("\n");
52
76
  }
53
77
  function install(port, host) {
54
78
  if (host) validateHost(host);
@@ -71,14 +95,48 @@ function install(port, host) {
71
95
  execFileSync("chmod", ["755", DATA_DIR]);
72
96
  execFileSync("chmod", ["755", AGENTS_DIR]);
73
97
  console.log("Set permissions on directories");
98
+ writeFileSync(
99
+ PROFILE_PATH,
100
+ `export VOLUTE_HOME=${DATA_DIR}
101
+ export VOLUTE_AGENTS_DIR=${AGENTS_DIR}
102
+ `
103
+ );
104
+ console.log(`Wrote ${PROFILE_PATH}`);
105
+ const binDir = dirname(voluteBin);
106
+ if (voluteBin !== WRAPPER_PATH && !voluteBin.startsWith("/usr/bin")) {
107
+ const nodeBin = resolve(binDir, "node");
108
+ const wrapper = `#!/bin/sh
109
+ exec "${nodeBin}" "${voluteBin}" "$@"
110
+ `;
111
+ writeFileSync(WRAPPER_PATH, wrapper, { mode: 493 });
112
+ console.log(`Wrote ${WRAPPER_PATH} (wrapper for ${voluteBin})`);
113
+ }
74
114
  writeFileSync(SERVICE_PATH, generateUnit(voluteBin, port, host ?? "0.0.0.0"));
75
115
  console.log(`Wrote ${SERVICE_PATH}`);
76
- execFileSync("systemctl", ["daemon-reload"]);
77
- execFileSync("systemctl", ["enable", "--now", SERVICE_NAME]);
78
- console.log("Service installed, enabled, and started.");
79
- console.log(`
116
+ try {
117
+ execFileSync("systemctl", ["daemon-reload"]);
118
+ } catch (err) {
119
+ const e = err;
120
+ console.error(`Failed to reload systemd after writing ${SERVICE_PATH}.`);
121
+ if (e.stderr) console.error(e.stderr.toString().trim());
122
+ console.error(
123
+ "Try running `systemctl daemon-reload` manually, then `systemctl enable --now volute`."
124
+ );
125
+ process.exit(1);
126
+ }
127
+ try {
128
+ execFileSync("systemctl", ["enable", "--now", SERVICE_NAME]);
129
+ console.log("Service installed, enabled, and started.");
130
+ console.log(`
80
131
  Volute daemon is running. Data directory: ${DATA_DIR}`);
81
- console.log("Use `systemctl status volute` to check status.");
132
+ console.log("Use `systemctl status volute` to check status.");
133
+ } catch (err) {
134
+ const e = err;
135
+ console.error("Service installed but failed to start.");
136
+ if (e.stderr) console.error(e.stderr.toString().trim());
137
+ console.error("Check `journalctl -xeu volute.service` for details.");
138
+ process.exit(1);
139
+ }
82
140
  }
83
141
  function uninstall(force) {
84
142
  if (process.getuid?.() !== 0) {
@@ -95,6 +153,8 @@ function uninstall(force) {
95
153
  console.warn("Warning: failed to disable service (may already be stopped)");
96
154
  }
97
155
  unlinkSync(SERVICE_PATH);
156
+ if (existsSync(PROFILE_PATH)) unlinkSync(PROFILE_PATH);
157
+ if (existsSync(WRAPPER_PATH)) unlinkSync(WRAPPER_PATH);
98
158
  try {
99
159
  execFileSync("systemctl", ["daemon-reload"]);
100
160
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volute",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "CLI for creating and managing self-modifying AI agents powered by the Claude Agent SDK",
5
5
  "type": "module",
6
6
  "license": "MIT",