volute 0.18.0 → 0.19.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.
- package/README.md +1 -1
- package/dist/archive-ZCFOSTKB.js +15 -0
- package/dist/{channel-SLURLIRV.js → channel-PUQKGSQM.js} +60 -7
- package/dist/{chunk-AYB7XAWO.js → chunk-2TJGRJ4O.js} +114 -279
- package/dist/{chunk-6BDNWYKG.js → chunk-32VR2EOH.js} +2 -2
- package/dist/chunk-4KPUF5JD.js +214 -0
- package/dist/{chunk-QJIIHU32.js → chunk-7NO7EV5Z.js} +2 -2
- package/dist/chunk-AW7P4EVV.js +159 -0
- package/dist/{chunk-2Y77MCFG.js → chunk-DYZGP3EW.js} +2 -2
- package/dist/{chunk-M77QBTEH.js → chunk-EBGCNDMM.js} +24 -14
- package/dist/{chunk-GSPWIM5E.js → chunk-EMQSAY3B.js} +77 -6
- package/dist/{chunk-37X7ECMF.js → chunk-FCDU5BFX.js} +1 -1
- package/dist/chunk-FGV2H4TX.js +803 -0
- package/dist/{chunk-ZCEYUUID.js → chunk-OGXOMR65.js} +2 -1
- package/dist/chunk-OTWLI7F4.js +375 -0
- package/dist/{chunk-GK4E7LM7.js → chunk-RHEGSQFJ.js} +1 -1
- package/dist/{chunk-MVSXRMJJ.js → chunk-SCUDS4US.js} +1 -1
- package/dist/{chunk-FW5API7X.js → chunk-UJ6GHNR7.js} +2 -2
- package/dist/{chunk-OYSZNX5I.js → chunk-VDWCHYTS.js} +1 -1
- package/dist/{chunk-6DVBMLVN.js → chunk-VE4D3GOP.js} +2 -2
- package/dist/chunk-VQWDC6UK.js +142 -0
- package/dist/{chunk-OJQ47SCA.js → chunk-WC6ZHVRL.js} +1 -1
- package/dist/chunk-YUIHSKR6.js +72 -0
- package/dist/chunk-Z524RFCJ.js +36 -0
- package/dist/cli.js +33 -25
- package/dist/{connector-3ELFMI2R.js → connector-JBVNZ7VK.js} +6 -6
- package/dist/connectors/discord.js +2 -2
- package/dist/connectors/slack.js +2 -2
- package/dist/connectors/telegram.js +2 -2
- package/dist/{create-ZWHCRT5F.js → create-HP4OVVHF.js} +6 -4
- package/dist/{daemon-client-ODKDUYDE.js → daemon-client-ITWUCNFO.js} +2 -2
- package/dist/{daemon-restart-2HVTHZAT.js → daemon-restart-JMZM3QY4.js} +8 -8
- package/dist/daemon.js +1144 -1108
- package/dist/db-5ZVC6MQF.js +10 -0
- package/dist/{delete-6G6WEX4F.js → delete-BSU7K3RY.js} +1 -1
- package/dist/delivery-manager-ISTJMZDW.js +16 -0
- package/dist/down-ZY35KMHR.js +14 -0
- package/dist/{env-6IDWGBUH.js → env-A3LMO777.js} +6 -6
- package/dist/export-GCDNQCF3.js +100 -0
- package/dist/{history-YUEKTJ2N.js → history-WNK3DFUM.js} +6 -6
- package/dist/{import-EDGRLIGO.js → import-M63VIUJ5.js} +3 -3
- package/dist/log-PPPZDVEF.js +39 -0
- package/dist/{login-ORQDXLBM.js → login-HNH3EUQV.js} +2 -2
- package/dist/{logout-XC5AUO5I.js → logout-I5CB5UZS.js} +2 -2
- package/dist/{logs-GYOR3L2L.js → logs-SF2IMJN4.js} +6 -6
- package/dist/merge-33C237A4.js +46 -0
- package/dist/{mind-OJN6RBZW.js → mind-PQ5NCPSU.js} +14 -10
- package/dist/mind-manager-RVCFROAY.js +18 -0
- package/dist/{package-OKLFO7UY.js → package-MYE2ZJLV.js} +5 -3
- package/dist/{pages-6IV4VQTU.js → pages-AXCOSY3P.js} +2 -2
- package/dist/{publish-Q4RPSJLL.js → publish-YB377JB7.js} +18 -4
- package/dist/pull-XAEWQJ47.js +39 -0
- package/dist/{register-LDE6LRXY.js → register-VSPCMHKX.js} +2 -2
- package/dist/{restart-YFAWFS5T.js → restart-IQKMCK5M.js} +6 -6
- package/dist/{schedule-AGYLDMNS.js → schedule-LMX7GAQZ.js} +6 -6
- package/dist/schema-5BW7DFZI.js +24 -0
- package/dist/{seed-AP4Q7RZ7.js → seed-J43YDKXG.js} +7 -4
- package/dist/{send-BNDTLUPM.js → send-KVIZIGCE.js} +8 -8
- package/dist/{service-U7MZ2H7F.js → service-LUR7WDO7.js} +6 -6
- package/dist/{setup-DJKIZKGW.js → setup-OH3PJUJO.js} +7 -7
- package/dist/shared-KO35ZM44.js +39 -0
- package/dist/{skill-2Y42P4JY.js → skill-BCVNI6TV.js} +6 -6
- package/{templates/_base/_skills → dist/skills}/orientation/SKILL.md +1 -1
- package/{templates/_base/_skills → dist/skills}/sessions/SKILL.md +2 -2
- package/{templates/_base/_skills → dist/skills}/volute-mind/SKILL.md +19 -1
- package/dist/{sprout-TJ3BHVOG.js → sprout-VBEX63LX.js} +38 -20
- package/dist/{start-3YYRXBKP.js → start-I5JYB65M.js} +6 -6
- package/dist/{status-VSFZYX7S.js → status-4ESFLGH4.js} +5 -5
- package/dist/status-D7E5HHBV.js +35 -0
- package/dist/{status-OKNA6AR3.js → status-JCJAOXTW.js} +2 -2
- package/dist/{stop-AA5K5LYG.js → stop-NBVKEFQQ.js} +6 -6
- package/dist/{up-7B3BWF2U.js → up-WG65SWJU.js} +5 -5
- package/dist/{update-YAGN5ODG.js → update-FJIHDJKM.js} +5 -5
- package/dist/{update-check-APLTH4IN.js → update-check-MWE5AH4U.js} +2 -2
- package/dist/{upgrade-KXZCQSZN.js → upgrade-AIT24B5I.js} +1 -1
- package/dist/{variant-X5QFG6KK.js → variant-63ZWO2W7.js} +4 -4
- package/dist/variants-JAGWGBXG.js +26 -0
- package/dist/web-assets/assets/index-BAbuRsVF.css +1 -0
- package/dist/web-assets/assets/index-CiQhSKi_.js +63 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0010_delivery_queue.sql +12 -0
- package/drizzle/0011_rename_human_to_brain.sql +1 -0
- package/drizzle/meta/0010_snapshot.json +7 -0
- package/drizzle/meta/0011_snapshot.json +7 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +5 -3
- package/templates/_base/.init/.config/hooks/startup-context.sh +1 -1
- package/templates/_base/.init/.config/scripts/session-reader.ts +3 -3
- package/templates/_base/home/VOLUTE.md +16 -1
- package/templates/_base/src/lib/auto-commit.ts +51 -14
- package/templates/_base/src/lib/router.ts +123 -1
- package/templates/_base/src/lib/types.ts +4 -0
- package/templates/_base/src/lib/volute-server.ts +91 -2
- package/templates/claude/src/server.ts +2 -2
- package/templates/claude/volute-template.json +1 -2
- package/templates/pi/src/agent.ts +1 -1
- package/templates/pi/src/lib/session-context-extension.ts +2 -2
- package/templates/pi/volute-template.json +1 -2
- package/dist/chunk-PO5Q2AYN.js +0 -121
- package/dist/down-A56B5JLK.js +0 -14
- package/dist/mind-manager-Z7O7PN2O.js +0 -15
- package/dist/web-assets/assets/index-CtiimdWK.css +0 -1
- package/dist/web-assets/assets/index-kt1_EcuO.js +0 -63
- /package/{templates/_base/_skills → dist/skills}/memory/SKILL.md +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
logger_default
|
|
4
|
+
} from "./chunk-YUIHSKR6.js";
|
|
5
|
+
import {
|
|
6
|
+
gitExec
|
|
7
|
+
} from "./chunk-DYZGP3EW.js";
|
|
8
|
+
import {
|
|
9
|
+
isIsolationEnabled,
|
|
10
|
+
mindUserName
|
|
11
|
+
} from "./chunk-OGXOMR65.js";
|
|
12
|
+
import {
|
|
13
|
+
voluteHome
|
|
14
|
+
} from "./chunk-EBGCNDMM.js";
|
|
15
|
+
|
|
16
|
+
// src/lib/shared.ts
|
|
17
|
+
import { execFileSync } from "child_process";
|
|
18
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
19
|
+
import { resolve } from "path";
|
|
20
|
+
function readWorktreeGitDir(worktreePath) {
|
|
21
|
+
const dotGit = resolve(worktreePath, ".git");
|
|
22
|
+
if (!existsSync(dotGit)) return null;
|
|
23
|
+
try {
|
|
24
|
+
const content = readFileSync(dotGit, "utf-8").trim();
|
|
25
|
+
const match = content.match(/^gitdir:\s*(.+)$/);
|
|
26
|
+
return match ? match[1] : null;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function sharedDir() {
|
|
32
|
+
return resolve(voluteHome(), "shared");
|
|
33
|
+
}
|
|
34
|
+
async function ensureSharedRepo() {
|
|
35
|
+
const dir = sharedDir();
|
|
36
|
+
mkdirSync(dir, { recursive: true });
|
|
37
|
+
if (existsSync(resolve(dir, ".git"))) return;
|
|
38
|
+
const initArgs = isIsolationEnabled() ? ["init", "--shared=group"] : ["init"];
|
|
39
|
+
await gitExec(initArgs, { cwd: dir });
|
|
40
|
+
await gitExec(["checkout", "-b", "main"], { cwd: dir });
|
|
41
|
+
const pagesDir = resolve(dir, "pages");
|
|
42
|
+
mkdirSync(pagesDir, { recursive: true });
|
|
43
|
+
writeFileSync(resolve(pagesDir, ".gitkeep"), "");
|
|
44
|
+
await gitExec(["add", "-A"], { cwd: dir });
|
|
45
|
+
await gitExec(["commit", "-m", "init shared repo"], { cwd: dir });
|
|
46
|
+
if (isIsolationEnabled()) {
|
|
47
|
+
try {
|
|
48
|
+
execFileSync("chgrp", ["-R", "volute", dir], { stdio: "ignore" });
|
|
49
|
+
} catch (err) {
|
|
50
|
+
logger_default.warn("failed to chgrp shared repo to volute group", logger_default.errorData(err));
|
|
51
|
+
}
|
|
52
|
+
chmodSync(dir, 1533);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function addSharedWorktree(mindName, mindDir) {
|
|
56
|
+
const dir = sharedDir();
|
|
57
|
+
if (!existsSync(resolve(dir, ".git"))) return;
|
|
58
|
+
const worktreePath = resolve(mindDir, "home", "shared");
|
|
59
|
+
if (existsSync(worktreePath)) return;
|
|
60
|
+
let branchExists = false;
|
|
61
|
+
try {
|
|
62
|
+
await gitExec(["rev-parse", "--verify", mindName], { cwd: dir });
|
|
63
|
+
branchExists = true;
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
if (branchExists) {
|
|
67
|
+
await gitExec(["worktree", "add", worktreePath, mindName], { cwd: dir });
|
|
68
|
+
} else {
|
|
69
|
+
await gitExec(["worktree", "add", "-b", mindName, worktreePath], { cwd: dir });
|
|
70
|
+
}
|
|
71
|
+
if (isIsolationEnabled()) {
|
|
72
|
+
const worktreeGitDir = readWorktreeGitDir(worktreePath);
|
|
73
|
+
if (worktreeGitDir) {
|
|
74
|
+
try {
|
|
75
|
+
const user = mindUserName(mindName);
|
|
76
|
+
execFileSync("chown", ["-R", `${user}:volute`, worktreeGitDir], { stdio: "ignore" });
|
|
77
|
+
} catch (err) {
|
|
78
|
+
logger_default.warn(`failed to chown worktree git dir for ${mindName}`, logger_default.errorData(err));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function removeSharedWorktree(mindName, mindDir) {
|
|
84
|
+
const dir = sharedDir();
|
|
85
|
+
if (!existsSync(resolve(dir, ".git"))) return;
|
|
86
|
+
const worktreePath = resolve(mindDir, "home", "shared");
|
|
87
|
+
if (existsSync(worktreePath)) {
|
|
88
|
+
try {
|
|
89
|
+
await gitExec(["worktree", "remove", "--force", worktreePath], { cwd: dir });
|
|
90
|
+
} catch (err) {
|
|
91
|
+
logger_default.debug(`worktree remove failed for ${mindName}`, logger_default.errorData(err));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
await gitExec(["worktree", "prune"], { cwd: dir });
|
|
96
|
+
} catch (err) {
|
|
97
|
+
logger_default.debug(`worktree prune failed for ${mindName}`, logger_default.errorData(err));
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
await gitExec(["branch", "-D", mindName], { cwd: dir });
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
var sharedLock = Promise.resolve();
|
|
105
|
+
function rechownWorktree(worktreePath, mindName) {
|
|
106
|
+
if (!isIsolationEnabled()) return;
|
|
107
|
+
try {
|
|
108
|
+
const user = mindUserName(mindName);
|
|
109
|
+
execFileSync("chown", ["-R", `${user}:volute`, worktreePath], { stdio: "ignore" });
|
|
110
|
+
} catch (err) {
|
|
111
|
+
logger_default.warn(`failed to rechown worktree for ${mindName}`, logger_default.errorData(err));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function withSharedLock(fn) {
|
|
115
|
+
const prev = sharedLock;
|
|
116
|
+
let resolve_;
|
|
117
|
+
sharedLock = new Promise((r) => {
|
|
118
|
+
resolve_ = r;
|
|
119
|
+
});
|
|
120
|
+
await prev;
|
|
121
|
+
try {
|
|
122
|
+
return await fn();
|
|
123
|
+
} finally {
|
|
124
|
+
resolve_();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function sharedMerge(mindName, mindDir, message) {
|
|
128
|
+
return withSharedLock(async () => {
|
|
129
|
+
const dir = sharedDir();
|
|
130
|
+
const worktreePath = resolve(mindDir, "home", "shared");
|
|
131
|
+
const status = (await gitExec(["status", "--porcelain"], { cwd: worktreePath })).trim();
|
|
132
|
+
if (status) {
|
|
133
|
+
await gitExec(["add", "-A"], { cwd: worktreePath });
|
|
134
|
+
await gitExec(
|
|
135
|
+
["commit", "--author", `${mindName} <${mindName}@volute>`, "-m", `wip: ${mindName}`],
|
|
136
|
+
{ cwd: worktreePath }
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
const diff = (await gitExec(["diff", `main...${mindName}`, "--stat"], { cwd: dir })).trim();
|
|
140
|
+
if (!diff) {
|
|
141
|
+
return { ok: true, message: "Nothing to merge" };
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
await gitExec(["merge", "--squash", mindName], { cwd: dir });
|
|
145
|
+
} catch {
|
|
146
|
+
try {
|
|
147
|
+
await gitExec(["reset", "--hard", "HEAD"], { cwd: dir });
|
|
148
|
+
} catch (resetErr) {
|
|
149
|
+
logger_default.error("reset after squash conflict failed in shared repo", logger_default.errorData(resetErr));
|
|
150
|
+
}
|
|
151
|
+
return { ok: false, conflicts: true, message: "Merge conflicts detected" };
|
|
152
|
+
}
|
|
153
|
+
await gitExec(["commit", "--author", `${mindName} <${mindName}@volute>`, "-m", message], {
|
|
154
|
+
cwd: dir
|
|
155
|
+
});
|
|
156
|
+
try {
|
|
157
|
+
await gitExec(["reset", "--hard", "main"], { cwd: worktreePath });
|
|
158
|
+
} catch {
|
|
159
|
+
return {
|
|
160
|
+
ok: true,
|
|
161
|
+
message: "Merged to main, but branch reset failed \u2014 run 'volute shared pull' to sync"
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
rechownWorktree(worktreePath, mindName);
|
|
165
|
+
return { ok: true };
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
async function sharedPull(mindName, mindDir) {
|
|
169
|
+
return withSharedLock(async () => {
|
|
170
|
+
const worktreePath = resolve(mindDir, "home", "shared");
|
|
171
|
+
const status = (await gitExec(["status", "--porcelain"], { cwd: worktreePath })).trim();
|
|
172
|
+
if (status) {
|
|
173
|
+
await gitExec(["add", "-A"], { cwd: worktreePath });
|
|
174
|
+
await gitExec(
|
|
175
|
+
["commit", "--author", `${mindName} <${mindName}@volute>`, "-m", `wip: ${mindName}`],
|
|
176
|
+
{ cwd: worktreePath }
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
await gitExec(["rebase", "main"], { cwd: worktreePath });
|
|
181
|
+
rechownWorktree(worktreePath, mindName);
|
|
182
|
+
return { ok: true };
|
|
183
|
+
} catch {
|
|
184
|
+
try {
|
|
185
|
+
await gitExec(["rebase", "--abort"], { cwd: worktreePath });
|
|
186
|
+
} catch {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
message: "Rebase failed and abort failed \u2014 shared worktree may need manual repair"
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return { ok: false, message: "Rebase failed \u2014 conflicts with main" };
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
async function sharedLog(limit = 20) {
|
|
197
|
+
const dir = sharedDir();
|
|
198
|
+
return gitExec(["log", "--oneline", "-n", String(limit), "main"], { cwd: dir });
|
|
199
|
+
}
|
|
200
|
+
async function sharedStatus(mindName) {
|
|
201
|
+
const dir = sharedDir();
|
|
202
|
+
return gitExec(["diff", `main...${mindName}`, "--stat"], { cwd: dir });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export {
|
|
206
|
+
sharedDir,
|
|
207
|
+
ensureSharedRepo,
|
|
208
|
+
addSharedWorktree,
|
|
209
|
+
removeSharedWorktree,
|
|
210
|
+
sharedMerge,
|
|
211
|
+
sharedPull,
|
|
212
|
+
sharedLog,
|
|
213
|
+
sharedStatus
|
|
214
|
+
};
|
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
pollHealthDown,
|
|
6
6
|
readDaemonConfig,
|
|
7
7
|
stopService
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-32VR2EOH.js";
|
|
9
9
|
import {
|
|
10
10
|
voluteHome
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-EBGCNDMM.js";
|
|
12
12
|
|
|
13
13
|
// src/commands/down.ts
|
|
14
14
|
import { existsSync, readFileSync, unlinkSync } from "fs";
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
mindDir,
|
|
4
|
+
stateDir
|
|
5
|
+
} from "./chunk-EBGCNDMM.js";
|
|
6
|
+
|
|
7
|
+
// src/lib/archive.ts
|
|
8
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
9
|
+
import { join, relative, resolve } from "path";
|
|
10
|
+
import AdmZip from "adm-zip";
|
|
11
|
+
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".variants", ".git"]);
|
|
12
|
+
function walkDir(dir, base, skipSessions) {
|
|
13
|
+
const results = [];
|
|
14
|
+
const baseDir = base ?? dir;
|
|
15
|
+
for (const entry of readdirSync(dir)) {
|
|
16
|
+
const fullPath = resolve(dir, entry);
|
|
17
|
+
const relPath = relative(baseDir, fullPath);
|
|
18
|
+
const stat = statSync(fullPath);
|
|
19
|
+
if (stat.isDirectory()) {
|
|
20
|
+
if (EXCLUDED_DIRS.has(entry)) continue;
|
|
21
|
+
if (skipSessions && relPath === join(".mind", "sessions")) continue;
|
|
22
|
+
results.push(...walkDir(fullPath, baseDir, skipSessions));
|
|
23
|
+
} else {
|
|
24
|
+
results.push(relPath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return results;
|
|
28
|
+
}
|
|
29
|
+
function createExportArchive(options) {
|
|
30
|
+
const {
|
|
31
|
+
name,
|
|
32
|
+
template,
|
|
33
|
+
includeEnv = false,
|
|
34
|
+
includeIdentity = false,
|
|
35
|
+
includeConnectors = false,
|
|
36
|
+
includeHistory = false,
|
|
37
|
+
includeSessions = false
|
|
38
|
+
} = options;
|
|
39
|
+
const dir = mindDir(name);
|
|
40
|
+
const state = stateDir(name);
|
|
41
|
+
const zip = new AdmZip();
|
|
42
|
+
const files = walkDir(dir, void 0, includeSessions);
|
|
43
|
+
for (const relPath of files) {
|
|
44
|
+
if (!includeIdentity && relPath.startsWith(join(".mind", "identity"))) continue;
|
|
45
|
+
if (!includeConnectors && relPath.startsWith(join(".mind", "connectors"))) continue;
|
|
46
|
+
const fullPath = resolve(dir, relPath);
|
|
47
|
+
zip.addFile(`mind/${relPath}`, readFileSync(fullPath));
|
|
48
|
+
}
|
|
49
|
+
if (existsSync(state)) {
|
|
50
|
+
const channelsPath = resolve(state, "channels.json");
|
|
51
|
+
if (existsSync(channelsPath)) {
|
|
52
|
+
zip.addFile("state/channels.json", readFileSync(channelsPath));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (includeEnv && existsSync(state)) {
|
|
56
|
+
const envPath = resolve(state, "env.json");
|
|
57
|
+
if (existsSync(envPath)) {
|
|
58
|
+
zip.addFile("state/env.json", readFileSync(envPath));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (includeSessions) {
|
|
62
|
+
const sessionsDir = resolve(dir, ".mind/sessions");
|
|
63
|
+
if (existsSync(sessionsDir)) {
|
|
64
|
+
for (const file of readdirSync(sessionsDir)) {
|
|
65
|
+
if (!file.endsWith(".json") && !file.endsWith(".jsonl")) continue;
|
|
66
|
+
const fullPath = resolve(sessionsDir, file);
|
|
67
|
+
zip.addFile(`sessions/${file}`, readFileSync(fullPath));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
let voluteVersion = "unknown";
|
|
72
|
+
try {
|
|
73
|
+
const pkgPath = resolve(import.meta.dirname, "../../package.json");
|
|
74
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
75
|
+
voluteVersion = pkg.version;
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
const manifest = {
|
|
79
|
+
version: 1,
|
|
80
|
+
name,
|
|
81
|
+
template,
|
|
82
|
+
voluteVersion,
|
|
83
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
84
|
+
includes: {
|
|
85
|
+
env: includeEnv,
|
|
86
|
+
identity: includeIdentity,
|
|
87
|
+
connectors: includeConnectors,
|
|
88
|
+
history: includeHistory,
|
|
89
|
+
sessions: includeSessions
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
zip.addFile("manifest.json", Buffer.from(`${JSON.stringify(manifest, null, 2)}
|
|
93
|
+
`));
|
|
94
|
+
return zip;
|
|
95
|
+
}
|
|
96
|
+
function addHistoryToArchive(zip, rows) {
|
|
97
|
+
if (rows.length === 0) return;
|
|
98
|
+
const lines = `${rows.map((r) => JSON.stringify(r)).join("\n")}
|
|
99
|
+
`;
|
|
100
|
+
zip.addFile("history.jsonl", Buffer.from(lines));
|
|
101
|
+
}
|
|
102
|
+
function readManifest(archivePath) {
|
|
103
|
+
const zip = new AdmZip(archivePath);
|
|
104
|
+
const entry = zip.getEntry("manifest.json");
|
|
105
|
+
if (!entry) {
|
|
106
|
+
throw new Error("Invalid archive: missing manifest.json");
|
|
107
|
+
}
|
|
108
|
+
const manifest = JSON.parse(entry.getData().toString("utf-8"));
|
|
109
|
+
if (manifest.version !== 1) {
|
|
110
|
+
throw new Error(`Unsupported archive version: ${manifest.version}`);
|
|
111
|
+
}
|
|
112
|
+
return manifest;
|
|
113
|
+
}
|
|
114
|
+
function extractArchive(archivePath, destDir) {
|
|
115
|
+
const zip = new AdmZip(archivePath);
|
|
116
|
+
const manifestEntry = zip.getEntry("manifest.json");
|
|
117
|
+
if (!manifestEntry) {
|
|
118
|
+
throw new Error("Invalid archive: missing manifest.json");
|
|
119
|
+
}
|
|
120
|
+
const manifest = JSON.parse(manifestEntry.getData().toString("utf-8"));
|
|
121
|
+
if (manifest.version !== 1) {
|
|
122
|
+
throw new Error(`Unsupported archive version: ${manifest.version}`);
|
|
123
|
+
}
|
|
124
|
+
const normalizedDestDir = resolve(destDir);
|
|
125
|
+
const extractedMindDir = resolve(normalizedDestDir, "mind");
|
|
126
|
+
const extractedStateDir = resolve(normalizedDestDir, "state");
|
|
127
|
+
mkdirSync(extractedMindDir, { recursive: true });
|
|
128
|
+
mkdirSync(extractedStateDir, { recursive: true });
|
|
129
|
+
for (const entry of zip.getEntries()) {
|
|
130
|
+
if (entry.isDirectory) continue;
|
|
131
|
+
const name = entry.entryName;
|
|
132
|
+
if (name === "manifest.json") continue;
|
|
133
|
+
const destPath = resolve(normalizedDestDir, name);
|
|
134
|
+
if (!destPath.startsWith(`${normalizedDestDir}/`)) {
|
|
135
|
+
throw new Error(`Archive contains path traversal entry: ${name}`);
|
|
136
|
+
}
|
|
137
|
+
mkdirSync(resolve(destPath, ".."), { recursive: true });
|
|
138
|
+
writeFileSync(destPath, entry.getData());
|
|
139
|
+
}
|
|
140
|
+
const channelsJson = resolve(extractedStateDir, "channels.json");
|
|
141
|
+
const envJson = resolve(extractedStateDir, "env.json");
|
|
142
|
+
const historyJsonl = resolve(normalizedDestDir, "history.jsonl");
|
|
143
|
+
const sessionsDir = resolve(normalizedDestDir, "sessions");
|
|
144
|
+
return {
|
|
145
|
+
manifest,
|
|
146
|
+
mindDir: extractedMindDir,
|
|
147
|
+
channelsJson: existsSync(channelsJson) ? channelsJson : null,
|
|
148
|
+
envJson: existsSync(envJson) ? envJson : null,
|
|
149
|
+
historyJsonl: existsSync(historyJsonl) ? historyJsonl : null,
|
|
150
|
+
sessionsDir: existsSync(sessionsDir) ? sessionsDir : null
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export {
|
|
155
|
+
createExportArchive,
|
|
156
|
+
addHistoryToArchive,
|
|
157
|
+
readManifest,
|
|
158
|
+
extractArchive
|
|
159
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
wrapForIsolation
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-OGXOMR65.js";
|
|
5
5
|
|
|
6
6
|
// src/lib/exec.ts
|
|
7
7
|
import { execFile as execFileCb, execFileSync, spawn } from "child_process";
|
|
@@ -24,7 +24,7 @@ function exec(cmd, args, options) {
|
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
function gitExec(args, options) {
|
|
27
|
-
const fullArgs = process.env.VOLUTE_ISOLATION === "user" ? ["-c",
|
|
27
|
+
const fullArgs = process.env.VOLUTE_ISOLATION === "user" ? ["-c", "safe.directory=*", ...args] : args;
|
|
28
28
|
return exec("git", fullArgs, options);
|
|
29
29
|
}
|
|
30
30
|
function resolveVoluteBin() {
|
|
@@ -9,6 +9,24 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
|
|
|
9
9
|
import { homedir } from "os";
|
|
10
10
|
import { dirname, resolve } from "path";
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
12
|
+
var registryCache = null;
|
|
13
|
+
function initRegistryCache() {
|
|
14
|
+
registryCache = readRegistryFromDisk();
|
|
15
|
+
}
|
|
16
|
+
function readRegistryFromDisk() {
|
|
17
|
+
const registryPath = resolve(voluteHome(), "minds.json");
|
|
18
|
+
if (!existsSync(registryPath)) return [];
|
|
19
|
+
try {
|
|
20
|
+
const entries = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
21
|
+
return entries.map((e) => ({
|
|
22
|
+
...e,
|
|
23
|
+
running: e.running ?? false,
|
|
24
|
+
stage: e.stage ?? "sprouted"
|
|
25
|
+
}));
|
|
26
|
+
} catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
12
30
|
function voluteHome() {
|
|
13
31
|
if (process.env.VOLUTE_HOME) return process.env.VOLUTE_HOME;
|
|
14
32
|
const dir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -24,20 +42,11 @@ function ensureVoluteHome() {
|
|
|
24
42
|
mkdirSync(mindsBase, { recursive: true });
|
|
25
43
|
}
|
|
26
44
|
function readRegistry() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const entries = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
31
|
-
return entries.map((e) => ({
|
|
32
|
-
...e,
|
|
33
|
-
running: e.running ?? false,
|
|
34
|
-
stage: e.stage ?? "sprouted"
|
|
35
|
-
}));
|
|
36
|
-
} catch {
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
45
|
+
if (registryCache) return registryCache;
|
|
46
|
+
return readRegistryFromDisk();
|
|
39
47
|
}
|
|
40
48
|
function writeRegistry(entries) {
|
|
49
|
+
if (registryCache) registryCache = entries;
|
|
41
50
|
ensureVoluteHome();
|
|
42
51
|
const registryPath = resolve(voluteHome(), "minds.json");
|
|
43
52
|
const tmpPath = `${registryPath}.tmp`;
|
|
@@ -55,12 +64,12 @@ function validateMindName(name) {
|
|
|
55
64
|
}
|
|
56
65
|
return null;
|
|
57
66
|
}
|
|
58
|
-
function addMind(name, port, stage) {
|
|
67
|
+
function addMind(name, port, stage, template) {
|
|
59
68
|
const err = validateMindName(name);
|
|
60
69
|
if (err) throw new Error(err);
|
|
61
70
|
const entries = readRegistry();
|
|
62
71
|
const filtered = entries.filter((e) => e.name !== name);
|
|
63
|
-
filtered.push({ name, port, created: (/* @__PURE__ */ new Date()).toISOString(), running: false, stage });
|
|
72
|
+
filtered.push({ name, port, created: (/* @__PURE__ */ new Date()).toISOString(), running: false, stage, template });
|
|
64
73
|
writeRegistry(filtered);
|
|
65
74
|
}
|
|
66
75
|
function removeMind(name) {
|
|
@@ -242,6 +251,7 @@ export {
|
|
|
242
251
|
removeAllVariants,
|
|
243
252
|
checkHealth,
|
|
244
253
|
validateBranchName,
|
|
254
|
+
initRegistryCache,
|
|
245
255
|
voluteHome,
|
|
246
256
|
ensureVoluteHome,
|
|
247
257
|
readRegistry,
|
|
@@ -3,14 +3,25 @@ import {
|
|
|
3
3
|
mindEnvPath,
|
|
4
4
|
readEnv,
|
|
5
5
|
writeEnv
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-VDWCHYTS.js";
|
|
7
7
|
import {
|
|
8
8
|
parseArgs
|
|
9
9
|
} from "./chunk-D424ZQGI.js";
|
|
10
10
|
|
|
11
11
|
// src/commands/import.ts
|
|
12
|
-
import {
|
|
13
|
-
|
|
12
|
+
import {
|
|
13
|
+
closeSync,
|
|
14
|
+
existsSync as existsSync2,
|
|
15
|
+
mkdirSync as mkdirSync2,
|
|
16
|
+
openSync,
|
|
17
|
+
readdirSync,
|
|
18
|
+
readFileSync as readFileSync2,
|
|
19
|
+
readSync,
|
|
20
|
+
rmSync,
|
|
21
|
+
statSync,
|
|
22
|
+
writeFileSync as writeFileSync2
|
|
23
|
+
} from "fs";
|
|
24
|
+
import { homedir, tmpdir } from "os";
|
|
14
25
|
import { basename, resolve as resolve2 } from "path";
|
|
15
26
|
|
|
16
27
|
// src/lib/volute-config.ts
|
|
@@ -42,8 +53,13 @@ async function run(args) {
|
|
|
42
53
|
session: { type: "string" },
|
|
43
54
|
template: { type: "string" }
|
|
44
55
|
});
|
|
45
|
-
const
|
|
46
|
-
|
|
56
|
+
const inputPath = positional[0];
|
|
57
|
+
if (inputPath && (inputPath.endsWith(".volute") || isZipFile(inputPath))) {
|
|
58
|
+
await importArchive(resolve2(inputPath), flags.name);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const wsDir = resolveWorkspace(inputPath);
|
|
62
|
+
const { daemonFetch } = await import("./daemon-client-ITWUCNFO.js");
|
|
47
63
|
const { getClient, urlOf } = await import("./api-client-YPKOZP2O.js");
|
|
48
64
|
const client = getClient();
|
|
49
65
|
const res = await daemonFetch(urlOf(client.api.minds.import.$url()), {
|
|
@@ -66,6 +82,61 @@ ${data.message ?? `Imported mind: ${data.name} (port ${data.port})`}`);
|
|
|
66
82
|
console.log(`
|
|
67
83
|
volute mind start ${data.name}`);
|
|
68
84
|
}
|
|
85
|
+
function isZipFile(path) {
|
|
86
|
+
const resolved = resolve2(path);
|
|
87
|
+
if (!existsSync2(resolved)) return false;
|
|
88
|
+
const fd = openSync(resolved, "r");
|
|
89
|
+
try {
|
|
90
|
+
const buf = Buffer.alloc(4);
|
|
91
|
+
const bytesRead = readSync(fd, buf, 0, 4, 0);
|
|
92
|
+
return bytesRead === 4 && buf[0] === 80 && buf[1] === 75 && buf[2] === 3 && buf[3] === 4;
|
|
93
|
+
} finally {
|
|
94
|
+
closeSync(fd);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function importArchive(archivePath, nameOverride) {
|
|
98
|
+
if (!existsSync2(archivePath)) {
|
|
99
|
+
console.error(`File not found: ${archivePath}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
const { extractArchive } = await import("./archive-ZCFOSTKB.js");
|
|
103
|
+
const tempDir = resolve2(tmpdir(), `volute-import-${Date.now()}`);
|
|
104
|
+
mkdirSync2(tempDir, { recursive: true });
|
|
105
|
+
let extracted;
|
|
106
|
+
try {
|
|
107
|
+
extracted = extractArchive(archivePath, tempDir);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
110
|
+
console.error(`Failed to extract archive: ${err.message}`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const { daemonFetch } = await import("./daemon-client-ITWUCNFO.js");
|
|
115
|
+
const { getClient, urlOf } = await import("./api-client-YPKOZP2O.js");
|
|
116
|
+
const client = getClient();
|
|
117
|
+
const res = await daemonFetch(urlOf(client.api.minds.import.$url()), {
|
|
118
|
+
method: "POST",
|
|
119
|
+
headers: { "Content-Type": "application/json" },
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
archivePath: tempDir,
|
|
122
|
+
name: nameOverride,
|
|
123
|
+
manifest: extracted.manifest
|
|
124
|
+
})
|
|
125
|
+
});
|
|
126
|
+
const data = await res.json();
|
|
127
|
+
if (!res.ok) {
|
|
128
|
+
console.error(data.error ?? "Failed to import mind");
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
console.log(`
|
|
132
|
+
${data.message ?? `Imported mind: ${data.name} (port ${data.port})`}`);
|
|
133
|
+
console.log(`
|
|
134
|
+
volute mind start ${data.name}`);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
69
140
|
function resolveWorkspace(explicitPath) {
|
|
70
141
|
if (explicitPath) {
|
|
71
142
|
const wsDir = resolve2(explicitPath);
|
|
@@ -127,7 +198,7 @@ function sessionMatchesWorkspace(sessionPath, workspaceDir) {
|
|
|
127
198
|
}
|
|
128
199
|
function importPiSession(sessionFile, mindDirPath) {
|
|
129
200
|
const homeDir = resolve2(mindDirPath, "home");
|
|
130
|
-
const piSessionDir = resolve2(mindDirPath, ".
|
|
201
|
+
const piSessionDir = resolve2(mindDirPath, ".mind/pi-sessions/main");
|
|
131
202
|
mkdirSync2(piSessionDir, { recursive: true });
|
|
132
203
|
const content = readFileSync2(sessionFile, "utf-8");
|
|
133
204
|
const lines = content.trim().split("\n");
|