volute 0.33.0 → 0.34.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 (182) hide show
  1. package/dist/{accept-D5VBM7JW.js → accept-TW6V4WI4.js} +6 -6
  2. package/dist/{activity-events-XJO3P4RR.js → activity-events-BN7V6KCC.js} +4 -4
  3. package/dist/{ai-service-SBY2WG7O.js → ai-service-PSILB5WD.js} +5 -5
  4. package/dist/{api-client-YPKOZP2O.js → api-client-XUXOB7LI.js} +1 -1
  5. package/dist/api.d.ts +426 -3
  6. package/dist/{archive-INXYFVCW.js → archive-C2VEMQOR.js} +4 -4
  7. package/dist/{auth-GKCDSO4T.js → auth-ZFZXJZDQ.js} +5 -5
  8. package/dist/{bridge-TXWWPPOJ.js → bridge-O753D5F4.js} +6 -6
  9. package/dist/{chat-U5ZOME3O.js → chat-BHYX7DJ4.js} +9 -9
  10. package/dist/{chunk-M7UL5S3Q.js → chunk-2IOP6PHB.js} +1 -1
  11. package/dist/{chunk-NPKSDYA2.js → chunk-47XDEWWV.js} +5 -5
  12. package/dist/{chunk-RSX4OPZY.js → chunk-47ZPNLF4.js} +7 -7
  13. package/dist/{chunk-RPZZSXV3.js → chunk-4JSR7YO7.js} +20 -1
  14. package/dist/{chunk-N432I7QH.js → chunk-6OWJXUAR.js} +1 -1
  15. package/dist/{chunk-I5KY25PQ.js → chunk-6WAWMWR5.js} +1 -1
  16. package/dist/{chunk-NNB4WIG7.js → chunk-7F2SW2KD.js} +2 -2
  17. package/dist/chunk-7KJOFUNN.js +22 -0
  18. package/dist/{chunk-7J3HEVR7.js → chunk-B2BVAIZ4.js} +15 -9
  19. package/dist/{chunk-VH33ZWMW.js → chunk-BDK73LK6.js} +1 -1
  20. package/dist/{chunk-QTUVYI7W.js → chunk-BFWHBQK4.js} +1 -1
  21. package/dist/{chunk-JYVGHWEJ.js → chunk-BM474GX6.js} +3 -3
  22. package/dist/{chunk-LOEJ4HPQ.js → chunk-BTWAGDV5.js} +1 -1
  23. package/dist/{chunk-A2A4KLFE.js → chunk-CVL5IGIR.js} +596 -40
  24. package/dist/{chunk-RVGLDGMI.js → chunk-E5C7OWZ2.js} +20 -22
  25. package/dist/chunk-FYCALD4Q.js +23 -0
  26. package/dist/{chunk-SKLSMHXO.js → chunk-IS7WJ56Q.js} +1 -1
  27. package/dist/{chunk-2NGTS5UU.js → chunk-M3K5AARV.js} +1 -1
  28. package/dist/{chunk-ALEF47VT.js → chunk-MLOQKQNB.js} +1 -1
  29. package/dist/{chunk-C7I35G4R.js → chunk-N3DNFPVA.js} +41 -5
  30. package/dist/{chunk-LRCG2JLP.js → chunk-N7BLAHNE.js} +5 -1
  31. package/dist/{chunk-UKVWJRKN.js → chunk-PLDWHR4D.js} +1 -1
  32. package/dist/{chunk-3Z2DPESO.js → chunk-TAHX36HZ.js} +126 -81
  33. package/dist/{chunk-KIEPMIM5.js → chunk-U5BTYSAL.js} +1 -1
  34. package/dist/{chunk-GY5HBI7A.js → chunk-V45JXOWY.js} +2 -2
  35. package/dist/{chunk-JUKK7FPS.js → chunk-V6ZCNULL.js} +2 -2
  36. package/dist/{chunk-KVK2DLWI.js → chunk-XWXBJQBE.js} +2 -2
  37. package/dist/cli.js +23 -23
  38. package/dist/{clock-BVH3V6E3.js → clock-3X4DSC2N.js} +40 -25
  39. package/dist/{cloud-sync-4NWLMFVH.js → cloud-sync-TG3TIX5H.js} +21 -21
  40. package/dist/{config-H2H4UIF7.js → config-OROA5DUA.js} +4 -4
  41. package/dist/connectors/discord-bridge.js +1 -1
  42. package/dist/connectors/slack-bridge.js +1 -1
  43. package/dist/connectors/telegram-bridge.js +1 -1
  44. package/dist/{conversations-AWI5SZW2.js → conversations-HL2JP5GI.js} +5 -5
  45. package/dist/{create-YWD2TIP4.js → create-3SEKKI6P.js} +6 -6
  46. package/dist/{create-2FK7Z46Y.js → create-UOSOQ2HN.js} +4 -4
  47. package/dist/daemon-client-WOAQXXBM.js +12 -0
  48. package/dist/{daemon-restart-GOBUKLX7.js → daemon-restart-5ABHNXJZ.js} +9 -9
  49. package/dist/daemon.js +1747 -688
  50. package/dist/{db-RA45JBFG.js → db-PLEDCBHZ.js} +1 -1
  51. package/dist/db-RYX3SS2W.js +9 -0
  52. package/dist/{delete-QTGWEDBI.js → delete-KYOVWR23.js} +3 -3
  53. package/dist/delivery-manager-2BR5NZKF.js +32 -0
  54. package/dist/{delivery-router-FL45JL7N.js → delivery-router-D5ELDMS2.js} +4 -4
  55. package/dist/down-QVFN4UPK.js +15 -0
  56. package/dist/{env-JCOF2222.js → env-R34DT7XL.js} +12 -8
  57. package/dist/exec-DVLXKRIO.js +17 -0
  58. package/dist/{export-SUYRLI5Q.js → export-6ZXAXATG.js} +6 -6
  59. package/dist/extension-PM42QCID.js +97 -0
  60. package/dist/extensions-BBGVL5JC.js +38 -0
  61. package/dist/{files-65PMW5IK.js → files-VQV2VZQO.js} +7 -7
  62. package/dist/{import-DDUFE7AY.js → import-MK2I2T6F.js} +5 -5
  63. package/dist/{isolation-LLAYQYDY.js → isolation-62MKDZN3.js} +4 -4
  64. package/dist/{join-I5QEE3LG.js → join-DGYHTJUH.js} +3 -3
  65. package/dist/lib-DYEZMGW7.js +6588 -0
  66. package/dist/{list-JQ463EDA.js → list-C644WTHV.js} +18 -10
  67. package/dist/{login-D7ETSU4R.js → login-IIGEQPHL.js} +6 -6
  68. package/dist/{login-RIJF2F4G.js → login-KZQLMAWE.js} +4 -4
  69. package/dist/{logout-5MLHZALK.js → logout-AGTZVRGP.js} +4 -4
  70. package/dist/{logout-UZJRGY4Z.js → logout-KD6GXIJJ.js} +4 -4
  71. package/dist/message-delivery-V3R6NXJP.js +42 -0
  72. package/dist/{mind-IOJFLEM5.js → mind-BI4EPBVZ.js} +19 -19
  73. package/dist/{mind-activity-tracker-F6O4Q2SL.js → mind-activity-tracker-2ACNHA7B.js} +5 -5
  74. package/dist/mind-history-WOYFLQAI.js +264 -0
  75. package/dist/{mind-list-WUPMQDYQ.js → mind-list-6VPM7GUQ.js} +4 -4
  76. package/dist/mind-manager-MWW3BTS4.js +32 -0
  77. package/dist/{mind-profile-P67FEHOY.js → mind-profile-WPG42U5Y.js} +2 -2
  78. package/dist/mind-service-VIKZJK2M.js +38 -0
  79. package/dist/{mind-sleep-WW2IX7JT.js → mind-sleep-XDISJY74.js} +6 -6
  80. package/dist/{mind-status-L3EFFRPR.js → mind-status-7FTZWPZF.js} +4 -4
  81. package/dist/{mind-wake-VSSGW465.js → mind-wake-KIIKEI3A.js} +6 -6
  82. package/dist/{package-U3VFO273.js → package-V2WHWVG6.js} +8 -5
  83. package/dist/{read-EBY56C33.js → read-H5C26YO7.js} +20 -10
  84. package/dist/{read-stdin-HQJ7774D.js → read-stdin-PIRM6A2Y.js} +1 -1
  85. package/dist/{register-HD74C4TT.js → register-J27WP33N.js} +6 -6
  86. package/dist/{registry-PJ4S5PHQ.js → registry-UYV5S6QT.js} +3 -3
  87. package/dist/{reject-UJKFBHRO.js → reject-OEANJYIA.js} +6 -6
  88. package/dist/{restart-3UCMRUVC.js → restart-V5EGYBJG.js} +4 -4
  89. package/dist/{sandbox-GJOK4QLQ.js → sandbox-SI5HMBP3.js} +5 -5
  90. package/dist/scheduler-AGG3L2FO.js +32 -0
  91. package/dist/{schema-PA3M5ZKH.js → schema-ETMABTW4.js} +4 -2
  92. package/dist/{seed-QDYVLG74.js → seed-WNGI6PNW.js} +2 -2
  93. package/dist/{seed-check-S2IX25RL.js → seed-check-PXTH7YXS.js} +2 -2
  94. package/dist/{seed-cmd-DKOUFEAU.js → seed-cmd-VENFTGS3.js} +4 -4
  95. package/dist/{seed-create-4XBBOLRH.js → seed-create-663ALOKH.js} +6 -6
  96. package/dist/{seed-sprout-GQEIIQRT.js → seed-sprout-EH3AGKAI.js} +12 -12
  97. package/dist/{send-QIV2INHB.js → send-7FUUUZZH.js} +23 -10
  98. package/dist/{setup-TISPCO22.js → setup-GGMKENLN.js} +4 -4
  99. package/dist/{setup-XMCBE3LF.js → setup-Z3DEVWV7.js} +11 -11
  100. package/dist/{skill-PSQGRRJX.js → skill-DKNYJS4P.js} +14 -10
  101. package/dist/skills/plan-coordinator/SKILL.md +60 -0
  102. package/dist/skills/volute-mind/SKILL.md +7 -221
  103. package/dist/skills/volute-mind/references/extensions.md +37 -0
  104. package/dist/skills/volute-mind/references/integrations.md +48 -0
  105. package/dist/skills/volute-mind/references/routing.md +86 -0
  106. package/dist/skills/volute-mind/references/sleep.md +33 -0
  107. package/dist/skills/volute-mind/references/variants.md +31 -0
  108. package/dist/{skills-7FV7EJTE.js → skills-Q6VZ2UGD.js} +11 -7
  109. package/dist/sleep-manager-BJK2ROPX.js +36 -0
  110. package/dist/spirit-4JP4TY4C.js +23 -0
  111. package/dist/{split-STOROBYJ.js → split-3YPMS2CL.js} +3 -3
  112. package/dist/{sprout-WKLZXUIQ.js → sprout-E3HJIV2Z.js} +2 -2
  113. package/dist/{start-K2NCUUCG.js → start-W3TPKX4D.js} +4 -4
  114. package/dist/{status-3JBTFSMI.js → status-4OVFXFEJ.js} +7 -7
  115. package/dist/{stop-H26JZDXF.js → stop-GTT6YWYO.js} +4 -4
  116. package/dist/system-channel-DXD2JBOU.js +36 -0
  117. package/dist/system-chat-TYLOL7SX.js +36 -0
  118. package/dist/{systems-XRI52VCH.js → systems-AYLO727G.js} +7 -7
  119. package/dist/{tailscale-XHQBZROW.js → tailscale-ZEUK7GKZ.js} +3 -3
  120. package/dist/{template-hash-A6VVKOXJ.js → template-hash-EJRTKE36.js} +1 -1
  121. package/dist/up-PA7F2CXE.js +18 -0
  122. package/dist/{update-UD543CXX.js → update-HG4LCUSG.js} +7 -7
  123. package/dist/{update-check-ZD6OOIYQ.js → update-check-X3YG4WVP.js} +4 -4
  124. package/dist/{upgrade-O4Q7WJM3.js → upgrade-YGNIDICG.js} +3 -3
  125. package/dist/{variant-7TGZHOU3.js → variant-MZUMRTQO.js} +1 -1
  126. package/dist/{version-notify-NBI2MTJO.js → version-notify-YCH4UVQ2.js} +19 -19
  127. package/dist/{volute-config-HD7WWUQC.js → volute-config-WBKYJGYQ.js} +1 -1
  128. package/dist/web-assets/assets/index-DiiwC-CZ.css +1 -0
  129. package/dist/web-assets/assets/index-d6y5b9Ij.js +75 -0
  130. package/dist/web-assets/ext-theme.css +48 -9
  131. package/dist/web-assets/index.html +2 -2
  132. package/drizzle/0005_meta_summaries.sql +15 -0
  133. package/drizzle/meta/0005_snapshot.json +7 -0
  134. package/drizzle/meta/_journal.json +7 -0
  135. package/package.json +7 -4
  136. package/packages/extensions/plan/dist/ui/assets/index-CJj2gZnZ.css +1 -0
  137. package/packages/extensions/plan/dist/ui/assets/index-FMEJmvQz.js +61 -0
  138. package/packages/extensions/plan/dist/ui/index.html +14 -0
  139. package/packages/extensions/plan/skills/plan/SKILL.md +43 -0
  140. package/packages/extensions/plan/skills/plan/scripts/plan-hook.sh +37 -0
  141. package/templates/_base/home/VOLUTE.md +12 -19
  142. package/templates/_base/src/lib/context-breakdown.ts +450 -0
  143. package/templates/_base/src/lib/format-prefix.ts +17 -0
  144. package/templates/_base/src/lib/hook-loader.ts +8 -2
  145. package/templates/_base/src/lib/router.ts +75 -33
  146. package/templates/_base/src/lib/routing.ts +4 -1
  147. package/templates/_base/src/lib/startup.ts +16 -8
  148. package/templates/_base/src/lib/types.ts +2 -1
  149. package/templates/_base/src/lib/volute-server.ts +69 -8
  150. package/templates/claude/.init/CLAUDE.md +4 -10
  151. package/templates/claude/package.json.tmpl +1 -0
  152. package/templates/claude/src/agent.ts +100 -32
  153. package/templates/claude/src/lib/hooks/reply-instructions.ts +27 -7
  154. package/templates/claude/src/lib/stream-consumer.ts +2 -2
  155. package/templates/claude/src/server.ts +1 -0
  156. package/templates/codex/package.json.tmpl +1 -0
  157. package/templates/codex/src/agent.ts +80 -8
  158. package/templates/codex/src/server.ts +1 -4
  159. package/templates/pi/package.json.tmpl +1 -0
  160. package/templates/pi/src/agent.ts +115 -36
  161. package/templates/pi/src/lib/event-handler.ts +22 -7
  162. package/templates/pi/src/lib/reply-instructions-extension.ts +23 -4
  163. package/templates/pi/src/lib/subagents.ts +20 -17
  164. package/templates/pi/src/server.ts +2 -5
  165. package/dist/chunk-K3NQKI34.js +0 -10
  166. package/dist/daemon-client-6QXHZ7US.js +0 -12
  167. package/dist/db-F34YLV7D.js +0 -9
  168. package/dist/delivery-manager-PFAKEJTC.js +0 -32
  169. package/dist/down-FWWTEKXM.js +0 -15
  170. package/dist/extension-OBTGKQQD.js +0 -175
  171. package/dist/extensions-KYNTVTMO.js +0 -30
  172. package/dist/history-DKCDI3JO.js +0 -128
  173. package/dist/message-delivery-DFF5SJRM.js +0 -42
  174. package/dist/mind-manager-NBJF5D26.js +0 -32
  175. package/dist/mind-service-2MQ6UK5N.js +0 -38
  176. package/dist/scheduler-ZZ7XGQG6.js +0 -32
  177. package/dist/sleep-manager-JTXSN7NV.js +0 -36
  178. package/dist/spirit-VRONKFMF.js +0 -23
  179. package/dist/system-chat-JAPOJ3KE.js +0 -36
  180. package/dist/up-M5AS6SBV.js +0 -18
  181. package/dist/web-assets/assets/index-CWJrVveV.css +0 -1
  182. package/dist/web-assets/assets/index-DJt14FRI.js +0 -75
