volute 0.18.0 → 0.20.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 (117) hide show
  1. package/README.md +67 -67
  2. package/dist/activity-events-OMXKXD5N.js +16 -0
  3. package/dist/archive-ZCFOSTKB.js +15 -0
  4. package/dist/{channel-SLURLIRV.js → channel-PUQKGSQM.js} +60 -7
  5. package/dist/{chunk-6BDNWYKG.js → chunk-32VR2EOH.js} +2 -2
  6. package/dist/chunk-5XNT2472.js +36 -0
  7. package/dist/{chunk-QJIIHU32.js → chunk-7NO7EV5Z.js} +2 -2
  8. package/dist/{chunk-6DVBMLVN.js → chunk-7UFKREVW.js} +2 -2
  9. package/dist/chunk-AW7P4EVV.js +159 -0
  10. package/dist/{chunk-2Y77MCFG.js → chunk-DYZGP3EW.js} +2 -2
  11. package/dist/{chunk-M77QBTEH.js → chunk-EBGCNDMM.js} +24 -14
  12. package/dist/{chunk-37X7ECMF.js → chunk-FCDU5BFX.js} +1 -1
  13. package/dist/chunk-FGSYHIS3.js +891 -0
  14. package/dist/chunk-GZ7DW4YL.js +97 -0
  15. package/dist/chunk-IKMY5X76.js +375 -0
  16. package/dist/chunk-NSE7VJQA.js +159 -0
  17. package/dist/{chunk-GSPWIM5E.js → chunk-O6ASDHFO.js} +79 -7
  18. package/dist/{chunk-ZCEYUUID.js → chunk-OGXOMR65.js} +2 -1
  19. package/dist/{chunk-AYB7XAWO.js → chunk-PUVXOZ6T.js} +120 -279
  20. package/dist/{chunk-GK4E7LM7.js → chunk-RHEGSQFJ.js} +1 -1
  21. package/dist/{chunk-MVSXRMJJ.js → chunk-SCUDS4US.js} +1 -1
  22. package/dist/chunk-TIWH32HP.js +227 -0
  23. package/dist/{chunk-FW5API7X.js → chunk-UJ6GHNR7.js} +2 -2
  24. package/dist/chunk-UU7A7KLB.js +58 -0
  25. package/dist/{chunk-OYSZNX5I.js → chunk-VDWCHYTS.js} +1 -1
  26. package/dist/{chunk-OJQ47SCA.js → chunk-WC6ZHVRL.js} +1 -1
  27. package/dist/chunk-YUIHSKR6.js +72 -0
  28. package/dist/cli.js +43 -25
  29. package/dist/{connector-3ELFMI2R.js → connector-JBVNZ7VK.js} +6 -6
  30. package/dist/connectors/discord.js +2 -2
  31. package/dist/connectors/slack.js +2 -2
  32. package/dist/connectors/telegram.js +2 -2
  33. package/dist/{create-ZWHCRT5F.js → create-HP4OVVHF.js} +6 -4
  34. package/dist/{daemon-client-ODKDUYDE.js → daemon-client-ITWUCNFO.js} +2 -2
  35. package/dist/{daemon-restart-2HVTHZAT.js → daemon-restart-KPSWNYTH.js} +6 -6
  36. package/dist/daemon.js +2463 -1707
  37. package/dist/db-C2CJ46ZU.js +10 -0
  38. package/dist/{delete-6G6WEX4F.js → delete-BSU7K3RY.js} +1 -1
  39. package/dist/delivery-manager-CSG7LXA4.js +16 -0
  40. package/dist/down-ZY35KMHR.js +14 -0
  41. package/dist/{env-6IDWGBUH.js → env-A3LMO777.js} +6 -6
  42. package/dist/export-6QBUOQGC.js +100 -0
  43. package/dist/file-C57SK5DK.js +204 -0
  44. package/dist/{history-YUEKTJ2N.js → history-WNK3DFUM.js} +6 -6
  45. package/dist/{import-EDGRLIGO.js → import-XEC34Y4Z.js} +3 -3
  46. package/dist/log-PPPZDVEF.js +39 -0
  47. package/dist/{login-ORQDXLBM.js → login-HNH3EUQV.js} +2 -2
  48. package/dist/{logout-XC5AUO5I.js → logout-I5CB5UZS.js} +2 -2
  49. package/dist/{logs-GYOR3L2L.js → logs-SF2IMJN4.js} +6 -6
  50. package/dist/merge-33C237A4.js +46 -0
  51. package/dist/{mind-OJN6RBZW.js → mind-Z7CKD6DG.js} +14 -10
  52. package/dist/mind-activity-tracker-624QLQLC.js +19 -0
  53. package/dist/mind-manager-3DMYKZPB.js +18 -0
  54. package/dist/{package-OKLFO7UY.js → package-4NHAVUUI.js} +5 -3
  55. package/dist/{pages-6IV4VQTU.js → pages-4DGQT7ZA.js} +2 -2
  56. package/dist/{publish-Q4RPSJLL.js → publish-TAJUET4I.js} +22 -5
  57. package/dist/pull-XAEWQJ47.js +39 -0
  58. package/dist/{register-LDE6LRXY.js → register-VSPCMHKX.js} +2 -2
  59. package/dist/{restart-YFAWFS5T.js → restart-IQKMCK5M.js} +6 -6
  60. package/dist/{schedule-AGYLDMNS.js → schedule-FFZG23IW.js} +31 -11
  61. package/dist/schema-GFH6RV3W.js +26 -0
  62. package/dist/{seed-AP4Q7RZ7.js → seed-J43YDKXG.js} +7 -4
  63. package/dist/{send-BNDTLUPM.js → send-KVIZIGCE.js} +8 -8
  64. package/dist/{service-U7MZ2H7F.js → service-LUR7WDO7.js} +6 -6
  65. package/dist/{setup-DJKIZKGW.js → setup-52YRV7VP.js} +23 -7
  66. package/dist/shared-KO35ZM44.js +39 -0
  67. package/dist/{skill-2Y42P4JY.js → skill-BCVNI6TV.js} +6 -6
  68. package/{templates/_base/_skills → dist/skills}/orientation/SKILL.md +1 -1
  69. package/{templates/_base/_skills → dist/skills}/sessions/SKILL.md +2 -2
  70. package/{templates/_base/_skills → dist/skills}/volute-mind/SKILL.md +51 -3
  71. package/dist/{sprout-TJ3BHVOG.js → sprout-QN7Y4VVO.js} +38 -20
  72. package/dist/{start-3YYRXBKP.js → start-I5JYB65M.js} +6 -6
  73. package/dist/{status-VSFZYX7S.js → status-4ESFLGH4.js} +5 -5
  74. package/dist/status-D7E5HHBV.js +35 -0
  75. package/dist/{status-OKNA6AR3.js → status-FU2PFVVF.js} +5 -4
  76. package/dist/{stop-AA5K5LYG.js → stop-NBVKEFQQ.js} +6 -6
  77. package/dist/{up-7B3BWF2U.js → up-FS7CKM6V.js} +5 -5
  78. package/dist/{update-YAGN5ODG.js → update-FJIHDJKM.js} +5 -5
  79. package/dist/{update-check-APLTH4IN.js → update-check-MWE5AH4U.js} +2 -2
  80. package/dist/{upgrade-KXZCQSZN.js → upgrade-AIT24B5I.js} +1 -1
  81. package/dist/{variant-X5QFG6KK.js → variant-63ZWO2W7.js} +4 -4
  82. package/dist/variants-JAGWGBXG.js +26 -0
  83. package/dist/web-assets/assets/index-CUZTZzaW.js +64 -0
  84. package/dist/web-assets/assets/index-adVuCkqy.css +1 -0
  85. package/dist/web-assets/index.html +2 -2
  86. package/drizzle/0010_delivery_queue.sql +12 -0
  87. package/drizzle/0011_rename_human_to_brain.sql +1 -0
  88. package/drizzle/0012_activity.sql +11 -0
  89. package/drizzle/meta/0010_snapshot.json +7 -0
  90. package/drizzle/meta/0011_snapshot.json +7 -0
  91. package/drizzle/meta/0012_snapshot.json +7 -0
  92. package/drizzle/meta/_journal.json +21 -0
  93. package/package.json +5 -3
  94. package/templates/_base/.init/.config/hooks/startup-context.sh +1 -1
  95. package/templates/_base/.init/.config/scripts/session-reader.ts +3 -3
  96. package/templates/_base/home/.config/routes.json +2 -2
  97. package/templates/_base/home/VOLUTE.md +16 -1
  98. package/templates/_base/src/lib/auto-commit.ts +51 -14
  99. package/templates/_base/src/lib/daemon-client.ts +22 -0
  100. package/templates/_base/src/lib/router.ts +123 -1
  101. package/templates/_base/src/lib/transparency.ts +1 -1
  102. package/templates/_base/src/lib/types.ts +4 -0
  103. package/templates/_base/src/lib/volute-server.ts +91 -2
  104. package/templates/claude/.init/.config/routes.json +7 -1
  105. package/templates/claude/src/server.ts +2 -2
  106. package/templates/claude/volute-template.json +1 -2
  107. package/templates/pi/.init/.config/routes.json +7 -1
  108. package/templates/pi/src/agent.ts +12 -6
  109. package/templates/pi/src/lib/session-context-extension.ts +6 -4
  110. package/templates/pi/src/server.ts +2 -0
  111. package/templates/pi/volute-template.json +1 -2
  112. package/dist/chunk-PO5Q2AYN.js +0 -121
  113. package/dist/down-A56B5JLK.js +0 -14
  114. package/dist/mind-manager-Z7O7PN2O.js +0 -15
  115. package/dist/web-assets/assets/index-CtiimdWK.css +0 -1
  116. package/dist/web-assets/assets/index-kt1_EcuO.js +0 -63
  117. /package/{templates/_base/_skills → dist/skills}/memory/SKILL.md +0 -0
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ publish
4
+ } from "./chunk-UU7A7KLB.js";
5
+ import {
6
+ logger_default
7
+ } from "./chunk-YUIHSKR6.js";
8
+
9
+ // src/lib/mind-activity-tracker.ts
10
+ var IDLE_TIMEOUT_MS = 2 * 60 * 1e3;
11
+ var minds = /* @__PURE__ */ new Map();
12
+ function getState(mind) {
13
+ let state = minds.get(mind);
14
+ if (!state) {
15
+ state = { active: false, idleTimer: null };
16
+ minds.set(mind, state);
17
+ }
18
+ return state;
19
+ }
20
+ var IGNORED_EVENTS = /* @__PURE__ */ new Set(["done", "usage", "log"]);
21
+ function onMindEvent(mind, type, channel) {
22
+ const state = getState(mind);
23
+ if (type === "done") {
24
+ state.active = false;
25
+ if (state.idleTimer) {
26
+ clearTimeout(state.idleTimer);
27
+ }
28
+ state.idleTimer = setTimeout(() => {
29
+ state.idleTimer = null;
30
+ if (!state.active) {
31
+ publish({
32
+ type: "mind_idle",
33
+ mind,
34
+ summary: `${mind} is idle`
35
+ }).catch((err) => {
36
+ logger_default.error("[mind-activity] failed to publish mind_idle", logger_default.errorData(err));
37
+ });
38
+ }
39
+ }, IDLE_TIMEOUT_MS);
40
+ } else if (!IGNORED_EVENTS.has(type)) {
41
+ if (state.idleTimer) {
42
+ clearTimeout(state.idleTimer);
43
+ state.idleTimer = null;
44
+ }
45
+ if (!state.active) {
46
+ state.active = true;
47
+ state.channel = channel;
48
+ publish({
49
+ type: "mind_active",
50
+ mind,
51
+ summary: `${mind} is active`,
52
+ metadata: channel ? { channel } : void 0
53
+ }).catch((err) => {
54
+ logger_default.error("[mind-activity] failed to publish mind_active", logger_default.errorData(err));
55
+ });
56
+ }
57
+ }
58
+ }
59
+ function markIdle(mind) {
60
+ const state = minds.get(mind);
61
+ if (!state) return;
62
+ if (state.idleTimer) {
63
+ clearTimeout(state.idleTimer);
64
+ state.idleTimer = null;
65
+ }
66
+ if (state.active) {
67
+ state.active = false;
68
+ publish({
69
+ type: "mind_idle",
70
+ mind,
71
+ summary: `${mind} is idle`
72
+ }).catch((err) => {
73
+ logger_default.error("[mind-activity] failed to publish mind_idle", logger_default.errorData(err));
74
+ });
75
+ }
76
+ minds.delete(mind);
77
+ }
78
+ function getActiveMinds() {
79
+ const result = [];
80
+ for (const [mind, state] of minds) {
81
+ if (state.active) result.push(mind);
82
+ }
83
+ return result;
84
+ }
85
+ function stopAll() {
86
+ for (const [, state] of minds) {
87
+ if (state.idleTimer) clearTimeout(state.idleTimer);
88
+ }
89
+ minds.clear();
90
+ }
91
+
92
+ export {
93
+ onMindEvent,
94
+ markIdle,
95
+ getActiveMinds,
96
+ stopAll
97
+ };
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ logger_default
4
+ } from "./chunk-YUIHSKR6.js";
5
+ import {
6
+ getDb
7
+ } from "./chunk-5XNT2472.js";
8
+ import {
9
+ sharedSkills
10
+ } from "./chunk-NSE7VJQA.js";
11
+ import {
12
+ exec,
13
+ gitExec
14
+ } from "./chunk-DYZGP3EW.js";
15
+ import {
16
+ voluteHome
17
+ } from "./chunk-EBGCNDMM.js";
18
+
19
+ // src/lib/skills.ts
20
+ import { createHash } from "crypto";
21
+ import {
22
+ cpSync,
23
+ existsSync,
24
+ mkdirSync,
25
+ readdirSync,
26
+ readFileSync,
27
+ rmSync,
28
+ writeFileSync
29
+ } from "fs";
30
+ import { tmpdir } from "os";
31
+ import { basename, dirname, join, resolve } from "path";
32
+ import { eq, sql } from "drizzle-orm";
33
+ var VALID_SKILL_ID = /^[a-zA-Z0-9_-]+$/;
34
+ var SEED_SKILLS = ["orientation", "memory"];
35
+ var STANDARD_SKILLS = ["volute-mind", "memory", "sessions"];
36
+ function validateSkillId(id) {
37
+ if (!id || !VALID_SKILL_ID.test(id)) {
38
+ throw new Error(`Invalid skill ID: ${id}`);
39
+ }
40
+ }
41
+ function sharedSkillsDir() {
42
+ return resolve(voluteHome(), "skills");
43
+ }
44
+ function parseSkillMd(content) {
45
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
46
+ if (!match) return { name: "", description: "" };
47
+ const frontmatter = match[1];
48
+ const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
49
+ const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
50
+ return {
51
+ name: nameMatch?.[1].trim() ?? "",
52
+ description: descMatch?.[1].trim() ?? ""
53
+ };
54
+ }
55
+ async function listSharedSkills() {
56
+ const db = await getDb();
57
+ return db.select().from(sharedSkills).all();
58
+ }
59
+ async function getSharedSkill(id) {
60
+ const db = await getDb();
61
+ return db.select().from(sharedSkills).where(eq(sharedSkills.id, id)).get();
62
+ }
63
+ async function importSkillFromDir(sourceDir, author) {
64
+ const skillMdPath = join(sourceDir, "SKILL.md");
65
+ if (!existsSync(skillMdPath)) {
66
+ throw new Error("SKILL.md not found in source directory");
67
+ }
68
+ const content = readFileSync(skillMdPath, "utf-8");
69
+ const { name, description } = parseSkillMd(content);
70
+ const id = basename(sourceDir);
71
+ if (!id || id === "." || id === "..") {
72
+ throw new Error("Invalid skill directory name");
73
+ }
74
+ validateSkillId(id);
75
+ const destDir = join(sharedSkillsDir(), id);
76
+ if (existsSync(destDir)) rmSync(destDir, { recursive: true });
77
+ mkdirSync(destDir, { recursive: true });
78
+ cpSync(sourceDir, destDir, { recursive: true });
79
+ const upstreamPath = join(destDir, ".upstream.json");
80
+ if (existsSync(upstreamPath)) rmSync(upstreamPath);
81
+ const db = await getDb();
82
+ const existing = await db.select().from(sharedSkills).where(eq(sharedSkills.id, id)).get();
83
+ const version = existing ? existing.version + 1 : 1;
84
+ await db.insert(sharedSkills).values({ id, name: name || id, description, author, version }).onConflictDoUpdate({
85
+ target: sharedSkills.id,
86
+ set: {
87
+ name: name || id,
88
+ description,
89
+ author,
90
+ version,
91
+ updated_at: sql`(datetime('now'))`
92
+ }
93
+ });
94
+ const row = await db.select().from(sharedSkills).where(eq(sharedSkills.id, id)).get();
95
+ if (!row) throw new Error(`Failed to upsert shared skill: ${id}`);
96
+ return row;
97
+ }
98
+ async function removeSharedSkill(id) {
99
+ const db = await getDb();
100
+ const existing = await db.select().from(sharedSkills).where(eq(sharedSkills.id, id)).get();
101
+ if (!existing) throw new Error(`Shared skill not found: ${id}`);
102
+ await db.delete(sharedSkills).where(eq(sharedSkills.id, id));
103
+ const dir = join(sharedSkillsDir(), id);
104
+ if (existsSync(dir)) rmSync(dir, { recursive: true });
105
+ }
106
+ function mindSkillsDir(dir) {
107
+ return resolve(dir, "home", ".claude", "skills");
108
+ }
109
+ function readUpstream(skillDir) {
110
+ const upstreamPath = join(skillDir, ".upstream.json");
111
+ if (!existsSync(upstreamPath)) return null;
112
+ try {
113
+ const data = JSON.parse(readFileSync(upstreamPath, "utf-8"));
114
+ if (typeof data?.source !== "string" || typeof data?.version !== "number" || typeof data?.baseCommit !== "string") {
115
+ return null;
116
+ }
117
+ return data;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+ async function installSkill(_mindName, dir, skillId) {
123
+ validateSkillId(skillId);
124
+ const shared = await getSharedSkill(skillId);
125
+ if (!shared) throw new Error(`Shared skill not found: ${skillId}`);
126
+ const sourceDir = join(sharedSkillsDir(), skillId);
127
+ if (!existsSync(sourceDir)) throw new Error(`Shared skill files not found: ${skillId}`);
128
+ const destDir = join(mindSkillsDir(dir), skillId);
129
+ if (existsSync(destDir)) throw new Error(`Skill already installed: ${skillId}`);
130
+ mkdirSync(destDir, { recursive: true });
131
+ cpSync(sourceDir, destDir, { recursive: true });
132
+ await gitExec(["add", join("home", ".claude", "skills", skillId)], { cwd: dir });
133
+ await gitExec(["commit", "-m", `Install shared skill: ${skillId}`], { cwd: dir });
134
+ const commitHash = (await gitExec(["rev-parse", "HEAD"], { cwd: dir })).trim();
135
+ const upstream = {
136
+ source: skillId,
137
+ version: shared.version,
138
+ baseCommit: commitHash
139
+ };
140
+ writeFileSync(join(destDir, ".upstream.json"), `${JSON.stringify(upstream, null, 2)}
141
+ `);
142
+ await gitExec(["add", join("home", ".claude", "skills", skillId, ".upstream.json")], {
143
+ cwd: dir
144
+ });
145
+ await gitExec(["commit", "--amend", "--no-edit"], { cwd: dir });
146
+ }
147
+ async function uninstallSkill(_mindName, dir, skillId) {
148
+ validateSkillId(skillId);
149
+ const skillDir = join(mindSkillsDir(dir), skillId);
150
+ if (!existsSync(skillDir)) throw new Error(`Skill not installed: ${skillId}`);
151
+ rmSync(skillDir, { recursive: true });
152
+ await gitExec(["add", join("home", ".claude", "skills", skillId)], { cwd: dir });
153
+ await gitExec(["commit", "-m", `Uninstall skill: ${skillId}`], { cwd: dir });
154
+ }
155
+ async function updateSkill(_mindName, dir, skillId) {
156
+ validateSkillId(skillId);
157
+ const skillDir = join(mindSkillsDir(dir), skillId);
158
+ if (!existsSync(skillDir)) throw new Error(`Skill not installed: ${skillId}`);
159
+ const upstream = readUpstream(skillDir);
160
+ if (!upstream) throw new Error(`No upstream tracking for skill: ${skillId}`);
161
+ const shared = await getSharedSkill(upstream.source);
162
+ if (!shared) throw new Error(`Shared skill no longer exists: ${upstream.source}`);
163
+ if (shared.version <= upstream.version) {
164
+ return { status: "up-to-date" };
165
+ }
166
+ const sourceDir = join(sharedSkillsDir(), upstream.source);
167
+ if (!existsSync(sourceDir)) throw new Error(`Shared skill files missing: ${upstream.source}`);
168
+ const relSkillPath = join("home", ".claude", "skills", skillId);
169
+ const currentFiles = listFilesRecursive(skillDir).filter((f) => f !== ".upstream.json");
170
+ const newFiles = listFilesRecursive(sourceDir).filter((f) => f !== ".upstream.json");
171
+ const allFiles = [.../* @__PURE__ */ new Set([...currentFiles, ...newFiles])];
172
+ const conflictFiles = [];
173
+ const tmpBase = join(tmpdir(), `volute-merge-${process.pid}-${Date.now()}`);
174
+ mkdirSync(tmpBase, { recursive: true });
175
+ try {
176
+ for (const file of allFiles) {
177
+ const currentPath = join(skillDir, file);
178
+ const newPath = join(sourceDir, file);
179
+ const currentExists = existsSync(currentPath);
180
+ const newExists = existsSync(newPath);
181
+ if (!currentExists && newExists) {
182
+ const destPath = join(skillDir, file);
183
+ mkdirSync(join(skillDir, ...file.split("/").slice(0, -1)), { recursive: true });
184
+ cpSync(newPath, destPath);
185
+ continue;
186
+ }
187
+ if (currentExists && !newExists) {
188
+ let baseContent2 = null;
189
+ try {
190
+ baseContent2 = await gitExec(
191
+ ["show", `${upstream.baseCommit}:${join(relSkillPath, file)}`],
192
+ { cwd: dir }
193
+ );
194
+ } catch {
195
+ continue;
196
+ }
197
+ const currentContent2 = readFileSync(currentPath, "utf-8");
198
+ if (currentContent2 === baseContent2) {
199
+ rmSync(currentPath);
200
+ }
201
+ continue;
202
+ }
203
+ let baseContent;
204
+ try {
205
+ baseContent = await gitExec(
206
+ ["show", `${upstream.baseCommit}:${join(relSkillPath, file)}`],
207
+ { cwd: dir }
208
+ );
209
+ } catch {
210
+ baseContent = "";
211
+ }
212
+ const currentContent = readFileSync(currentPath, "utf-8");
213
+ const newContent = readFileSync(newPath, "utf-8");
214
+ if (currentContent === baseContent) {
215
+ writeFileSync(currentPath, newContent);
216
+ continue;
217
+ }
218
+ if (newContent === baseContent) {
219
+ continue;
220
+ }
221
+ const baseTmp = join(tmpBase, `${file}.base`);
222
+ const currentTmp = join(tmpBase, `${file}.current`);
223
+ const newTmp = join(tmpBase, `${file}.new`);
224
+ mkdirSync(join(tmpBase, ...file.split("/").slice(0, -1)), { recursive: true });
225
+ writeFileSync(baseTmp, baseContent);
226
+ writeFileSync(currentTmp, currentContent);
227
+ writeFileSync(newTmp, newContent);
228
+ try {
229
+ await exec("git", ["merge-file", currentTmp, baseTmp, newTmp]);
230
+ writeFileSync(currentPath, readFileSync(currentTmp, "utf-8"));
231
+ } catch (e) {
232
+ const exitCode = e && typeof e === "object" && "code" in e ? e.code : null;
233
+ if (exitCode === 1) {
234
+ writeFileSync(currentPath, readFileSync(currentTmp, "utf-8"));
235
+ conflictFiles.push(file);
236
+ } else {
237
+ throw e;
238
+ }
239
+ }
240
+ }
241
+ } finally {
242
+ rmSync(tmpBase, { recursive: true, force: true });
243
+ }
244
+ if (conflictFiles.length > 0) {
245
+ return { status: "conflict", conflictFiles };
246
+ }
247
+ const upstreamInfo = {
248
+ source: upstream.source,
249
+ version: shared.version,
250
+ baseCommit: upstream.baseCommit
251
+ // will update after commit
252
+ };
253
+ writeFileSync(join(skillDir, ".upstream.json"), `${JSON.stringify(upstreamInfo, null, 2)}
254
+ `);
255
+ await gitExec(["add", relSkillPath], { cwd: dir });
256
+ await gitExec(["commit", "-m", `Update skill: ${skillId} (v${shared.version})`], { cwd: dir });
257
+ const commitHash = (await gitExec(["rev-parse", "HEAD"], { cwd: dir })).trim();
258
+ upstreamInfo.baseCommit = commitHash;
259
+ writeFileSync(join(skillDir, ".upstream.json"), `${JSON.stringify(upstreamInfo, null, 2)}
260
+ `);
261
+ await gitExec(["add", join(relSkillPath, ".upstream.json")], { cwd: dir });
262
+ await gitExec(["commit", "--amend", "--no-edit"], { cwd: dir });
263
+ return { status: "updated" };
264
+ }
265
+ async function listMindSkills(dir) {
266
+ const skillsDir = mindSkillsDir(dir);
267
+ if (!existsSync(skillsDir)) return [];
268
+ const entries = readdirSync(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
269
+ const sharedMap = /* @__PURE__ */ new Map();
270
+ for (const s of await listSharedSkills()) {
271
+ sharedMap.set(s.id, s);
272
+ }
273
+ const results = [];
274
+ for (const entry of entries) {
275
+ const skillDir = join(skillsDir, entry.name);
276
+ const skillMdPath = join(skillDir, "SKILL.md");
277
+ let name = entry.name;
278
+ let description = "";
279
+ if (existsSync(skillMdPath)) {
280
+ const parsed = parseSkillMd(readFileSync(skillMdPath, "utf-8"));
281
+ if (parsed.name) name = parsed.name;
282
+ description = parsed.description;
283
+ }
284
+ const upstream = readUpstream(skillDir);
285
+ let updateAvailable = false;
286
+ if (upstream) {
287
+ const shared = sharedMap.get(upstream.source);
288
+ if (shared && shared.version > upstream.version) {
289
+ updateAvailable = true;
290
+ }
291
+ }
292
+ results.push({ id: entry.name, name, description, upstream, updateAvailable });
293
+ }
294
+ return results;
295
+ }
296
+ async function publishSkill(mindName, dir, skillId) {
297
+ const skillDir = join(mindSkillsDir(dir), skillId);
298
+ if (!existsSync(skillDir)) throw new Error(`Skill not found: ${skillId}`);
299
+ const skillMdPath = join(skillDir, "SKILL.md");
300
+ if (!existsSync(skillMdPath)) throw new Error(`SKILL.md not found in ${skillId}`);
301
+ return importSkillFromDir(skillDir, mindName);
302
+ }
303
+ function listFilesRecursive(dir, prefix = "") {
304
+ const results = [];
305
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
306
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
307
+ if (entry.isDirectory()) {
308
+ results.push(...listFilesRecursive(join(dir, entry.name), rel));
309
+ } else {
310
+ results.push(rel);
311
+ }
312
+ }
313
+ return results;
314
+ }
315
+ function findSkillsRoot() {
316
+ let dir = dirname(new URL(import.meta.url).pathname);
317
+ for (let i = 0; i < 5; i++) {
318
+ const candidate = resolve(dir, "skills");
319
+ if (existsSync(candidate) && readdirSync(candidate).length > 0) return candidate;
320
+ dir = dirname(dir);
321
+ }
322
+ throw new Error("Skills directory not found");
323
+ }
324
+ function hashSkillDir(dir) {
325
+ const hash = createHash("sha256");
326
+ const files = listFilesRecursive(dir).sort();
327
+ for (const file of files) {
328
+ hash.update(file);
329
+ hash.update(readFileSync(join(dir, file)));
330
+ }
331
+ return hash.digest("hex");
332
+ }
333
+ async function syncBuiltinSkills() {
334
+ let skillsRoot;
335
+ try {
336
+ skillsRoot = findSkillsRoot();
337
+ } catch {
338
+ logger_default.warn("built-in skills directory not found, skipping sync");
339
+ return;
340
+ }
341
+ const entries = readdirSync(skillsRoot, { withFileTypes: true }).filter((e) => e.isDirectory());
342
+ for (const entry of entries) {
343
+ const sourceDir = join(skillsRoot, entry.name);
344
+ if (!existsSync(join(sourceDir, "SKILL.md"))) continue;
345
+ try {
346
+ const sourceHash = hashSkillDir(sourceDir);
347
+ const destDir = join(sharedSkillsDir(), entry.name);
348
+ if (existsSync(destDir)) {
349
+ const destHash = hashSkillDir(destDir);
350
+ if (sourceHash === destHash) continue;
351
+ }
352
+ await importSkillFromDir(sourceDir, "volute");
353
+ logger_default.info(`synced built-in skill: ${entry.name}`);
354
+ } catch (err) {
355
+ logger_default.error(`failed to sync built-in skill: ${entry.name}`, logger_default.errorData(err));
356
+ }
357
+ }
358
+ }
359
+
360
+ export {
361
+ SEED_SKILLS,
362
+ STANDARD_SKILLS,
363
+ sharedSkillsDir,
364
+ listSharedSkills,
365
+ getSharedSkill,
366
+ importSkillFromDir,
367
+ removeSharedSkill,
368
+ installSkill,
369
+ uninstallSkill,
370
+ updateSkill,
371
+ listMindSkills,
372
+ publishSkill,
373
+ listFilesRecursive,
374
+ syncBuiltinSkills
375
+ };
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ __export
4
+ } from "./chunk-K3NQKI34.js";
5
+
6
+ // src/lib/schema.ts
7
+ var schema_exports = {};
8
+ __export(schema_exports, {
9
+ activity: () => activity,
10
+ conversationParticipants: () => conversationParticipants,
11
+ conversations: () => conversations,
12
+ deliveryQueue: () => deliveryQueue,
13
+ messages: () => messages,
14
+ mindHistory: () => mindHistory,
15
+ sessions: () => sessions,
16
+ sharedSkills: () => sharedSkills,
17
+ systemPrompts: () => systemPrompts,
18
+ users: () => users
19
+ });
20
+ import { sql } from "drizzle-orm";
21
+ import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
22
+ var users = sqliteTable("users", {
23
+ id: integer("id").primaryKey({ autoIncrement: true }),
24
+ username: text("username").unique().notNull(),
25
+ password_hash: text("password_hash").notNull(),
26
+ role: text("role").notNull().default("pending"),
27
+ user_type: text("user_type").notNull().default("brain"),
28
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
29
+ });
30
+ var conversations = sqliteTable(
31
+ "conversations",
32
+ {
33
+ id: text("id").primaryKey(),
34
+ mind_name: text("mind_name"),
35
+ channel: text("channel").notNull(),
36
+ type: text("type").notNull().default("dm"),
37
+ name: text("name"),
38
+ user_id: integer("user_id").references(() => users.id),
39
+ title: text("title"),
40
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`),
41
+ updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
42
+ },
43
+ (table) => [
44
+ index("idx_conversations_mind_name").on(table.mind_name),
45
+ index("idx_conversations_user_id").on(table.user_id),
46
+ index("idx_conversations_updated_at").on(table.updated_at),
47
+ uniqueIndex("idx_conversations_name").on(table.name)
48
+ ]
49
+ );
50
+ var mindHistory = sqliteTable(
51
+ "mind_history",
52
+ {
53
+ id: integer("id").primaryKey({ autoIncrement: true }),
54
+ mind: text("mind").notNull(),
55
+ channel: text("channel"),
56
+ session: text("session"),
57
+ sender: text("sender"),
58
+ message_id: text("message_id"),
59
+ type: text("type").notNull(),
60
+ content: text("content"),
61
+ metadata: text("metadata"),
62
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
63
+ },
64
+ (table) => [
65
+ index("idx_mind_history_mind").on(table.mind),
66
+ index("idx_mind_history_mind_channel").on(table.mind, table.channel),
67
+ index("idx_mind_history_mind_type").on(table.mind, table.type)
68
+ ]
69
+ );
70
+ var conversationParticipants = sqliteTable(
71
+ "conversation_participants",
72
+ {
73
+ conversation_id: text("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
74
+ user_id: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
75
+ role: text("role").notNull().default("member"),
76
+ joined_at: text("joined_at").notNull().default(sql`(datetime('now'))`)
77
+ },
78
+ (table) => [
79
+ uniqueIndex("idx_cp_unique").on(table.conversation_id, table.user_id),
80
+ index("idx_cp_user_id").on(table.user_id)
81
+ ]
82
+ );
83
+ var sessions = sqliteTable("sessions", {
84
+ id: text("id").primaryKey(),
85
+ userId: integer("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
86
+ createdAt: integer("created_at").notNull()
87
+ });
88
+ var systemPrompts = sqliteTable("system_prompts", {
89
+ key: text("key").primaryKey(),
90
+ content: text("content").notNull(),
91
+ updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
92
+ });
93
+ var sharedSkills = sqliteTable("shared_skills", {
94
+ id: text("id").primaryKey(),
95
+ name: text("name").notNull(),
96
+ description: text("description").notNull().default(""),
97
+ author: text("author").notNull(),
98
+ version: integer("version").notNull().default(1),
99
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`),
100
+ updated_at: text("updated_at").notNull().default(sql`(datetime('now'))`)
101
+ });
102
+ var deliveryQueue = sqliteTable(
103
+ "delivery_queue",
104
+ {
105
+ id: integer("id").primaryKey({ autoIncrement: true }),
106
+ mind: text("mind").notNull(),
107
+ session: text("session").notNull(),
108
+ channel: text("channel"),
109
+ sender: text("sender"),
110
+ status: text("status").notNull().default("pending"),
111
+ payload: text("payload").notNull(),
112
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
113
+ },
114
+ (table) => [
115
+ index("idx_delivery_queue_mind_session").on(table.mind, table.session),
116
+ index("idx_delivery_queue_mind_status").on(table.mind, table.status)
117
+ ]
118
+ );
119
+ var activity = sqliteTable(
120
+ "activity",
121
+ {
122
+ id: integer("id").primaryKey({ autoIncrement: true }),
123
+ type: text("type").notNull(),
124
+ mind: text("mind").notNull(),
125
+ summary: text("summary").notNull(),
126
+ metadata: text("metadata"),
127
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
128
+ },
129
+ (table) => [
130
+ index("idx_activity_created_at").on(table.created_at),
131
+ index("idx_activity_mind").on(table.mind)
132
+ ]
133
+ );
134
+ var messages = sqliteTable(
135
+ "messages",
136
+ {
137
+ id: integer("id").primaryKey({ autoIncrement: true }),
138
+ conversation_id: text("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
139
+ role: text("role").notNull(),
140
+ sender_name: text("sender_name"),
141
+ content: text("content").notNull(),
142
+ created_at: text("created_at").notNull().default(sql`(datetime('now'))`)
143
+ },
144
+ (table) => [index("idx_messages_conversation_id").on(table.conversation_id)]
145
+ );
146
+
147
+ export {
148
+ users,
149
+ conversations,
150
+ mindHistory,
151
+ conversationParticipants,
152
+ sessions,
153
+ systemPrompts,
154
+ sharedSkills,
155
+ deliveryQueue,
156
+ activity,
157
+ messages,
158
+ schema_exports
159
+ };