volute 0.25.0 → 0.26.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 (102) hide show
  1. package/README.md +15 -20
  2. package/dist/{activity-events-4O37J7PD.js → activity-events-ZMBAKLUF.js} +2 -2
  3. package/dist/api.d.ts +477 -6
  4. package/dist/{auth-HM2RSPY7.js → auth-4TV573WE.js} +2 -2
  5. package/dist/{channel-HZOSHGNF.js → channel-ZVZV42UD.js} +3 -3
  6. package/dist/{chunk-SHSWYG2J.js → chunk-2VO7453N.js} +56 -19
  7. package/dist/{chunk-PMX4EIJK.js → chunk-3CFRE2VC.js} +878 -741
  8. package/dist/{chunk-PHHKNGA3.js → chunk-3TV4GLFO.js} +2 -2
  9. package/dist/{chunk-BOTQ25QT.js → chunk-5Y3PBKW6.js} +2 -2
  10. package/dist/{chunk-BFK6SOEJ.js → chunk-J2CO4WEV.js} +1 -1
  11. package/dist/{chunk-ZSH4G2P5.js → chunk-LX22GRG7.js} +10 -13
  12. package/dist/{chunk-E7GOKNOT.js → chunk-NWI2425I.js} +1 -1
  13. package/dist/{chunk-2767L2RZ.js → chunk-OZFKBXD6.js} +1 -1
  14. package/dist/{chunk-RVKR2R7F.js → chunk-SSI47XP2.js} +10 -2
  15. package/dist/chunk-TZKJLDQN.js +78 -0
  16. package/dist/{chunk-DG7TO7EE.js → chunk-USNBKHYG.js} +3 -3
  17. package/dist/chunk-UTL75LP6.js +113 -0
  18. package/dist/{chunk-3AIBT4TW.js → chunk-V63B7DX3.js} +24 -1
  19. package/dist/{chunk-33XAVCS4.js → chunk-WBHMQ5OZ.js} +49 -0
  20. package/dist/{chunk-TRQEV3CD.js → chunk-WGOGUMPO.js} +22 -3
  21. package/dist/chunk-XOXLRRR2.js +176 -0
  22. package/dist/{chunk-JTDFJWI2.js → chunk-YJA7P64S.js} +1 -1
  23. package/dist/chunk-ZYGKG6VC.js +22 -0
  24. package/dist/cli.js +44 -20
  25. package/dist/{cloud-sync-PPBBJDY6.js → cloud-sync-NI2K3C7G.js} +11 -9
  26. package/dist/{connector-M6XFI6GM.js → connector-G722WXAU.js} +4 -4
  27. package/dist/{create-VDQJER52.js → create-4YBRTTJS.js} +1 -1
  28. package/dist/{daemon-client-JOVQZ52X.js → daemon-client-Z7FAJ6JW.js} +1 -1
  29. package/dist/{daemon-restart-FDNOZEAD.js → daemon-restart-BJZ3O4U4.js} +6 -5
  30. package/dist/daemon.js +693 -265
  31. package/dist/{delete-2MRR4JX5.js → delete-27OYNK25.js} +1 -1
  32. package/dist/{down-674SX2IZ.js → down-7UKFMJJZ.js} +4 -4
  33. package/dist/{env-2FPOZK37.js → env-M336ONDP.js} +4 -4
  34. package/dist/{export-IKFAPRAO.js → export-HP4G5DQC.js} +1 -1
  35. package/dist/{file-KT3UIQM3.js → file-HUDKTRAS.js} +3 -3
  36. package/dist/{history-46WZN5CN.js → history-B64GTFTD.js} +3 -3
  37. package/dist/{import-TH26J76F.js → import-XIB7UV4S.js} +1 -1
  38. package/dist/{log-6SGSSR3D.js → log-PBFNILJ4.js} +3 -3
  39. package/dist/{login-UO6AOVEA.js → login-6U7U6BNG.js} +1 -1
  40. package/dist/login-B5E7N7MY.js +46 -0
  41. package/dist/logout-XSJRYS3U.js +39 -0
  42. package/dist/{logs-HRBONI5I.js → logs-3CART7O7.js} +3 -3
  43. package/dist/{merge-KSFJKX6T.js → merge-VK2HSKMA.js} +3 -3
  44. package/dist/{message-delivery-XMGV3FUM.js → message-delivery-MS5JYPZX.js} +10 -8
  45. package/dist/{mind-YVWAHL2A.js → mind-HZ3QSDDJ.js} +17 -17
  46. package/dist/{mind-activity-tracker-NMDDEV3K.js → mind-activity-tracker-4G6FURY2.js} +3 -3
  47. package/dist/{mind-manager-4NDNAYAB.js → mind-manager-VVK67AY3.js} +6 -4
  48. package/dist/{mind-sleep-GHPTSAYN.js → mind-sleep-DTV7L44D.js} +3 -3
  49. package/dist/{mind-wake-BJDJFMDF.js → mind-wake-PFN4FN3T.js} +3 -3
  50. package/dist/notes-37FW2UR2.js +230 -0
  51. package/dist/{package-3HF5MXU2.js → package-VZWLXPHV.js} +2 -1
  52. package/dist/{pages-Y6DRWUOJ.js → pages-DIIT5HMQ.js} +1 -1
  53. package/dist/{publish-EEKTZBHW.js → publish-HQV7YREB.js} +3 -3
  54. package/dist/{pull-D32SPFVU.js → pull-2MB4SK3C.js} +3 -3
  55. package/dist/{register-U2UO6TC4.js → register-EFND67FQ.js} +1 -1
  56. package/dist/{restart-5BMNV7KU.js → restart-CCK7D6TV.js} +3 -3
  57. package/dist/sandbox-EHGFF52K.js +19 -0
  58. package/dist/{schedule-YEFDLVMJ.js → schedule-6F7ELB2M.js} +3 -3
  59. package/dist/{seed-6FEKB3YC.js → seed-E5OQGWX3.js} +1 -1
  60. package/dist/{send-IISDYFCL.js → send-IH6XZKPC.js} +6 -20
  61. package/dist/service-LLBV3R7M.js +122 -0
  62. package/dist/setup-F6TWFYGQ.js +371 -0
  63. package/dist/setup-YGAAIKKZ.js +17 -0
  64. package/dist/{shared-LWMNTTZN.js → shared-UMO4S7CC.js} +4 -4
  65. package/dist/{skill-T3EMR6IR.js → skill-42LGFBQC.js} +3 -3
  66. package/dist/skills/dreaming/SKILL.md +68 -0
  67. package/dist/skills/dreaming/references/INSTALL.md +56 -0
  68. package/dist/skills/dreaming/scripts/dream.ts +289 -0
  69. package/dist/skills/dreaming/scripts/wake-context-dreams.sh +30 -0
  70. package/dist/skills/notes/SKILL.md +34 -0
  71. package/dist/{sleep-manager-RKTFZPD3.js → sleep-manager-EE4NRN2Q.js} +10 -8
  72. package/dist/{sprout-QJVGJDSH.js → sprout-QL74KR2X.js} +5 -5
  73. package/dist/{start-C7XITZ5O.js → start-O5JQASRC.js} +3 -3
  74. package/dist/{status-SIRPLEZC.js → status-FZBEBM7Q.js} +3 -3
  75. package/dist/{status-LYS4NUOZ.js → status-WXD4HXRL.js} +3 -3
  76. package/dist/{stop-CVKBSLXY.js → stop-2SOG5NYF.js} +3 -3
  77. package/dist/up-SDMCSVI3.js +17 -0
  78. package/dist/{update-7XCZMYBT.js → update-5VUDAI3D.js} +6 -6
  79. package/dist/{upgrade-7RUIXGOO.js → upgrade-QCCO33BK.js} +1 -1
  80. package/dist/{variant-UGREB4G5.js → variant-WWLDY6D5.js} +4 -4
  81. package/dist/{version-notify-AZQMC32A.js → version-notify-USFZBWMG.js} +11 -9
  82. package/dist/web-assets/assets/index-CUQ31ieL.js +69 -0
  83. package/dist/web-assets/assets/index-CW8NSl1o.css +1 -0
  84. package/dist/web-assets/index.html +2 -2
  85. package/drizzle/0015_notes.sql +23 -0
  86. package/drizzle/0016_note_reactions_and_replies.sql +15 -0
  87. package/drizzle/meta/_journal.json +14 -0
  88. package/package.json +2 -1
  89. package/templates/_base/.init/.config/hooks/wake-context.sh +7 -0
  90. package/templates/_base/src/lib/startup.ts +8 -0
  91. package/templates/claude/src/agent.ts +51 -1
  92. package/templates/claude/src/server.ts +1 -0
  93. package/templates/pi/package.json.tmpl +1 -0
  94. package/templates/pi/src/agent.ts +48 -1
  95. package/templates/pi/src/lib/subagents.ts +150 -0
  96. package/templates/pi/src/server.ts +1 -0
  97. package/dist/chunk-NWPT4ASZ.js +0 -89
  98. package/dist/service-FASYWLTC.js +0 -247
  99. package/dist/setup-BMLM2UTK.js +0 -230
  100. package/dist/up-CJ26KQLN.js +0 -15
  101. package/dist/web-assets/assets/index-CGPSVu19.js +0 -69
  102. package/dist/web-assets/assets/index-V_rNDsM8.css +0 -1