@@ -1,4 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ hashSkillDir,
4
+ importSkillFromDir,
5
+ removeSharedSkill,
6
+ sharedSkillsDir
7
+ } from "./chunk-N3DNFPVA.js";
2
8
  import {
3
9
  getAllSites,
4
10
  getPublishedPages,
@@ -9,27 +15,26 @@ import {
9
15
  import {
10
16
  getUser,
11
17
  getUserByUsername
12
- } from "./chunk-JYVGHWEJ.js";
18
+ } from "./chunk-BM474GX6.js";
13
19
  import {
14
20
  publish
15
- } from "./chunk-KVK2DLWI.js";
16
- import {
17
- hashSkillDir,
18
- importSkillFromDir,
19
- sharedSkillsDir
20
- } from "./chunk-C7I35G4R.js";
21
+ } from "./chunk-XWXBJQBE.js";
21
22
  import {
22
23
  logger_default
23
24
  } from "./chunk-YUIHSKR6.js";
25
+ import {
26
+ readGlobalConfig,
27
+ writeGlobalConfig
28
+ } from "./chunk-6OWJXUAR.js";
24
29
  import {
25
30
  mindDir,
26
31
  voluteHome,
27
32
  voluteSystemDir
28
- } from "./chunk-LRCG2JLP.js";
33
+ } from "./chunk-N7BLAHNE.js";
29
34
 
30
35
  // src/lib/extensions.ts
31
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
32
- import { dirname, resolve as resolve6 } from "path";
36
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
37
+ import { dirname, resolve as resolve7 } from "path";
33
38
 
34
39
  // packages/extensions/notes/src/index.ts
35
40
  import { resolve } from "path";
@@ -745,7 +750,7 @@ Warning: remote publish failed: ${err.message}`;
745
750
  const allFlag = args.includes("--all");
746
751
  const port = process.env.VOLUTE_DAEMON_PORT || "1618";
747
752
  if (allFlag) {
748
- const { getAllSites: getAllSites2 } = await import("./db-RA45JBFG.js");
753
+ const { getAllSites: getAllSites2 } = await import("./db-PLEDCBHZ.js");
749
754
  const sites = getAllSites2(db);
750
755
  const lines2 = [];
751
756
  for (const site of sites) {
@@ -942,7 +947,7 @@ function createRoutes2(ctx) {
942
947
  var _voluteHome = null;
943
948
  async function getVoluteHome() {
944
949
  if (_voluteHome) return _voluteHome();
945
- const mod = await import("./registry-PJ4S5PHQ.js");
950
+ const mod = await import("./registry-UYV5S6QT.js");
946
951
  _voluteHome = mod.voluteHome;
947
952
  return _voluteHome();
948
953
  }
@@ -1021,12 +1026,438 @@ var src_default2 = createExtension({
1021
1026
  }
1022
1027
  });
1023
1028
 
1029
+ // packages/extensions/plan/src/index.ts
1030
+ import { resolve as resolve5 } from "path";
1031
+
1032
+ // packages/extensions/plan/src/plans.ts
1033
+ async function startPlan(db, getUser2, userId, title, description) {
1034
+ db.prepare(
1035
+ "UPDATE plans SET status = 'archived', completed_at = datetime('now') WHERE status = 'active'"
1036
+ ).run();
1037
+ const row = db.prepare(
1038
+ `INSERT INTO plans (title, description, set_by)
1039
+ VALUES (?, ?, ?)
1040
+ RETURNING *`
1041
+ ).get(title, description, userId);
1042
+ const user = await getUser2(userId);
1043
+ return {
1044
+ ...row,
1045
+ set_by_username: user?.username ?? "unknown",
1046
+ set_by_display_name: user?.display_name ?? null
1047
+ };
1048
+ }
1049
+ async function getActivePlan(db, getUser2) {
1050
+ const row = db.prepare("SELECT * FROM plans WHERE status = 'active' LIMIT 1").get();
1051
+ if (!row) return null;
1052
+ const user = await getUser2(row.set_by);
1053
+ const logs = db.prepare("SELECT * FROM plan_logs WHERE plan_id = ? ORDER BY created_at DESC LIMIT 20").all(row.id);
1054
+ const messages = db.prepare("SELECT * FROM plan_messages WHERE plan_id = ? ORDER BY id DESC LIMIT 10").all(row.id);
1055
+ const latestMessage = messages.length > 0 ? messages[0].content : null;
1056
+ return {
1057
+ ...row,
1058
+ set_by_username: user?.username ?? "unknown",
1059
+ set_by_display_name: user?.display_name ?? null,
1060
+ logs,
1061
+ messages,
1062
+ latestMessage
1063
+ };
1064
+ }
1065
+ function logProgress(db, planId, mindName, content) {
1066
+ return db.prepare(
1067
+ `INSERT INTO plan_logs (plan_id, mind_name, content)
1068
+ VALUES (?, ?, ?)
1069
+ RETURNING *`
1070
+ ).get(planId, mindName, content);
1071
+ }
1072
+ function addPlanMessage(db, planId, content) {
1073
+ return db.prepare(
1074
+ `INSERT INTO plan_messages (plan_id, content)
1075
+ VALUES (?, ?)
1076
+ RETURNING *`
1077
+ ).get(planId, content);
1078
+ }
1079
+ function finishPlan(db, planId, message) {
1080
+ const result = db.prepare(
1081
+ "UPDATE plans SET status = 'completed', completed_at = datetime('now'), finish_message = ? WHERE id = ? AND status = 'active'"
1082
+ ).run(message ?? null, planId);
1083
+ return result.changes > 0;
1084
+ }
1085
+ async function listPlans(db, getUser2, opts) {
1086
+ const limit = opts?.limit ?? 20;
1087
+ const offset = opts?.offset ?? 0;
1088
+ const rows = opts?.status ? db.prepare("SELECT * FROM plans WHERE status = ? ORDER BY created_at DESC LIMIT ? OFFSET ?").all(opts.status, limit, offset) : db.prepare("SELECT * FROM plans ORDER BY created_at DESC LIMIT ? OFFSET ?").all(limit, offset);
1089
+ const userCache = /* @__PURE__ */ new Map();
1090
+ const result = [];
1091
+ for (const row of rows) {
1092
+ if (!userCache.has(row.set_by)) {
1093
+ const u = await getUser2(row.set_by);
1094
+ userCache.set(row.set_by, {
1095
+ username: u?.username ?? "unknown",
1096
+ display_name: u?.display_name ?? null
1097
+ });
1098
+ }
1099
+ const userInfo = userCache.get(row.set_by);
1100
+ result.push({
1101
+ ...row,
1102
+ set_by_username: userInfo.username,
1103
+ set_by_display_name: userInfo.display_name
1104
+ });
1105
+ }
1106
+ return result;
1107
+ }
1108
+
1109
+ // packages/extensions/plan/src/commands.ts
1110
+ function getFlag2(args, flag) {
1111
+ const idx = args.indexOf(flag);
1112
+ if (idx !== -1 && args[idx + 1]) return args[idx + 1];
1113
+ return void 0;
1114
+ }
1115
+ var _announce = null;
1116
+ async function announceToSystem(text) {
1117
+ if (!_announce) {
1118
+ try {
1119
+ const mod = await import("./system-channel-DXD2JBOU.js");
1120
+ _announce = mod.announceToSystem;
1121
+ } catch {
1122
+ return false;
1123
+ }
1124
+ }
1125
+ try {
1126
+ await _announce(text);
1127
+ return true;
1128
+ } catch (err) {
1129
+ console.error("[plan] Failed to announce to system channel:", err);
1130
+ return false;
1131
+ }
1132
+ }
1133
+ function createCommands3() {
1134
+ return {
1135
+ start: {
1136
+ description: "Start a new system plan",
1137
+ usage: 'volute plan start "title" "description" (description can be piped via stdin)',
1138
+ handler: async (args, ctx) => {
1139
+ if (!ctx.db) return { error: "Plan extension requires a database" };
1140
+ const mindName = ctx.mindName;
1141
+ if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
1142
+ const user = await ctx.getUserByUsername(mindName);
1143
+ if (!user) return { error: `Unknown mind: ${mindName}` };
1144
+ const title = args[0];
1145
+ const description = args[1] ?? ctx.stdin ?? "";
1146
+ if (!title) return { error: 'Usage: volute plan start "title" "description"' };
1147
+ const plan = await startPlan(ctx.db, ctx.getUser, user.id, title, description);
1148
+ ctx.publishActivity({
1149
+ type: "plan_started",
1150
+ mind: user.username,
1151
+ summary: `${user.username} started plan: "${title}"`,
1152
+ metadata: { planId: plan.id, title }
1153
+ });
1154
+ return { output: `Plan started: ${plan.title}` };
1155
+ }
1156
+ },
1157
+ message: {
1158
+ description: "Post a message about the current plan (sent to #system)",
1159
+ usage: `volute plan message "today's focus: ..." (content can be piped via stdin)`,
1160
+ handler: async (args, ctx) => {
1161
+ if (!ctx.db) return { error: "Plan extension requires a database" };
1162
+ const mindName = ctx.mindName;
1163
+ if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
1164
+ const content = args[0] ?? ctx.stdin;
1165
+ if (!content) return { error: 'Usage: volute plan message "your message"' };
1166
+ const plan = await getActivePlan(ctx.db, ctx.getUser);
1167
+ if (!plan) return { error: "No active plan" };
1168
+ const msg = addPlanMessage(ctx.db, plan.id, content);
1169
+ ctx.publishActivity({
1170
+ type: "plan_message",
1171
+ mind: mindName,
1172
+ summary: `Plan message: "${content.slice(0, 100)}"`,
1173
+ metadata: { planId: plan.id, messageId: msg.id }
1174
+ });
1175
+ const announced = await announceToSystem(`[Plan: ${plan.title}] ${content}`);
1176
+ return {
1177
+ output: announced ? "Message posted to #system." : "Message logged (system channel unavailable)."
1178
+ };
1179
+ }
1180
+ },
1181
+ log: {
1182
+ description: "Log progress on the current plan",
1183
+ usage: 'volute plan log "progress update" (content can be piped via stdin)',
1184
+ handler: async (args, ctx) => {
1185
+ if (!ctx.db) return { error: "Plan extension requires a database" };
1186
+ const mindName = ctx.mindName;
1187
+ if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
1188
+ const content = args[0] ?? ctx.stdin;
1189
+ if (!content) return { error: 'Usage: volute plan log "progress update"' };
1190
+ const plan = await getActivePlan(ctx.db, ctx.getUser);
1191
+ if (!plan) return { error: "No active plan" };
1192
+ const log = logProgress(ctx.db, plan.id, mindName, content);
1193
+ ctx.publishActivity({
1194
+ type: "plan_progress",
1195
+ mind: mindName,
1196
+ summary: `${mindName} logged progress: "${content.slice(0, 100)}"`,
1197
+ metadata: { planId: plan.id, logId: log.id }
1198
+ });
1199
+ return { output: "Progress logged." };
1200
+ }
1201
+ },
1202
+ current: {
1203
+ description: "Show the current active plan",
1204
+ usage: "volute plan current",
1205
+ handler: async (_args, ctx) => {
1206
+ if (!ctx.db) return { error: "Plan extension requires a database" };
1207
+ const plan = await getActivePlan(ctx.db, ctx.getUser);
1208
+ if (!plan) return { output: "No active plan." };
1209
+ const lines = [
1210
+ `# ${plan.title}`,
1211
+ "",
1212
+ `Set by ${plan.set_by_username} \u2014 ${new Date(plan.created_at).toLocaleString()}`
1213
+ ];
1214
+ if (plan.description) {
1215
+ lines.push("", plan.description);
1216
+ }
1217
+ if (plan.latestMessage) {
1218
+ lines.push("", `## Latest message`, "", plan.latestMessage);
1219
+ }
1220
+ if (plan.logs.length > 0) {
1221
+ lines.push("", "## Progress");
1222
+ for (const log of plan.logs) {
1223
+ const date = new Date(log.created_at).toLocaleString();
1224
+ lines.push(` ${log.mind_name} (${date}): ${log.content}`);
1225
+ }
1226
+ }
1227
+ return { output: lines.join("\n") };
1228
+ }
1229
+ },
1230
+ history: {
1231
+ description: "List past plans",
1232
+ usage: "volute plan history [--limit N]",
1233
+ handler: async (args, ctx) => {
1234
+ if (!ctx.db) return { error: "Plan extension requires a database" };
1235
+ const limit = parseInt(getFlag2(args, "--limit") ?? "10", 10);
1236
+ const plans = await listPlans(ctx.db, ctx.getUser, { limit });
1237
+ if (plans.length === 0) return { output: "No plans found." };
1238
+ const lines = plans.map((p) => {
1239
+ const date = new Date(p.created_at).toLocaleDateString();
1240
+ const status = p.status === "active" ? " [active]" : "";
1241
+ return ` ${p.title} (${date}, by ${p.set_by_username})${status}`;
1242
+ });
1243
+ return { output: lines.join("\n") };
1244
+ }
1245
+ },
1246
+ finish: {
1247
+ description: "Finish the current plan with a closing message",
1248
+ usage: 'volute plan finish "closing message" (message can be piped via stdin)',
1249
+ handler: async (args, ctx) => {
1250
+ if (!ctx.db) return { error: "Plan extension requires a database" };
1251
+ const mindName = ctx.mindName;
1252
+ if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
1253
+ const plan = await getActivePlan(ctx.db, ctx.getUser);
1254
+ if (!plan) return { error: "No active plan" };
1255
+ const message = args[0] ?? ctx.stdin ?? "";
1256
+ finishPlan(ctx.db, plan.id, message);
1257
+ ctx.publishActivity({
1258
+ type: "plan_finished",
1259
+ mind: mindName,
1260
+ summary: `${mindName} finished plan: "${plan.title}"`,
1261
+ metadata: { planId: plan.id }
1262
+ });
1263
+ const announcement = message ? `[Plan finished: ${plan.title}] ${message}` : `[Plan finished: ${plan.title}]`;
1264
+ const announced = await announceToSystem(announcement);
1265
+ const suffix = announced ? "" : " (system channel unavailable)";
1266
+ return { output: `Finished: ${plan.title}${suffix}` };
1267
+ }
1268
+ }
1269
+ };
1270
+ }
1271
+
1272
+ // packages/extensions/plan/src/db.ts
1273
+ function initDb3(db) {
1274
+ db.exec(`
1275
+ CREATE TABLE IF NOT EXISTS plans (
1276
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1277
+ title TEXT NOT NULL,
1278
+ description TEXT NOT NULL DEFAULT '',
1279
+ status TEXT NOT NULL DEFAULT 'active',
1280
+ set_by INTEGER NOT NULL,
1281
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1282
+ completed_at TEXT,
1283
+ finish_message TEXT
1284
+ );
1285
+ CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status);
1286
+ CREATE INDEX IF NOT EXISTS idx_plans_created_at ON plans(created_at);
1287
+
1288
+ CREATE TABLE IF NOT EXISTS plan_logs (
1289
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1290
+ plan_id INTEGER NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
1291
+ mind_name TEXT NOT NULL,
1292
+ content TEXT NOT NULL,
1293
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1294
+ );
1295
+ CREATE INDEX IF NOT EXISTS idx_plan_logs_plan_id ON plan_logs(plan_id);
1296
+
1297
+ CREATE TABLE IF NOT EXISTS plan_messages (
1298
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1299
+ plan_id INTEGER NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
1300
+ content TEXT NOT NULL,
1301
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1302
+ );
1303
+ CREATE INDEX IF NOT EXISTS idx_plan_messages_plan_id ON plan_messages(plan_id);
1304
+ `);
1305
+ }
1306
+
1307
+ // packages/extensions/plan/src/routes.ts
1308
+ import { Hono as Hono3 } from "hono";
1309
+ function resolveUserId2(c) {
1310
+ const user = c.get("user");
1311
+ if (!user || user.id === 0) return null;
1312
+ return user;
1313
+ }
1314
+ async function parseJson2(c) {
1315
+ try {
1316
+ return await c.req.json();
1317
+ } catch {
1318
+ return null;
1319
+ }
1320
+ }
1321
+ function createRoutes3(ctx) {
1322
+ if (!ctx.db) throw new Error("Plan extension requires a database");
1323
+ const db = ctx.db;
1324
+ const { getUser: getUser2 } = ctx;
1325
+ const app = new Hono3().get("/current", async (c) => {
1326
+ const plan = await getActivePlan(db, getUser2);
1327
+ if (!plan) return c.json(null);
1328
+ return c.json(plan);
1329
+ }).get("/", async (c) => {
1330
+ const status = c.req.query("status");
1331
+ const rawLimit = c.req.query("limit");
1332
+ const rawOffset = c.req.query("offset");
1333
+ const limit = rawLimit ? parseInt(rawLimit, 10) : void 0;
1334
+ const offset = rawOffset ? parseInt(rawOffset, 10) : void 0;
1335
+ if (limit !== void 0 && Number.isNaN(limit) || offset !== void 0 && Number.isNaN(offset)) {
1336
+ return c.json({ error: "Invalid limit or offset parameter" }, 400);
1337
+ }
1338
+ const plans = await listPlans(db, getUser2, { status: status ?? void 0, limit, offset });
1339
+ return c.json(plans);
1340
+ }).post("/", async (c) => {
1341
+ const actor = resolveUserId2(c);
1342
+ if (!actor) return c.json({ error: "Unauthorized" }, 401);
1343
+ if (actor.role !== "admin" && actor.user_type !== "mind") {
1344
+ return c.json({ error: "Only spirit or admin can start plans" }, 403);
1345
+ }
1346
+ const body = await parseJson2(c);
1347
+ if (!body) return c.json({ error: "Invalid JSON body" }, 400);
1348
+ if (!body.title) return c.json({ error: "title is required" }, 400);
1349
+ const plan = await startPlan(db, getUser2, actor.id, body.title, body.description ?? "");
1350
+ ctx.publishActivity({
1351
+ type: "plan_started",
1352
+ mind: actor.username,
1353
+ summary: `${actor.username} started plan: "${body.title}"`,
1354
+ metadata: { planId: plan.id, title: body.title }
1355
+ });
1356
+ return c.json(plan, 201);
1357
+ }).post("/:id{[0-9]+}/message", async (c) => {
1358
+ const actor = resolveUserId2(c);
1359
+ if (!actor) return c.json({ error: "Unauthorized" }, 401);
1360
+ const planId = parseInt(c.req.param("id"), 10);
1361
+ if (Number.isNaN(planId)) return c.json({ error: "Invalid plan ID" }, 400);
1362
+ const plan = db.prepare("SELECT id FROM plans WHERE id = ? AND status = 'active'").get(planId);
1363
+ if (!plan) return c.json({ error: "Active plan not found" }, 404);
1364
+ const body = await parseJson2(c);
1365
+ if (!body) return c.json({ error: "Invalid JSON body" }, 400);
1366
+ if (!body.content) return c.json({ error: "content is required" }, 400);
1367
+ const msg = addPlanMessage(db, planId, body.content);
1368
+ ctx.publishActivity({
1369
+ type: "plan_message",
1370
+ mind: actor.username,
1371
+ summary: `Plan message: "${body.content.slice(0, 100)}"`,
1372
+ metadata: { planId, messageId: msg.id }
1373
+ });
1374
+ return c.json(msg, 201);
1375
+ }).post("/:id{[0-9]+}/log", async (c) => {
1376
+ const actor = resolveUserId2(c);
1377
+ if (!actor) return c.json({ error: "Unauthorized" }, 401);
1378
+ const planId = parseInt(c.req.param("id"), 10);
1379
+ if (Number.isNaN(planId)) return c.json({ error: "Invalid plan ID" }, 400);
1380
+ const plan = db.prepare("SELECT id FROM plans WHERE id = ? AND status = 'active'").get(planId);
1381
+ if (!plan) return c.json({ error: "Active plan not found" }, 404);
1382
+ const body = await parseJson2(c);
1383
+ if (!body) return c.json({ error: "Invalid JSON body" }, 400);
1384
+ if (!body.content) return c.json({ error: "content is required" }, 400);
1385
+ const log = logProgress(db, planId, actor.username, body.content);
1386
+ ctx.publishActivity({
1387
+ type: "plan_progress",
1388
+ mind: actor.username,
1389
+ summary: `${actor.username} logged progress: "${body.content.slice(0, 100)}"`,
1390
+ metadata: { planId, logId: log.id }
1391
+ });
1392
+ return c.json(log, 201);
1393
+ }).patch("/:id{[0-9]+}/finish", async (c) => {
1394
+ const actor = resolveUserId2(c);
1395
+ if (!actor) return c.json({ error: "Unauthorized" }, 401);
1396
+ if (actor.role !== "admin" && actor.user_type !== "mind") {
1397
+ return c.json({ error: "Only spirit or admin can finish plans" }, 403);
1398
+ }
1399
+ const planId = parseInt(c.req.param("id"), 10);
1400
+ if (Number.isNaN(planId)) return c.json({ error: "Invalid plan ID" }, 400);
1401
+ const body = await parseJson2(c);
1402
+ const message = body?.message;
1403
+ const ok = finishPlan(db, planId, message);
1404
+ if (!ok) return c.json({ error: "Plan not found" }, 404);
1405
+ ctx.publishActivity({
1406
+ type: "plan_finished",
1407
+ mind: actor.username,
1408
+ summary: `${actor.username} finished a plan`,
1409
+ metadata: { planId }
1410
+ });
1411
+ return c.json({ ok: true });
1412
+ }).get("/feed", async (c) => {
1413
+ const rawLimit = c.req.query("limit");
1414
+ const limit = rawLimit ? parseInt(rawLimit, 10) : 5;
1415
+ if (Number.isNaN(limit)) return c.json({ error: "Invalid limit parameter" }, 400);
1416
+ const plans = await listPlans(db, getUser2, { limit });
1417
+ return c.json(
1418
+ plans.map((p) => ({
1419
+ id: `plan-${p.id}`,
1420
+ title: p.title,
1421
+ url: `/plan`,
1422
+ date: p.created_at,
1423
+ author: p.set_by_username,
1424
+ bodyHtml: p.description || `<em>${p.status}</em>`,
1425
+ icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><path d="M8 4v4l2.5 2.5"/></svg>',
1426
+ color: "blue"
1427
+ }))
1428
+ );
1429
+ });
1430
+ return app;
1431
+ }
1432
+
1433
+ // packages/extensions/plan/src/index.ts
1434
+ var assetsDir3 = resolve5(import.meta.dirname, "../dist/ui");
1435
+ var skillsDir3 = resolve5(import.meta.dirname, "../skills");
1436
+ var src_default3 = createExtension({
1437
+ id: "plan",
1438
+ name: "Plan",
1439
+ version: "0.1.0",
1440
+ description: "System-wide plans for coordinated mind activity",
1441
+ icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><path d="M8 4v4l2.5 2.5"/></svg>',
1442
+ color: "blue",
1443
+ routes: (ctx) => createRoutes3(ctx),
1444
+ commands: createCommands3(),
1445
+ initDb: initDb3,
1446
+ skillsDir: skillsDir3,
1447
+ standardSkill: true,
1448
+ ui: {
1449
+ assetsDir: assetsDir3,
1450
+ systemSection: { id: "plan", label: "Plan", urlPatterns: ["/plan"] },
1451
+ feedSource: { endpoint: "/api/ext/plan/feed" }
1452
+ }
1453
+ });
1454
+
1024
1455
  // src/lib/systems-config.ts