@@ -4,11 +4,11 @@ import {
4
4
  } from "./chunk-YUIHSKR6.js";
5
5
  import {
6
6
  gitExec
7
- } from "./chunk-JTDFJWI2.js";
7
+ } from "./chunk-YJA7P64S.js";
8
8
  import {
9
9
  isIsolationEnabled,
10
10
  mindUserName
11
- } from "./chunk-NWPT4ASZ.js";
11
+ } from "./chunk-XOXLRRR2.js";
12
12
  import {
13
13
  voluteHome
14
14
  } from "./chunk-B2CPS4QU.js";
@@ -39,7 +39,7 @@ async function run(args) {
39
39
  return;
40
40
  }
41
41
  const wsDir = resolveWorkspace(inputPath);
42
- const { daemonFetch } = await import("./daemon-client-JOVQZ52X.js");
42
+ const { daemonFetch } = await import("./daemon-client-Z7FAJ6JW.js");
43
43
  const { getClient, urlOf } = await import("./api-client-YPKOZP2O.js");
44
44
  const client = getClient();
45
45
  const res = await daemonFetch(urlOf(client.api.minds.import.$url()), {
@@ -91,7 +91,7 @@ async function importArchive(archivePath, nameOverride) {
91
91
  process.exit(1);
92
92
  }
93
93
  try {
94
- const { daemonFetch } = await import("./daemon-client-JOVQZ52X.js");
94
+ const { daemonFetch } = await import("./daemon-client-Z7FAJ6JW.js");
95
95
  const { getClient, urlOf } = await import("./api-client-YPKOZP2O.js");
96
96
  const client = getClient();
97
97
  const res = await daemonFetch(urlOf(client.api.minds.import.$url()), {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  activity,
4
4
  getDb
5
- } from "./chunk-33XAVCS4.js";
5
+ } from "./chunk-WBHMQ5OZ.js";
6
6
  import {
7
7
  logger_default
8
8
  } from "./chunk-YUIHSKR6.js";
@@ -4,10 +4,13 @@ import {
4
4
  modeLabel,
5
5
  pollHealth,
6
6
  startService
7
- } from "./chunk-3AIBT4TW.js";
7
+ } from "./chunk-V63B7DX3.js";
8
8
  import {
9
9
  parseArgs
10
10
  } from "./chunk-D424ZQGI.js";
11
+ import {
12
+ readGlobalConfig
13
+ } from "./chunk-TZKJLDQN.js";
11
14
  import {
12
15
  voluteHome
13
16
  } from "./chunk-B2CPS4QU.js";
@@ -16,22 +19,13 @@ import {
16
19
  import { spawn } from "child_process";
17
20
  import { existsSync, mkdirSync, openSync, readFileSync } from "fs";
18
21
  import { dirname, resolve } from "path";
19
- function readGlobalConfig() {
20
- const configPath = resolve(voluteHome(), "config.json");
21
- if (!existsSync(configPath)) return {};
22
- try {
23
- return JSON.parse(readFileSync(configPath, "utf-8"));
24
- } catch (err) {
25
- console.error(`Invalid config file ${configPath}: ${err instanceof Error ? err.message : err}`);
26
- process.exit(1);
27
- }
28
- }
29
22
  async function run(args) {
30
23
  const { flags } = parseArgs(args, {
31
24
  port: { type: "number" },
32
25
  host: { type: "string" },
33
26
  foreground: { type: "boolean" },
34
- tailscale: { type: "boolean" }
27
+ tailscale: { type: "boolean" },
28
+ "no-sandbox": { type: "boolean" }
35
29
  });
36
30
  const mode = getServiceMode();
37
31
  if (!flags.foreground && mode !== "manual") {
@@ -95,6 +89,9 @@ async function run(args) {
95
89
  }
96
90
  } catch {
97
91
  }
92
+ if (flags["no-sandbox"]) {
93
+ process.env.VOLUTE_SANDBOX = "0";
94
+ }
98
95
  if (flags.foreground) {
99
96
  const { startDaemon } = await import("./daemon.js");
100
97
  await startDaemon({ port, hostname, foreground: true, tailscale: flags.tailscale });
@@ -110,6 +107,7 @@ async function run(args) {
110
107
  const logFd = openSync(logFile, "a");
111
108
  const daemonArgs = [daemonModule, "--port", String(port), "--host", hostname];
112
109
  if (flags.tailscale) daemonArgs.push("--tailscale");
110
+ if (flags["no-sandbox"]) daemonArgs.push("--no-sandbox");
113
111
  const child = spawn(process.execPath, daemonArgs, {
114
112
  stdio: ["ignore", "ignore", logFd],
115
113
  detached: true
@@ -151,6 +149,5 @@ async function run(args) {
151
149
  }
152
150
 
153
151
  export {
154
- readGlobalConfig,
155
152
  run
156
153
  };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  publish
4
- } from "./chunk-BFK6SOEJ.js";
4
+ } from "./chunk-J2CO4WEV.js";
5
5
  import {
6
6
  logger_default
7
7
  } from "./chunk-YUIHSKR6.js";
@@ -5,7 +5,7 @@ import {
5
5
  pollHealthDown,
6
6
  readDaemonConfig,
7
7
  stopService
8
- } from "./chunk-3AIBT4TW.js";
8
+ } from "./chunk-V63B7DX3.js";
9
9
  import {
10
10
  voluteHome
11
11
  } from "./chunk-B2CPS4QU.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/lib/prompt.ts
4
- function promptLine(prompt) {
4
+ function rawPrompt(prompt, echo) {
5
5
  process.stderr.write(prompt);
6
6
  return new Promise((resolve) => {
7
7
  let value = "";
@@ -23,6 +23,7 @@ function promptLine(prompt) {
23
23
  value = value.slice(0, -1);
24
24
  } else {
25
25
  value += String.fromCharCode(byte);
26
+ if (echo) process.stderr.write(String.fromCharCode(byte));
26
27
  }
27
28
  }
28
29
  };
@@ -31,7 +32,14 @@ function promptLine(prompt) {
31
32
  process.stdin.on("data", onData);
32
33
  });
33
34
  }
35
+ function promptLine(prompt) {
36
+ return rawPrompt(prompt, true);
37
+ }
38
+ function promptPassword(prompt) {
39
+ return rawPrompt(prompt, false);
40
+ }
34
41
 
35
42
  export {
36
- promptLine
43
+ promptLine,
44
+ promptPassword
37
45
  };
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ voluteHome
4
+ } from "./chunk-B2CPS4QU.js";
5
+
6
+ // src/lib/setup.ts
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
+ import { resolve } from "path";
9
+ function configPath() {
10
+ return resolve(voluteHome(), "config.json");
11
+ }
12
+ function readGlobalConfig() {
13
+ const path = configPath();
14
+ if (!existsSync(path)) return {};
15
+ try {
16
+ return JSON.parse(readFileSync(path, "utf-8"));
17
+ } catch (err) {
18
+ console.error(`Failed to parse ${path}: ${err instanceof Error ? err.message : err}`);
19
+ return {};
20
+ }
21
+ }
22
+ function writeGlobalConfig(config) {
23
+ const path = configPath();
24
+ mkdirSync(voluteHome(), { recursive: true });
25
+ writeFileSync(path, JSON.stringify(config, null, 2) + "\n");
26
+ }
27
+ function isSetupComplete() {
28
+ const config = readGlobalConfig();
29
+ return config.setup != null;
30
+ }
31
+ function migrateSetupConfig() {
32
+ const config = readGlobalConfig();
33
+ if (config.setup) return;
34
+ const home = voluteHome();
35
+ const registryPath = resolve(home, "minds.json");
36
+ if (!existsSync(registryPath)) return;
37
+ const isSystem = process.env.VOLUTE_ISOLATION === "user";
38
+ const mindsDir = process.env.VOLUTE_MINDS_DIR || resolve(home, "minds");
39
+ let hasService = false;
40
+ try {
41
+ if (process.platform === "darwin") {
42
+ const plistPath = resolve(
43
+ process.env.HOME || "",
44
+ "Library",
45
+ "LaunchAgents",
46
+ "com.volute.daemon.plist"
47
+ );
48
+ if (existsSync(plistPath)) hasService = true;
49
+ }
50
+ if (process.platform === "linux") {
51
+ if (existsSync("/etc/systemd/system/volute.service")) hasService = true;
52
+ const userUnit = resolve(
53
+ process.env.HOME || "",
54
+ ".config",
55
+ "systemd",
56
+ "user",
57
+ "volute.service"
58
+ );
59
+ if (existsSync(userUnit)) hasService = true;
60
+ }
61
+ } catch {
62
+ }
63
+ const setup = {
64
+ type: isSystem ? "system" : "local",
65
+ isolation: isSystem ? "user" : "none",
66
+ mindsDir,
67
+ service: hasService
68
+ };
69
+ writeGlobalConfig({ ...config, setup });
70
+ }
71
+
72
+ export {
73
+ configPath,
74
+ readGlobalConfig,
75
+ writeGlobalConfig,
76
+ isSetupComplete,
77
+ migrateSetupConfig
78
+ };
@@ -2,14 +2,14 @@
2
2
  import {
3
3
  getDb,
4
4
  sharedSkills
5
- } from "./chunk-33XAVCS4.js";
5
+ } from "./chunk-WBHMQ5OZ.js";
6
6
  import {
7
7
  logger_default
8
8
  } from "./chunk-YUIHSKR6.js";
9
9
  import {
10
10
  exec,
11
11
  gitExec
12
- } from "./chunk-JTDFJWI2.js";
12
+ } from "./chunk-YJA7P64S.js";
13
13
  import {
14
14
  voluteHome
15
15
  } from "./chunk-B2CPS4QU.js";
@@ -30,7 +30,7 @@ import { basename, dirname, join, resolve } from "path";
30
30
  import { eq, sql } from "drizzle-orm";
31
31
  var VALID_SKILL_ID = /^[a-zA-Z0-9_-]+$/;
32
32
  var SEED_SKILLS = ["orientation", "memory"];
33
- var STANDARD_SKILLS = ["volute-mind", "memory", "sessions"];
33
+ var STANDARD_SKILLS = ["volute-mind", "memory", "sessions", "notes", "dreaming"];
34
34
  function validateSkillId(id) {
35
35
  if (!id || !VALID_SKILL_ID.test(id)) {
36
36
  throw new Error(`Invalid skill ID: ${id}`);
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ logger_default
4
+ } from "./chunk-YUIHSKR6.js";
5
+ import {
6
+ readGlobalConfig
7
+ } from "./chunk-TZKJLDQN.js";
8
+ import {
9
+ voluteHome
10
+ } from "./chunk-B2CPS4QU.js";
11
+
12
+ // src/lib/sandbox.ts
13
+ import { existsSync, readFileSync } from "fs";
14
+ import { resolve } from "path";
15
+ var slog = logger_default.child("sandbox");
16
+ var sandboxManager = null;
17
+ function isSandboxEnabled() {
18
+ if (process.env.VOLUTE_SANDBOX === "0") return false;
19
+ return readGlobalConfig().setup?.isolation === "sandbox";
20
+ }
21
+ async function initSandbox() {
22
+ if (!isSandboxEnabled()) return;
23
+ try {
24
+ const { SandboxManager } = await import("@anthropic-ai/sandbox-runtime");
25
+ const config = {
26
+ network: {
27
+ allowedDomains: ["*"],
28
+ deniedDomains: [],
29
+ allowLocalBinding: true
30
+ },
31
+ filesystem: {
32
+ denyRead: [],
33
+ allowWrite: [],
34
+ denyWrite: []
35
+ }
36
+ };
37
+ await SandboxManager.initialize(config);
38
+ sandboxManager = SandboxManager;
39
+ } catch (err) {
40
+ slog.error(
41
+ "sandbox runtime not available \u2014 minds will run without sandbox isolation",
42
+ logger_default.errorData(err)
43
+ );
44
+ }
45
+ }
46
+ function buildDenyRead(mindName, mindDir) {
47
+ const home = voluteHome();
48
+ const userHome = process.env.HOME || "";
49
+ const mindsDir = process.env.VOLUTE_MINDS_DIR || resolve(home, "minds");
50
+ const deny = [];
51
+ deny.push(resolve(home, "state"));
52
+ deny.push(resolve(home, "volute.db"));
53
+ deny.push(resolve(home, "env.json"));
54
+ deny.push(resolve(home, "config.json"));
55
+ deny.push(resolve(home, "daemon.json"));
56
+ deny.push(resolve(home, "minds.json"));
57
+ deny.push(resolve(home, "systems.json"));
58
+ try {
59
+ const registryPath = resolve(home, "minds.json");
60
+ if (existsSync(registryPath)) {
61
+ const registry = JSON.parse(readFileSync(registryPath, "utf-8"));
62
+ for (const entry of registry) {
63
+ if (entry.name === mindName.split("@")[0]) continue;
64
+ const otherDir = resolve(mindsDir, entry.name);
65
+ if (otherDir !== mindDir) {
66
+ deny.push(otherDir);
67
+ }
68
+ }
69
+ }
70
+ } catch (err) {
71
+ slog.warn("failed to read minds registry for deny-read list", logger_default.errorData(err));
72
+ }
73
+ if (userHome) {
74
+ deny.push(resolve(userHome, ".ssh"));
75
+ deny.push(resolve(userHome, ".aws"));
76
+ deny.push(resolve(userHome, ".gnupg"));
77
+ deny.push(resolve(userHome, ".config"));
78
+ }
79
+ return deny;
80
+ }
81
+ function shellEscape(s) {
82
+ return `'${s.replace(/'/g, "'\\''")}'`;
83
+ }
84
+ async function wrapForSandbox(cmd, args, mindDir, mindName, allowWrite) {
85
+ if (!sandboxManager) return [cmd, args];
86
+ const denyRead = buildDenyRead(mindName, mindDir);
87
+ const customConfig = {
88
+ filesystem: {
89
+ denyRead,
90
+ allowWrite: allowWrite ?? [mindDir],
91
+ denyWrite: []
92
+ }
93
+ };
94
+ try {
95
+ const shellCmd = [cmd, ...args].map(shellEscape).join(" ");
96
+ const wrapped = await sandboxManager.wrapWithSandbox(shellCmd, void 0, customConfig);
97
+ return ["bash", ["-c", wrapped]];
98
+ } catch (err) {
99
+ slog.error(
100
+ `failed to sandbox mind ${mindName} \u2014 running without isolation`,
101
+ logger_default.errorData(err)
102
+ );
103
+ return [cmd, args];
104
+ }
105
+ }
106
+
107
+ export {
108
+ isSandboxEnabled,
109
+ initSandbox,
110
+ buildDenyRead,
111
+ shellEscape,
112
+ wrapForSandbox
113
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  execInherit
4
- } from "./chunk-JTDFJWI2.js";
4
+ } from "./chunk-YJA7P64S.js";
5
5
  import {
6
6
  voluteHome
7
7
  } from "./chunk-B2CPS4QU.js";
@@ -20,6 +20,7 @@ var LAUNCHD_PLIST_PATH = resolve(
20
20
  "LaunchAgents",
21
21
  `${LAUNCHD_PLIST_LABEL}.plist`
22
22
  );
23
+ var SYSTEM_LAUNCHD_PLIST_PATH = "/Library/LaunchDaemons/com.volute.daemon.plist";
23
24
  var HEALTH_POLL_TIMEOUT = 3e4;
24
25
  var STOP_GRACE_TIMEOUT = 1e4;
25
26
  var POLL_INTERVAL = 500;
@@ -38,6 +39,9 @@ function getServiceMode() {
38
39
  } catch {
39
40
  }
40
41
  }
42
+ if (process.platform === "darwin" && existsSync(SYSTEM_LAUNCHD_PLIST_PATH)) {
43
+ return "system-launchd";
44
+ }
41
45
  if (process.platform === "darwin" && existsSync(LAUNCHD_PLIST_PATH)) {
42
46
  return "user-launchd";
43
47
  }
@@ -92,6 +96,9 @@ async function startService(mode) {
92
96
  case "user-systemd":
93
97
  await execInherit("systemctl", ["--user", "start", "volute"]);
94
98
  break;
99
+ case "system-launchd":
100
+ await execInherit("sudo", ["launchctl", "load", SYSTEM_LAUNCHD_PLIST_PATH]);
101
+ break;
95
102
  case "user-launchd":
96
103
  await execInherit("launchctl", ["load", LAUNCHD_PLIST_PATH]);
97
104
  break;
@@ -105,6 +112,9 @@ async function stopService(mode) {
105
112
  case "user-systemd":
106
113
  await execInherit("systemctl", ["--user", "stop", "volute"]);
107
114
  break;
115
+ case "system-launchd":
116
+ await execInherit("sudo", ["launchctl", "unload", SYSTEM_LAUNCHD_PLIST_PATH]);
117
+ break;
108
118
  case "user-launchd":
109
119
  await execInherit("launchctl", ["unload", LAUNCHD_PLIST_PATH]);
110
120
  break;
@@ -118,6 +128,16 @@ async function restartService(mode) {
118
128
  case "user-systemd":
119
129
  await execInherit("systemctl", ["--user", "restart", "volute"]);
120
130
  break;
131
+ case "system-launchd":
132
+ try {
133
+ await execInherit("sudo", ["launchctl", "unload", SYSTEM_LAUNCHD_PLIST_PATH]);
134
+ } catch (err) {
135
+ console.warn(
136
+ `Warning: launchctl unload failed: ${err instanceof Error ? err.message : err}`
137
+ );
138
+ }
139
+ await execInherit("sudo", ["launchctl", "load", SYSTEM_LAUNCHD_PLIST_PATH]);
140
+ break;
121
141
  case "user-launchd":
122
142
  try {
123
143
  await execInherit("launchctl", ["unload", LAUNCHD_PLIST_PATH]);
@@ -152,6 +172,8 @@ function modeLabel(mode) {
152
172
  return "system service (systemd)";
153
173
  case "user-systemd":
154
174
  return "user service (systemd)";
175
+ case "system-launchd":
176
+ return "system service (launchd)";
155
177
  case "user-launchd":
156
178
  return "user service (launchd)";
157
179
  case "manual":
@@ -164,6 +186,7 @@ export {
164
186
  USER_SYSTEMD_UNIT,
165
187
  LAUNCHD_PLIST_LABEL,
166
188
  LAUNCHD_PLIST_PATH,
189
+ SYSTEM_LAUNCHD_PLIST_PATH,
167
190
  getServiceMode,
168
191
  getDaemonUrl,
169
192
  pollHealth,
@@ -16,6 +16,9 @@ __export(schema_exports, {
16
16
  deliveryQueue: () => deliveryQueue,
17
17
  messages: () => messages,
18
18
  mindHistory: () => mindHistory,
19
+ noteComments: () => noteComments,
20
+ noteReactions: () => noteReactions,
21
+ notes: () => notes,
19
22
  sessions: () => sessions,
20
23
  sharedSkills: () => sharedSkills,
21
24
  systemPrompts: () => systemPrompts,
@@ -149,6 +152,49 @@ var conversationReads = sqliteTable(
149
152
  uniqueIndex("idx_conversation_reads_unique").on(table.user_id, table.conversation_id)
150
153
  ]
151
154
  );
155
+ var notes = sqliteTable(
156
+ "notes",
157
+ {
158
+ id: integer("id").primaryKey({ autoIncrement: true }),
159
+ author_id: integer("author_id").notNull().references(() => users.id, { onDelete: "cascade" }),
160
+ title: text("title").notNull(),
161
+ slug: text("slug").notNull(),
162
+ content: text("content").notNull(),
163
+ reply_to_id: integer("reply_to_id"),
164
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`),
165
+ updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
166
+ },
167
+ (table) => [
168
+ uniqueIndex("idx_notes_author_slug").on(table.author_id, table.slug),
169
+ index("idx_notes_created_at").on(table.created_at),
170
+ index("idx_notes_reply_to").on(table.reply_to_id)
171
+ ]
172
+ );
173
+ var noteComments = sqliteTable(
174
+ "note_comments",
175
+ {
176
+ id: integer("id").primaryKey({ autoIncrement: true }),
177
+ note_id: integer("note_id").notNull().references(() => notes.id, { onDelete: "cascade" }),
178
+ author_id: integer("author_id").notNull().references(() => users.id, { onDelete: "cascade" }),
179
+ content: text("content").notNull(),
180
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
181
+ },
182
+ (table) => [index("idx_note_comments_note_id").on(table.note_id)]
183
+ );
184
+ var noteReactions = sqliteTable(
185
+ "note_reactions",
186
+ {
187
+ id: integer("id").primaryKey({ autoIncrement: true }),
188
+ note_id: integer("note_id").notNull().references(() => notes.id, { onDelete: "cascade" }),
189
+ user_id: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
190
+ emoji: text("emoji").notNull(),
191
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
192
+ },
193
+ (table) => [
194
+ uniqueIndex("idx_note_reactions_unique").on(table.note_id, table.user_id, table.emoji),
195
+ index("idx_note_reactions_note_id").on(table.note_id)
196
+ ]
197
+ );
152
198
  var messages = sqliteTable(
153
199
  "messages",
154
200
  {
@@ -198,6 +244,9 @@ export {
198
244
  deliveryQueue,
199
245
  activity,
200
246
  conversationReads,
247
+ notes,
248
+ noteComments,
249
+ noteReactions,
201
250
  messages,
202
251
  getDb
203
252
  };
@@ -6,6 +6,15 @@ import {
6
6
  // src/lib/daemon-client.ts
7
7
  import { existsSync, readFileSync } from "fs";
8
8
  import { resolve } from "path";
9
+ function readCliSession() {
10
+ const sessionPath = resolve(voluteHome(), "cli-session.json");
11
+ if (!existsSync(sessionPath)) return null;
12
+ try {
13
+ return JSON.parse(readFileSync(sessionPath, "utf-8"));
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
9
18
  function readDaemonConfig() {
10
19
  const configPath = resolve(voluteHome(), "daemon.json");
11
20
  if (!existsSync(configPath)) {
@@ -47,12 +56,22 @@ async function daemonFetch(path, options) {
47
56
  const config = readDaemonConfig();
48
57
  const url = buildUrl(config);
49
58
  const headers = new Headers(options?.headers);
50
- if (config.token) {
51
- headers.set("Authorization", `Bearer ${config.token}`);
59
+ const cliSession = readCliSession();
60
+ if (cliSession?.sessionId) {
61
+ headers.set("Authorization", `Bearer ${cliSession.sessionId}`);
52
62
  }
53
63
  headers.set("Origin", url);
54
64
  try {
55
- return await fetch(`${url}${path}`, { ...options, headers });
65
+ const res = await fetch(`${url}${path}`, { ...options, headers });
66
+ if (res.status === 401 && !path.startsWith("/api/auth/")) {
67
+ if (cliSession) {
68
+ console.error("Session expired. Run `volute login` again.");
69
+ } else {
70
+ console.error("Not logged in. Run `volute login` first.");
71
+ }
72
+ process.exit(1);
73
+ }
74
+ return res;
56
75
  } catch (err) {
57
76
  if (err instanceof TypeError && err.cause?.code === "ECONNREFUSED") {
58
77
  console.error("Volute is not running. Start with: volute up");