1025
1456
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
1026
- import { resolve as resolve5 } from "path";
1457
+ import { resolve as resolve6 } from "path";
1027
1458
  var DEFAULT_API_URL = "https://volute.systems";
1028
1459
  function configPath() {
1029
- return resolve5(voluteSystemDir(), "systems.json");
1460
+ return resolve6(voluteSystemDir(), "systems.json");
1030
1461
  }
1031
1462
  function readSystemsConfig() {
1032
1463
  const path = configPath();
@@ -1066,14 +1497,15 @@ function deleteSystemsConfig() {
1066
1497
  // src/lib/extensions.ts
1067
1498
  var VALID_EXTENSION_ID2 = /^[a-z0-9][a-z0-9_-]*$/;
1068
1499
  var loaded = [];
1500
+ var discovered = [];
1069
1501
  function extensionsBaseDir() {
1070
- return resolve6(voluteHome(), "extensions");
1502
+ return resolve7(voluteHome(), "extensions");
1071
1503
  }
1072
1504
  function extensionDataDir(id) {
1073
- return resolve6(voluteSystemDir(), "extension-data", id);
1505
+ return resolve7(voluteSystemDir(), "extension-data", id);
1074
1506
  }
1075
1507
  function extensionsConfigPath() {
1076
- return resolve6(voluteHome(), "system", "extensions.json");
1508
+ return resolve7(voluteHome(), "system", "extensions.json");
1077
1509
  }
1078
1510
  function readExtensionsConfig() {
1079
1511
  const configPath2 = extensionsConfigPath();
@@ -1097,7 +1529,7 @@ async function getLibsqlDatabase() {
1097
1529
  return _LibsqlDatabase;
1098
1530
  }
1099
1531
  async function openExtensionDb(_id, dataDir) {
1100
- const dbPath = resolve6(dataDir, "data.db");
1532
+ const dbPath = resolve7(dataDir, "data.db");
1101
1533
  const Database = await getLibsqlDatabase();
1102
1534
  return new Database(dbPath);
1103
1535
  }
@@ -1223,7 +1655,7 @@ async function loadExtension(manifest, app, authMw) {
1223
1655
  if (resolvedAssetsDir && !existsSync3(resolvedAssetsDir)) {
1224
1656
  let searchDir = dirname(new URL(import.meta.url).pathname);
1225
1657
  for (let i = 0; i < 5; i++) {
1226
- const candidate = resolve6(searchDir, "packages", "extensions", manifest.id, "dist", "ui");
1658
+ const candidate = resolve7(searchDir, "packages", "extensions", manifest.id, "dist", "ui");
1227
1659
  if (existsSync3(candidate)) {
1228
1660
  resolvedAssetsDir = candidate;
1229
1661
  break;
@@ -1232,7 +1664,7 @@ async function loadExtension(manifest, app, authMw) {
1232
1664
  }
1233
1665
  }
1234
1666
  if (resolvedAssetsDir && existsSync3(resolvedAssetsDir)) {
1235
- const assetsDir3 = resolvedAssetsDir;
1667
+ const assetsDir4 = resolvedAssetsDir;
1236
1668
  const { readFile: readFile2, stat: fsStat } = await import("fs/promises");
1237
1669
  const { extname: ext } = await import("path");
1238
1670
  const mimeTypes = {
@@ -1248,12 +1680,12 @@ async function loadExtension(manifest, app, authMw) {
1248
1680
  ".woff2": "font/woff2"
1249
1681
  };
1250
1682
  const prefix = `/ext/${manifest.id}`;
1251
- const indexPath = resolve6(assetsDir3, "index.html");
1683
+ const indexPath = resolve7(assetsDir4, "index.html");
1252
1684
  const serveExtAssets = async (c) => {
1253
1685
  const urlPath = new URL(c.req.url).pathname;
1254
1686
  const relativePath = urlPath.slice(prefix.length).replace(/^\//, "") || "index.html";
1255
- const filePath = resolve6(assetsDir3, relativePath);
1256
- if (filePath !== assetsDir3 && !filePath.startsWith(assetsDir3 + "/"))
1687
+ const filePath = resolve7(assetsDir4, relativePath);
1688
+ if (filePath !== assetsDir4 && !filePath.startsWith(assetsDir4 + "/"))
1257
1689
  return c.text("Forbidden", 403);
1258
1690
  const s = await fsStat(filePath).catch(() => null);
1259
1691
  if (s?.isFile()) {
@@ -1270,11 +1702,11 @@ async function loadExtension(manifest, app, authMw) {
1270
1702
  app.get(`${prefix}/*`, serveExtAssets);
1271
1703
  app.get(prefix, serveExtAssets);
1272
1704
  }
1273
- const skillsDir3 = resolveSkillsDir(manifest);
1274
- if (skillsDir3) {
1705
+ const skillsDir4 = resolveSkillsDir(manifest);
1706
+ if (skillsDir4) {
1275
1707
  let entries;
1276
1708
  try {
1277
- entries = readdirSync2(skillsDir3, { withFileTypes: true });
1709
+ entries = readdirSync2(skillsDir4, { withFileTypes: true });
1278
1710
  } catch (err) {
1279
1711
  logger_default.error(`failed to read skills dir for extension ${manifest.id}`, logger_default.errorData(err));
1280
1712
  entries = [];
@@ -1282,9 +1714,9 @@ async function loadExtension(manifest, app, authMw) {
1282
1714
  for (const entry of entries) {
1283
1715
  if (!entry.isDirectory()) continue;
1284
1716
  try {
1285
- const skillPath = resolve6(skillsDir3, entry.name);
1717
+ const skillPath = resolve7(skillsDir4, entry.name);
1286
1718
  const sourceHash = hashSkillDir(skillPath);
1287
- const destDir = resolve6(sharedSkillsDir(), entry.name);
1719
+ const destDir = resolve7(sharedSkillsDir(), entry.name);
1288
1720
  if (existsSync3(destDir)) {
1289
1721
  const destHash = hashSkillDir(destDir);
1290
1722
  if (sourceHash === destHash) continue;
@@ -1309,7 +1741,7 @@ function resolveSkillsDir(manifest) {
1309
1741
  if (!manifest.skillsDir) return null;
1310
1742
  let searchDir = dirname(new URL(import.meta.url).pathname);
1311
1743
  for (let i = 0; i < 5; i++) {
1312
- const candidate = resolve6(searchDir, "packages", "extensions", manifest.id, "skills");
1744
+ const candidate = resolve7(searchDir, "packages", "extensions", manifest.id, "skills");
1313
1745
  if (existsSync3(candidate)) return candidate;
1314
1746
  searchDir = dirname(searchDir);
1315
1747
  }
@@ -1318,30 +1750,30 @@ function resolveSkillsDir(manifest) {
1318
1750
  return null;
1319
1751
  }
1320
1752
  function discoverBuiltinExtensions() {
1321
- return [src_default, src_default2];
1753
+ return [src_default, src_default2, src_default3];
1322
1754
  }
1323
1755
  async function discoverInstalledExtensions() {
1324
- const manifests = [];
1756
+ const results = [];
1325
1757
  const packages = readExtensionsConfig();
1326
- const npmDir = resolve6(voluteHome(), "extensions", "_npm");
1758
+ const npmDir = resolve7(voluteHome(), "extensions", "_npm");
1327
1759
  const { createRequire } = await import("module");
1328
1760
  for (const pkg of packages) {
1329
1761
  try {
1330
1762
  let resolved = pkg;
1331
- const npmPkgDir = resolve6(npmDir, "node_modules", pkg);
1763
+ const npmPkgDir = resolve7(npmDir, "node_modules", pkg);
1332
1764
  if (existsSync3(npmPkgDir)) {
1333
- const require2 = createRequire(resolve6(npmDir, "noop.js"));
1765
+ const require2 = createRequire(resolve7(npmDir, "noop.js"));
1334
1766
  resolved = require2.resolve(pkg);
1335
1767
  }
1336
1768
  const mod = await import(resolved);
1337
1769
  const manifest = mod.default ?? mod.extension ?? mod;
1338
1770
  if (!validateManifest(manifest, `package ${pkg}`)) continue;
1339
- manifests.push(manifest);
1771
+ results.push({ manifest, package: pkg });
1340
1772
  } catch (err) {
1341
1773
  logger_default.error(`failed to load extension package: ${pkg}`, logger_default.errorData(err));
1342
1774
  }
1343
1775
  }
1344
- return manifests;
1776
+ return results;
1345
1777
  }
1346
1778
  function validateManifest(manifest, source) {
1347
1779
  if (!manifest || typeof manifest !== "object") {
@@ -1383,8 +1815,8 @@ async function discoverLocalExtensions() {
1383
1815
  return [];
1384
1816
  }
1385
1817
  for (const dir of entries) {
1386
- const extDir = resolve6(baseDir, dir);
1387
- const candidates = [resolve6(extDir, "src", "index.js"), resolve6(extDir, "index.js")];
1818
+ const extDir = resolve7(baseDir, dir);
1819
+ const candidates = [resolve7(extDir, "src", "index.js"), resolve7(extDir, "index.js")];
1388
1820
  const entryPoint = candidates.find((p) => existsSync3(p));
1389
1821
  if (!entryPoint) continue;
1390
1822
  try {
@@ -1403,14 +1835,25 @@ async function loadAllExtensions(app, authMw) {
1403
1835
  const builtins = discoverBuiltinExtensions();
1404
1836
  const installed = await discoverInstalledExtensions();
1405
1837
  const local = await discoverLocalExtensions();
1406
- const all = [...builtins, ...installed, ...local];
1838
+ const disabledIds = new Set(readGlobalConfig().disabledExtensions ?? []);
1839
+ const all = [
1840
+ ...builtins.map((m) => ({ manifest: m, source: "builtin" })),
1841
+ ...installed.map((i) => ({ manifest: i.manifest, source: "npm", package: i.package })),
1842
+ ...local.map((m) => ({ manifest: m, source: "local" }))
1843
+ ];
1407
1844
  const seen = /* @__PURE__ */ new Set();
1408
- for (const manifest of all) {
1845
+ for (const entry of all) {
1846
+ const { manifest } = entry;
1409
1847
  if (seen.has(manifest.id)) {
1410
1848
  logger_default.warn(`duplicate extension ID: ${manifest.id}, skipping`);
1411
1849
  continue;
1412
1850
  }
1413
1851
  seen.add(manifest.id);
1852
+ discovered.push(entry);
1853
+ if (disabledIds.has(manifest.id)) {
1854
+ logger_default.info(`extension disabled, skipping: ${manifest.id}`);
1855
+ continue;
1856
+ }
1414
1857
  try {
1415
1858
  await loadExtension(manifest, app, authMw);
1416
1859
  } catch (err) {
@@ -1455,6 +1898,114 @@ function getLoadedExtensions() {
1455
1898
  };
1456
1899
  });
1457
1900
  }
1901
+ function getAllDiscoveredExtensions() {
1902
+ const disabledIds = new Set(readGlobalConfig().disabledExtensions ?? []);
1903
+ return discovered.map((d) => ({
1904
+ id: d.manifest.id,
1905
+ name: d.manifest.name,
1906
+ version: d.manifest.version,
1907
+ description: d.manifest.description,
1908
+ icon: d.manifest.icon,
1909
+ source: d.source,
1910
+ enabled: !disabledIds.has(d.manifest.id),
1911
+ package: d.package
1912
+ }));
1913
+ }
1914
+ function setExtensionEnabled(id, enabled) {
1915
+ if (!discovered.find((d) => d.manifest.id === id)) {
1916
+ throw new Error(`Extension "${id}" not found`);
1917
+ }
1918
+ const config = readGlobalConfig();
1919
+ const disabled = new Set(config.disabledExtensions ?? []);
1920
+ if (enabled) {
1921
+ disabled.delete(id);
1922
+ } else {
1923
+ disabled.add(id);
1924
+ }
1925
+ config.disabledExtensions = disabled.size > 0 ? [...disabled] : void 0;
1926
+ writeGlobalConfig(config);
1927
+ }
1928
+ function extensionsNpmDir() {
1929
+ return resolve7(voluteHome(), "extensions", "_npm");
1930
+ }
1931
+ function ensureExtensionsNpmDir() {
1932
+ const dir = extensionsNpmDir();
1933
+ mkdirSync2(dir, { recursive: true });
1934
+ const pkgPath = resolve7(dir, "package.json");
1935
+ if (!existsSync3(pkgPath)) {
1936
+ writeFileSync2(pkgPath, '{"private":true,"dependencies":{}}\n');
1937
+ }
1938
+ return dir;
1939
+ }
1940
+ function writeExtensionsConfig(packages) {
1941
+ const configPath2 = extensionsConfigPath();
1942
+ mkdirSync2(resolve7(configPath2, ".."), { recursive: true });
1943
+ writeFileSync2(configPath2, `${JSON.stringify(packages, null, 2)}
1944
+ `);
1945
+ }
1946
+ var VALID_NPM_PACKAGE = /^(@[a-z0-9-~][a-z0-9._-~]*\/)?[a-z0-9-~][a-z0-9._-~]*(@[^\s]+)?$/;
1947
+ async function installNpmExtension(pkg) {
1948
+ if (!VALID_NPM_PACKAGE.test(pkg)) {
1949
+ throw new Error(`Invalid package name: "${pkg}"`);
1950
+ }
1951
+ const packages = readExtensionsConfig();
1952
+ if (packages.includes(pkg)) {
1953
+ throw new Error(`Extension "${pkg}" is already installed`);
1954
+ }
1955
+ const dir = ensureExtensionsNpmDir();
1956
+ const { exec } = await import("./exec-DVLXKRIO.js");
1957
+ try {
1958
+ await exec("npm", ["install", pkg], { cwd: dir });
1959
+ } catch (err) {
1960
+ logger_default.error(`npm install failed for "${pkg}"`, logger_default.errorData(err));
1961
+ throw new Error(`Failed to install "${pkg}". Check daemon logs for details.`);
1962
+ }
1963
+ packages.push(pkg);
1964
+ writeExtensionsConfig(packages);
1965
+ logger_default.info(`installed extension package: ${pkg}`);
1966
+ }
1967
+ async function uninstallNpmExtension(pkg) {
1968
+ const packages = readExtensionsConfig();
1969
+ const idx = packages.indexOf(pkg);
1970
+ if (idx === -1) {
1971
+ throw new Error(`Extension "${pkg}" is not installed`);
1972
+ }
1973
+ await cleanupExtensionSkills(pkg);
1974
+ packages.splice(idx, 1);
1975
+ writeExtensionsConfig(packages);
1976
+ try {
1977
+ const { exec } = await import("./exec-DVLXKRIO.js");
1978
+ await exec("npm", ["uninstall", pkg], { cwd: extensionsNpmDir() });
1979
+ } catch (err) {
1980
+ logger_default.warn(
1981
+ `npm uninstall failed for "${pkg}" (may have been manually removed)`,
1982
+ logger_default.errorData(err)
1983
+ );
1984
+ }
1985
+ logger_default.info(`uninstalled extension package: ${pkg}`);
1986
+ }
1987
+ async function cleanupExtensionSkills(pkg) {
1988
+ try {
1989
+ const pkgDir = resolve7(extensionsNpmDir(), "node_modules", pkg);
1990
+ if (!existsSync3(pkgDir)) return;
1991
+ const { createRequire } = await import("module");
1992
+ const require2 = createRequire(resolve7(extensionsNpmDir(), "noop.js"));
1993
+ const mod = require2(pkg);
1994
+ const manifest = mod.default ?? mod.extension ?? mod;
1995
+ if (!manifest?.skillsDir || !existsSync3(manifest.skillsDir)) return;
1996
+ const skillDirs = readdirSync2(manifest.skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
1997
+ for (const skillId of skillDirs) {
1998
+ try {
1999
+ await removeSharedSkill(skillId);
2000
+ logger_default.info(`removed skill "${skillId}" from extension ${pkg}`);
2001
+ } catch (err) {
2002
+ logger_default.warn(`failed to remove skill "${skillId}" for extension ${pkg}`, logger_default.errorData(err));
2003
+ }
2004
+ }
2005
+ } catch (err) {
2006
+ logger_default.warn(`could not clean up skills for "${pkg}"`, logger_default.errorData(err));
2007
+ }
2008
+ }
1458
2009
  function getExtensionStandardSkills() {
1459
2010
  const skills = [];
1460
2011
  for (const { manifest } of loaded) {
@@ -1494,6 +2045,7 @@ function notifyExtensionsDaemonStop() {
1494
2045
  }
1495
2046
  }
1496
2047
  loaded.length = 0;
2048
+ discovered.length = 0;
1497
2049
  }
1498
2050
  function notifyExtensionsMindStart(mindName) {
1499
2051
  for (const { manifest } of loaded) {
@@ -1520,6 +2072,10 @@ export {
1520
2072
  deleteSystemsConfig,
1521
2073
  loadAllExtensions,
1522
2074
  getLoadedExtensions,
2075
+ getAllDiscoveredExtensions,
2076
+ setExtensionEnabled,
2077
+ installNpmExtension,
2078
+ uninstallNpmExtension,
1523
2079
  getExtensionStandardSkills,
1524
2080
  notifyExtensionsDaemonStart,
1525
2081
  notifyExtensionsDaemonStop,