volute 0.31.0 → 0.33.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 +31 -22
- package/dist/{accept-GAKQ3MEH.js → accept-D5VBM7JW.js} +5 -4
- package/dist/{activity-events-T5ZRCVAL.js → activity-events-XJO3P4RR.js} +3 -2
- package/dist/{ai-service-UWUPM4T6.js → ai-service-SBY2WG7O.js} +18 -5
- package/dist/api.d.ts +703 -1068
- package/dist/{archive-YBNSJYZZ.js → archive-INXYFVCW.js} +3 -2
- package/dist/{auth-T5AW2USD.js → auth-GKCDSO4T.js} +4 -3
- package/dist/{bridge-4AJ3EY26.js → bridge-TXWWPPOJ.js} +5 -4
- package/dist/{chat-7YLT7FI3.js → chat-U5ZOME3O.js} +8 -8
- package/dist/{chunk-NV3TYNWX.js → chunk-2NGTS5UU.js} +1 -1
- package/dist/{chunk-BWKIHH7B.js → chunk-3Z2DPESO.js} +662 -508
- package/dist/chunk-6LXAAQ43.js +22 -0
- package/dist/chunk-7J3HEVR7.js +220 -0
- package/dist/{chunk-NOWVQ7AL.js → chunk-A2A4KLFE.js} +351 -301
- package/dist/{chunk-LX6T3GKQ.js → chunk-ALEF47VT.js} +1 -1
- package/dist/{chunk-S2TZLSDH.js → chunk-C7I35G4R.js} +163 -15
- package/dist/{chunk-VGWJSNHS.js → chunk-G53F3JA4.js} +1 -35
- package/dist/{chunk-A6TUJJ3L.js → chunk-G6BSYHPK.js} +2 -2
- package/dist/{chunk-DAXJKPHZ.js → chunk-GY5HBI7A.js} +2 -2
- package/dist/{chunk-BC3P3QCK.js → chunk-I5KY25PQ.js} +1 -9
- package/dist/{chunk-BNC43CSY.js → chunk-JUKK7FPS.js} +2 -2
- package/dist/{chunk-R5QJBZZG.js → chunk-JYVGHWEJ.js} +21 -11
- package/dist/chunk-KIEPMIM5.js +59 -0
- package/dist/{chunk-EKDWA7E4.js → chunk-KVK2DLWI.js} +5 -2
- package/dist/{chunk-AAO77TZX.js → chunk-LOEJ4HPQ.js} +1 -1
- package/dist/chunk-LRCG2JLP.js +251 -0
- package/dist/{chunk-EMPFLFTG.js → chunk-M7UL5S3Q.js} +1 -1
- package/dist/{chunk-6QIUN46C.js → chunk-N432I7QH.js} +20 -3
- package/dist/{chunk-SNVPRRT7.js → chunk-NNB4WIG7.js} +2 -2
- package/dist/{chunk-HDKY4TWU.js → chunk-NPKSDYA2.js} +3 -3
- package/dist/chunk-OYAKCAVY.js +29 -0
- package/dist/chunk-PB65JZK2.js +85 -0
- package/dist/chunk-PVY5W6QN.js +41 -0
- package/dist/{chunk-PNQCXLSV.js → chunk-QTUVYI7W.js} +58 -1
- package/dist/{chunk-X62AXPR7.js → chunk-RPZZSXV3.js} +8 -196
- package/dist/{chunk-WRS3B556.js → chunk-RSX4OPZY.js} +5 -5
- package/dist/{chunk-FAHDKPEH.js → chunk-RVGLDGMI.js} +5 -3
- package/dist/chunk-SKLSMHXO.js +208 -0
- package/dist/{chunk-4OUOFS23.js → chunk-UKVWJRKN.js} +1 -1
- package/dist/{chunk-57OKQMP3.js → chunk-VH33ZWMW.js} +5 -55
- package/dist/cli.js +49 -23
- package/dist/{clock-LJCG426D.js → clock-BVH3V6E3.js} +7 -6
- package/dist/{cloud-sync-O3LXIRN6.js → cloud-sync-4NWLMFVH.js} +20 -14
- package/dist/config-H2H4UIF7.js +72 -0
- package/dist/connectors/discord-bridge.js +1 -1
- package/dist/connectors/slack-bridge.js +1 -1
- package/dist/connectors/telegram-bridge.js +1 -1
- package/dist/{conversations-RKKGP5IA.js → conversations-AWI5SZW2.js} +4 -3
- package/dist/{create-TL623TFC.js → create-2FK7Z46Y.js} +6 -2
- package/dist/{create-WUTIIRI2.js → create-YWD2TIP4.js} +6 -5
- package/dist/{daemon-client-CVGM25DM.js → daemon-client-6QXHZ7US.js} +3 -2
- package/dist/{daemon-restart-EZP7XH3V.js → daemon-restart-GOBUKLX7.js} +8 -6
- package/dist/daemon.js +1918 -1472
- package/dist/{db-SW5PL6QA.js → db-F34YLV7D.js} +2 -1
- package/dist/db-RA45JBFG.js +16 -0
- package/dist/{delete-Z6HAG35F.js → delete-QTGWEDBI.js} +1 -1
- package/dist/delivery-manager-PFAKEJTC.js +32 -0
- package/dist/delivery-router-FL45JL7N.js +21 -0
- package/dist/down-FWWTEKXM.js +15 -0
- package/dist/{env-NHESNNSP.js → env-JCOF2222.js} +5 -4
- package/dist/{export-EVMP7GWY.js → export-SUYRLI5Q.js} +4 -3
- package/dist/{extension-LR7EW3JF.js → extension-OBTGKQQD.js} +5 -3
- package/dist/{extensions-NGEJI7JH.js → extensions-KYNTVTMO.js} +10 -7
- package/dist/{files-3SM7V33S.js → files-65PMW5IK.js} +6 -5
- package/dist/{history-PQD3LXEP.js → history-DKCDI3JO.js} +9 -4
- package/dist/{import-PR2OCGQJ.js → import-DDUFE7AY.js} +4 -3
- package/dist/isolation-LLAYQYDY.js +22 -0
- package/dist/{join-R4EN5CWQ.js → join-I5QEE3LG.js} +1 -1
- package/dist/{list-B4XNUOFO.js → list-JQ463EDA.js} +5 -4
- package/dist/{login-62JVY6A2.js → login-D7ETSU4R.js} +5 -4
- package/dist/{login-URWP6S2N.js → login-RIJF2F4G.js} +3 -2
- package/dist/{logout-NXJQJDLI.js → logout-5MLHZALK.js} +3 -2
- package/dist/{logout-ZK2N62T3.js → logout-UZJRGY4Z.js} +3 -2
- package/dist/message-delivery-DFF5SJRM.js +42 -0
- package/dist/{mind-E2ZV2WRX.js → mind-IOJFLEM5.js} +25 -19
- package/dist/{mind-activity-tracker-ASNZBMLC.js → mind-activity-tracker-F6O4Q2SL.js} +4 -3
- package/dist/{mind-list-BEI7E5WY.js → mind-list-WUPMQDYQ.js} +3 -2
- package/dist/mind-manager-NBJF5D26.js +32 -0
- package/dist/mind-profile-P67FEHOY.js +47 -0
- package/dist/mind-service-2MQ6UK5N.js +38 -0
- package/dist/{mind-sleep-CANABWJI.js → mind-sleep-WW2IX7JT.js} +5 -4
- package/dist/{mind-status-6WKZVUOP.js → mind-status-L3EFFRPR.js} +3 -2
- package/dist/{mind-wake-RZKLH2IN.js → mind-wake-VSSGW465.js} +5 -4
- package/dist/{package-NU4CA7OU.js → package-U3VFO273.js} +2 -1
- package/dist/{read-THL362EI.js → read-EBY56C33.js} +5 -4
- package/dist/read-stdin-HQJ7774D.js +8 -0
- package/dist/{register-QAQELAS6.js → register-HD74C4TT.js} +5 -4
- package/dist/{registry-ASXCQCNH.js → registry-PJ4S5PHQ.js} +8 -1
- package/dist/{reject-AYPBNPNL.js → reject-UJKFBHRO.js} +5 -4
- package/dist/{restart-6SKPV3T2.js → restart-3UCMRUVC.js} +3 -2
- package/dist/{sandbox-6ZEWQDVU.js → sandbox-GJOK4QLQ.js} +4 -3
- package/dist/scheduler-ZZ7XGQG6.js +32 -0
- package/dist/schema-PA3M5ZKH.js +32 -0
- package/dist/seed-QDYVLG74.js +11 -0
- package/dist/seed-check-S2IX25RL.js +32 -0
- package/dist/seed-cmd-DKOUFEAU.js +36 -0
- package/dist/{seed-OWX2AW75.js → seed-create-4XBBOLRH.js} +27 -10
- package/dist/{sprout-FDVI2CGN.js → seed-sprout-GQEIIQRT.js} +24 -9
- package/dist/{send-ZO4BTWXK.js → send-QIV2INHB.js} +92 -101
- package/dist/{setup-7CFITEQN.js → setup-TISPCO22.js} +7 -2
- package/dist/{setup-ZXBXG7E4.js → setup-XMCBE3LF.js} +11 -7
- package/dist/{skill-YFXP67A2.js → skill-PSQGRRJX.js} +5 -4
- package/dist/skills/dreaming/SKILL.md +6 -4
- package/dist/skills/dreaming/references/INSTALL.md +2 -2
- package/dist/skills/dreaming/scripts/dream.ts +2 -2
- package/dist/skills/dreaming/scripts/wake-context-dreams.sh +1 -1
- package/dist/skills/imagegen/SKILL.md +16 -11
- package/dist/skills/imagegen/references/INSTALL.md +1 -1
- package/dist/skills/imagegen/scripts/imagegen.ts +146 -25
- package/dist/skills/orientation/SKILL.md +9 -2
- package/dist/skills/resonance/SKILL.md +4 -1
- package/dist/skills/resonance/references/INSTALL.md +2 -2
- package/dist/skills/resonance/scripts/resonance-hook.sh +2 -0
- package/dist/skills/resonance/scripts/resonance.ts +35 -5
- package/dist/skills/seed-nurture/SKILL.md +42 -0
- package/dist/skills/volute-admin/SKILL.md +83 -0
- package/dist/skills/volute-mind/SKILL.md +15 -11
- package/dist/skills-7FV7EJTE.js +62 -0
- package/dist/sleep-manager-JTXSN7NV.js +36 -0
- package/dist/spirit-VRONKFMF.js +23 -0
- package/dist/{split-MI62KJUU.js → split-STOROBYJ.js} +1 -1
- package/dist/sprout-WKLZXUIQ.js +11 -0
- package/dist/{start-D64BRKPH.js → start-K2NCUUCG.js} +3 -2
- package/dist/{status-ZZWBYFGE.js → status-3JBTFSMI.js} +6 -4
- package/dist/{stop-OP2CTXCO.js → stop-H26JZDXF.js} +3 -2
- package/dist/system-chat-JAPOJ3KE.js +36 -0
- package/dist/{systems-EQPPT4B7.js → systems-XRI52VCH.js} +6 -5
- package/dist/{tailscale-6DJKUMNF.js → tailscale-XHQBZROW.js} +2 -1
- package/dist/{template-hash-3HOR4UAJ.js → template-hash-A6VVKOXJ.js} +2 -1
- package/dist/up-M5AS6SBV.js +18 -0
- package/dist/{update-KUJXATRS.js → update-UD543CXX.js} +6 -4
- package/dist/{update-check-5WVSU37T.js → update-check-ZD6OOIYQ.js} +3 -2
- package/dist/{upgrade-KBHCWX6T.js → upgrade-O4Q7WJM3.js} +12 -14
- package/dist/{version-notify-75ELVKPV.js → version-notify-NBI2MTJO.js} +22 -16
- package/dist/volute-config-HD7WWUQC.js +10 -0
- package/dist/web-assets/assets/index-CWJrVveV.css +1 -0
- package/dist/web-assets/assets/index-DJt14FRI.js +75 -0
- package/dist/web-assets/ext-theme.css +93 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0004_spirits.sql +5 -0
- package/drizzle/meta/0004_snapshot.json +7 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +2 -1
- package/packages/extensions/notes/dist/ui/assets/index-8jWEv9SA.js +61 -0
- package/packages/extensions/notes/dist/ui/assets/index-DkaB7Ytd.css +1 -0
- package/packages/extensions/notes/dist/ui/index.html +2 -2
- package/packages/extensions/pages/skills/pages/SKILL.md +16 -46
- package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +40 -0
- package/templates/_base/.init/{.config → .local}/bin/volute +1 -1
- package/templates/_base/.init/.local/hooks/pre-prompt/session-activity.ts +40 -0
- package/templates/_base/.init/.local/hooks/startup-context.ts +58 -0
- package/templates/_base/home/.config/routes.json +1 -1
- package/templates/_base/src/lib/daemon-client.ts +21 -13
- package/templates/_base/src/lib/format-prefix.ts +1 -0
- package/templates/_base/src/lib/hook-loader.ts +155 -0
- package/templates/_base/src/lib/startup.ts +11 -4
- package/templates/_base/src/lib/transparency.ts +2 -2
- package/templates/claude/.init/.claude/settings.json +1 -1
- package/templates/claude/.init/.config/routes.json +2 -2
- package/templates/claude/src/agent.ts +95 -13
- package/templates/claude/src/lib/message-channel.ts +7 -2
- package/templates/claude/src/lib/stream-consumer.ts +38 -0
- package/templates/codex/.init/.config/routes.json +11 -0
- package/templates/codex/.init/AGENTS.md +29 -0
- package/templates/codex/home/.config/config.json.tmpl +7 -0
- package/templates/codex/package.json.tmpl +20 -0
- package/templates/codex/src/agent.ts +554 -0
- package/templates/codex/src/lib/content.ts +16 -0
- package/templates/codex/src/lib/session-store.ts +56 -0
- package/templates/codex/src/server.ts +59 -0
- package/templates/codex/volute-template.json +8 -0
- package/templates/pi/.init/.config/routes.json +2 -2
- package/templates/pi/src/agent.ts +62 -8
- package/templates/pi/src/lib/event-handler.ts +1 -1
- package/templates/pi/src/lib/reply-instructions-extension.ts +32 -11
- package/dist/chunk-HR5JKIDG.js +0 -222
- package/dist/down-TS4XQBA4.js +0 -13
- package/dist/message-delivery-UJHCLVU4.js +0 -30
- package/dist/mind-manager-IPA6DZXD.js +0 -26
- package/dist/pages-watcher-72OVPRMH.js +0 -22
- package/dist/skills/sessions/SKILL.md +0 -49
- package/dist/sleep-manager-TPS6OGCA.js +0 -30
- package/dist/system-chat-B43GIXQU.js +0 -30
- package/dist/up-TDXEP3VA.js +0 -16
- package/dist/web-assets/assets/index-BM1cTzBg.js +0 -72
- package/dist/web-assets/assets/index-BfJkKTPF.css +0 -1
- package/packages/extensions/notes/dist/ui/assets/index-B8GdTnXs.css +0 -1
- package/packages/extensions/notes/dist/ui/assets/index-CDpGTCWb.js +0 -2
- package/packages/extensions/pages/skills/pages/scripts/pages.mjs +0 -58
- package/templates/_base/.init/.config/hooks/startup-context.sh +0 -46
- package/templates/_base/.init/.config/scripts/session-reader.ts +0 -59
- package/templates/_base/src/lib/session-monitor.ts +0 -400
- package/templates/claude/src/lib/hooks/session-context.ts +0 -32
- package/templates/pi/src/lib/session-context-extension.ts +0 -35
- /package/templates/_base/.init/{.config → .local}/hooks/wake-context.sh +0 -0
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getAllSites,
|
|
4
|
+
getPublishedPages,
|
|
5
|
+
getRecentPages,
|
|
6
|
+
initDb,
|
|
7
|
+
syncPublishedPages
|
|
8
|
+
} from "./chunk-PB65JZK2.js";
|
|
2
9
|
import {
|
|
3
10
|
getUser,
|
|
4
11
|
getUserByUsername
|
|
5
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-JYVGHWEJ.js";
|
|
6
13
|
import {
|
|
7
14
|
publish
|
|
8
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-KVK2DLWI.js";
|
|
9
16
|
import {
|
|
10
17
|
hashSkillDir,
|
|
11
18
|
importSkillFromDir,
|
|
12
19
|
sharedSkillsDir
|
|
13
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-C7I35G4R.js";
|
|
14
21
|
import {
|
|
15
22
|
logger_default
|
|
16
23
|
} from "./chunk-YUIHSKR6.js";
|
|
17
24
|
import {
|
|
18
|
-
getDb,
|
|
19
25
|
mindDir,
|
|
20
|
-
turns,
|
|
21
26
|
voluteHome,
|
|
22
27
|
voluteSystemDir
|
|
23
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-LRCG2JLP.js";
|
|
24
29
|
|
|
25
30
|
// src/lib/extensions.ts
|
|
26
|
-
import { existsSync as
|
|
27
|
-
import { dirname, resolve as
|
|
31
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
32
|
+
import { dirname, resolve as resolve6 } from "path";
|
|
28
33
|
|
|
29
34
|
// packages/extensions/notes/src/index.ts
|
|
30
35
|
import { resolve } from "path";
|
|
@@ -290,7 +295,7 @@ function createCommands() {
|
|
|
290
295
|
return {
|
|
291
296
|
write: {
|
|
292
297
|
description: "Write a new note",
|
|
293
|
-
usage: 'volute notes write "title" "content" [--reply-to author/slug]',
|
|
298
|
+
usage: 'volute notes write "title" ["content"] [--reply-to author/slug] (content can be piped via stdin)',
|
|
294
299
|
handler: async (args, ctx) => {
|
|
295
300
|
if (!ctx.db) return { error: "Notes extension requires a database" };
|
|
296
301
|
const mindName = ctx.mindName;
|
|
@@ -298,7 +303,7 @@ function createCommands() {
|
|
|
298
303
|
const user = await ctx.getUserByUsername(mindName);
|
|
299
304
|
if (!user) return { error: `Unknown mind: ${mindName}` };
|
|
300
305
|
const title = args[0];
|
|
301
|
-
const content = args[1];
|
|
306
|
+
const content = args[1] ?? ctx.stdin;
|
|
302
307
|
if (!title || !content)
|
|
303
308
|
return { error: 'Usage: volute notes write "title" "content" [--reply-to author/slug]' };
|
|
304
309
|
let replyToId;
|
|
@@ -372,7 +377,7 @@ Comments (${note.comments.length}):`);
|
|
|
372
377
|
},
|
|
373
378
|
comment: {
|
|
374
379
|
description: "Comment on a note",
|
|
375
|
-
usage: 'volute notes comment <author/slug> "content"',
|
|
380
|
+
usage: 'volute notes comment <author/slug> ["content"] (content can be piped via stdin)',
|
|
376
381
|
handler: async (args, ctx) => {
|
|
377
382
|
if (!ctx.db) return { error: "Notes extension requires a database" };
|
|
378
383
|
const mindName = ctx.mindName;
|
|
@@ -380,7 +385,7 @@ Comments (${note.comments.length}):`);
|
|
|
380
385
|
const user = await ctx.getUserByUsername(mindName);
|
|
381
386
|
if (!user) return { error: `Unknown mind: ${mindName}` };
|
|
382
387
|
const ref = args[0];
|
|
383
|
-
const content = args[1];
|
|
388
|
+
const content = args[1] ?? ctx.stdin;
|
|
384
389
|
if (!ref || !ref.includes("/") || !content) {
|
|
385
390
|
return { error: 'Usage: volute notes comment <author/slug> "content"' };
|
|
386
391
|
}
|
|
@@ -434,7 +439,7 @@ Comments (${note.comments.length}):`);
|
|
|
434
439
|
}
|
|
435
440
|
|
|
436
441
|
// packages/extensions/notes/src/db.ts
|
|
437
|
-
function
|
|
442
|
+
function initDb2(db) {
|
|
438
443
|
db.exec(`
|
|
439
444
|
CREATE TABLE IF NOT EXISTS notes (
|
|
440
445
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -519,19 +524,16 @@ function createRoutes(ctx) {
|
|
|
519
524
|
replyToId = id;
|
|
520
525
|
}
|
|
521
526
|
const note = await createNote(db, getUser2, actor.id, body.title, body.content, replyToId);
|
|
522
|
-
ctx.publishActivity(
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
},
|
|
533
|
-
c
|
|
534
|
-
);
|
|
527
|
+
ctx.publishActivity({
|
|
528
|
+
type: "note_created",
|
|
529
|
+
mind: actor.username,
|
|
530
|
+
summary: `${actor.username} wrote "${body.title}"`,
|
|
531
|
+
metadata: {
|
|
532
|
+
author: actor.username,
|
|
533
|
+
slug: note.slug,
|
|
534
|
+
bodyHtml: body.content.slice(0, 500)
|
|
535
|
+
}
|
|
536
|
+
});
|
|
535
537
|
return c.json(note, 201);
|
|
536
538
|
}).get("/:author/:slug", async (c) => {
|
|
537
539
|
const { author, slug } = c.req.param();
|
|
@@ -619,21 +621,17 @@ var src_default = createExtension({
|
|
|
619
621
|
name: "Notes",
|
|
620
622
|
version: "0.1.0",
|
|
621
623
|
description: "Public notes for sharing thoughts, reflections, and ideas",
|
|
624
|
+
icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 4h10M3 7h8M3 10h6M3 13h9"/></svg>',
|
|
625
|
+
color: "yellow",
|
|
622
626
|
routes: (ctx) => createRoutes(ctx),
|
|
623
627
|
commands: createCommands(),
|
|
624
|
-
initDb,
|
|
628
|
+
initDb: initDb2,
|
|
625
629
|
skillsDir,
|
|
626
630
|
standardSkill: true,
|
|
627
631
|
ui: {
|
|
628
632
|
assetsDir,
|
|
629
633
|
systemSection: { id: "notes", label: "Notes", urlPatterns: ["/notes", "/notes/:author/:slug"] },
|
|
630
|
-
mindSections: [
|
|
631
|
-
{
|
|
632
|
-
id: "notes",
|
|
633
|
-
label: "Notes",
|
|
634
|
-
icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 2h6l4 4v8H4V2z"/><path d="M10 2v4h4"/><path d="M6 9h6M6 12h4"/></svg>'
|
|
635
|
-
}
|
|
636
|
-
],
|
|
634
|
+
mindSections: [{ id: "notes", label: "Notes" }],
|
|
637
635
|
feedSource: {
|
|
638
636
|
endpoint: "/api/ext/notes/feed"
|
|
639
637
|
}
|
|
@@ -641,12 +639,221 @@ var src_default = createExtension({
|
|
|
641
639
|
});
|
|
642
640
|
|
|
643
641
|
// packages/extensions/pages/src/index.ts
|
|
644
|
-
import { resolve as
|
|
642
|
+
import { resolve as resolve4 } from "path";
|
|
643
|
+
|
|
644
|
+
// packages/extensions/pages/src/commands.ts
|
|
645
|
+
import { cpSync, existsSync, readdirSync, readFileSync, rmSync, statSync } from "fs";
|
|
646
|
+
import { relative, resolve as resolve2 } from "path";
|
|
647
|
+
function createCommands2() {
|
|
648
|
+
return {
|
|
649
|
+
publish: {
|
|
650
|
+
description: "Publish all pages (copy to public snapshot)",
|
|
651
|
+
usage: "volute pages publish [--remote]",
|
|
652
|
+
handler: async (args, ctx) => {
|
|
653
|
+
const mindName = ctx.mindName;
|
|
654
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
655
|
+
const remote = args.includes("--remote");
|
|
656
|
+
const mindDir2 = ctx.getMindDir(mindName);
|
|
657
|
+
if (!mindDir2) return { error: `Mind not found: ${mindName}` };
|
|
658
|
+
const sourceDir = resolve2(mindDir2, "home", "public", "pages");
|
|
659
|
+
if (!existsSync(sourceDir))
|
|
660
|
+
return { error: "No pages directory found (home/public/pages/)" };
|
|
661
|
+
const db = ctx.db;
|
|
662
|
+
if (!db) return { error: "Database not available" };
|
|
663
|
+
const snapshotDir = resolve2(ctx.dataDir, "sites", mindName);
|
|
664
|
+
try {
|
|
665
|
+
if (existsSync(snapshotDir)) rmSync(snapshotDir, { recursive: true });
|
|
666
|
+
cpSync(sourceDir, snapshotDir, { recursive: true });
|
|
667
|
+
} catch (err) {
|
|
668
|
+
return { error: `Failed to publish snapshot: ${err.message}` };
|
|
669
|
+
}
|
|
670
|
+
const htmlFiles = collectHtmlFiles(snapshotDir, snapshotDir);
|
|
671
|
+
let diff;
|
|
672
|
+
try {
|
|
673
|
+
diff = syncPublishedPages(db, mindName, htmlFiles);
|
|
674
|
+
} catch (err) {
|
|
675
|
+
return { error: `Failed to update page database: ${err.message}` };
|
|
676
|
+
}
|
|
677
|
+
for (const file of diff.added) {
|
|
678
|
+
ctx.publishActivity({
|
|
679
|
+
type: "page_published",
|
|
680
|
+
mind: mindName,
|
|
681
|
+
summary: `${mindName} published ${file}`,
|
|
682
|
+
metadata: { file, iframeUrl: `/ext/pages/public/${mindName}/${file}` }
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
for (const file of diff.removed) {
|
|
686
|
+
ctx.publishActivity({
|
|
687
|
+
type: "page_removed",
|
|
688
|
+
mind: mindName,
|
|
689
|
+
summary: `${mindName} removed ${file}`,
|
|
690
|
+
metadata: { file }
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
let output = `Published ${htmlFiles.length} files`;
|
|
694
|
+
const parts = [];
|
|
695
|
+
if (diff.added.length > 0) parts.push(`${diff.added.length} new`);
|
|
696
|
+
if (diff.updated.length > 0) parts.push(`${diff.updated.length} updated`);
|
|
697
|
+
if (diff.removed.length > 0) parts.push(`${diff.removed.length} removed`);
|
|
698
|
+
if (parts.length > 0) output += ` (${parts.join(", ")})`;
|
|
699
|
+
if (remote) {
|
|
700
|
+
const config = ctx.getSystemsConfig();
|
|
701
|
+
if (!config)
|
|
702
|
+
return {
|
|
703
|
+
error: "Not connected to volute.systems. Run volute systems register or login first."
|
|
704
|
+
};
|
|
705
|
+
const allFiles = collectAllFiles(snapshotDir, snapshotDir);
|
|
706
|
+
const files = {};
|
|
707
|
+
for (const f of allFiles) {
|
|
708
|
+
const fp = resolve2(snapshotDir, f);
|
|
709
|
+
files[f] = readFileSync(fp).toString("base64");
|
|
710
|
+
}
|
|
711
|
+
try {
|
|
712
|
+
const res = await fetch(`${config.apiUrl}/api/pages/publish/${mindName}`, {
|
|
713
|
+
method: "PUT",
|
|
714
|
+
headers: {
|
|
715
|
+
"Content-Type": "application/json",
|
|
716
|
+
Authorization: `Bearer ${config.apiKey}`
|
|
717
|
+
},
|
|
718
|
+
body: JSON.stringify({ files })
|
|
719
|
+
});
|
|
720
|
+
const data = await res.json().catch(() => ({}));
|
|
721
|
+
if (!res.ok) {
|
|
722
|
+
const errMsg = data.error || `HTTP ${res.status}`;
|
|
723
|
+
output += `
|
|
724
|
+
Warning: remote publish failed: ${errMsg}`;
|
|
725
|
+
} else if (data.url) {
|
|
726
|
+
output += `
|
|
727
|
+
Remote: ${data.url}`;
|
|
728
|
+
}
|
|
729
|
+
} catch (err) {
|
|
730
|
+
output += `
|
|
731
|
+
Warning: remote publish failed: ${err.message}`;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return { output };
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
list: {
|
|
738
|
+
description: "List pages with publish status",
|
|
739
|
+
usage: "volute pages list [--all]",
|
|
740
|
+
handler: async (args, ctx) => {
|
|
741
|
+
const mindName = ctx.mindName;
|
|
742
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
743
|
+
const db = ctx.db;
|
|
744
|
+
if (!db) return { error: "Database not available" };
|
|
745
|
+
const allFlag = args.includes("--all");
|
|
746
|
+
const port = process.env.VOLUTE_DAEMON_PORT || "1618";
|
|
747
|
+
if (allFlag) {
|
|
748
|
+
const { getAllSites: getAllSites2 } = await import("./db-RA45JBFG.js");
|
|
749
|
+
const sites = getAllSites2(db);
|
|
750
|
+
const lines2 = [];
|
|
751
|
+
for (const site of sites) {
|
|
752
|
+
for (const f of site.files) {
|
|
753
|
+
const url = `http://localhost:${port}/ext/pages/public/${site.mind}/${f.file}`;
|
|
754
|
+
lines2.push(`${site.mind.padEnd(15)} ${f.file.padEnd(25)} ${url}`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return { output: lines2.length > 0 ? lines2.join("\n") : "No published pages found." };
|
|
758
|
+
}
|
|
759
|
+
const mindDir2 = ctx.getMindDir(mindName);
|
|
760
|
+
if (!mindDir2) return { error: `Mind not found: ${mindName}` };
|
|
761
|
+
const sourceDir = resolve2(mindDir2, "home", "public", "pages");
|
|
762
|
+
const published = new Set(getPublishedPages(db, mindName).map((p) => p.file));
|
|
763
|
+
const draftFiles = existsSync(sourceDir) ? collectHtmlFiles(sourceDir, sourceDir) : [];
|
|
764
|
+
const allFiles = /* @__PURE__ */ new Set([...published, ...draftFiles]);
|
|
765
|
+
if (allFiles.size === 0) return { output: "No pages found." };
|
|
766
|
+
const lines = [...allFiles].sort().map((file) => {
|
|
767
|
+
const isPublished = published.has(file);
|
|
768
|
+
const status = isPublished ? "published" : "draft";
|
|
769
|
+
const url = isPublished ? `http://localhost:${port}/ext/pages/public/${mindName}/${file}` : "";
|
|
770
|
+
return `${status.padEnd(11)} ${file.padEnd(25)} ${url}`;
|
|
771
|
+
});
|
|
772
|
+
return { output: lines.join("\n") };
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
function collectHtmlFiles(dir, baseDir) {
|
|
778
|
+
const files = [];
|
|
779
|
+
let items;
|
|
780
|
+
try {
|
|
781
|
+
items = readdirSync(dir);
|
|
782
|
+
} catch (err) {
|
|
783
|
+
console.error(`[pages] failed to read directory ${dir}: ${err.message}`);
|
|
784
|
+
return files;
|
|
785
|
+
}
|
|
786
|
+
for (const item of items) {
|
|
787
|
+
if (item.startsWith(".")) continue;
|
|
788
|
+
const fullPath = resolve2(dir, item);
|
|
789
|
+
try {
|
|
790
|
+
const s = statSync(fullPath);
|
|
791
|
+
if (s.isFile() && item.endsWith(".html")) {
|
|
792
|
+
files.push(relative(baseDir, fullPath));
|
|
793
|
+
} else if (s.isDirectory()) {
|
|
794
|
+
files.push(...collectHtmlFiles(fullPath, baseDir));
|
|
795
|
+
}
|
|
796
|
+
} catch (err) {
|
|
797
|
+
console.error(`[pages] failed to stat ${fullPath}: ${err.message}`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return files.sort();
|
|
801
|
+
}
|
|
802
|
+
function collectAllFiles(dir, baseDir) {
|
|
803
|
+
const files = [];
|
|
804
|
+
let items;
|
|
805
|
+
try {
|
|
806
|
+
items = readdirSync(dir);
|
|
807
|
+
} catch (err) {
|
|
808
|
+
console.error(`[pages] failed to read directory ${dir}: ${err.message}`);
|
|
809
|
+
return files;
|
|
810
|
+
}
|
|
811
|
+
for (const item of items) {
|
|
812
|
+
if (item.startsWith(".")) continue;
|
|
813
|
+
const fullPath = resolve2(dir, item);
|
|
814
|
+
try {
|
|
815
|
+
const s = statSync(fullPath);
|
|
816
|
+
if (s.isFile()) {
|
|
817
|
+
files.push(relative(baseDir, fullPath));
|
|
818
|
+
} else if (s.isDirectory()) {
|
|
819
|
+
files.push(...collectAllFiles(fullPath, baseDir));
|
|
820
|
+
}
|
|
821
|
+
} catch (err) {
|
|
822
|
+
console.error(`[pages] failed to stat ${fullPath}: ${err.message}`);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return files.sort();
|
|
826
|
+
}
|
|
645
827
|
|
|
646
828
|
// packages/extensions/pages/src/routes.ts
|
|
647
829
|
import { readFile, stat } from "fs/promises";
|
|
648
|
-
import { extname, resolve as
|
|
830
|
+
import { extname, resolve as resolve3 } from "path";
|
|
649
831
|
import { Hono as Hono2 } from "hono";
|
|
832
|
+
|
|
833
|
+
// packages/extensions/pages/src/cache.ts
|
|
834
|
+
function getSites(db) {
|
|
835
|
+
const sites = getAllSites(db);
|
|
836
|
+
return sites.map((site) => ({
|
|
837
|
+
name: site.mind,
|
|
838
|
+
label: site.mind,
|
|
839
|
+
pages: site.files.map((f) => ({
|
|
840
|
+
file: f.file,
|
|
841
|
+
modified: f.updated_at,
|
|
842
|
+
url: `/ext/pages/public/${site.mind}/${f.file}`
|
|
843
|
+
}))
|
|
844
|
+
}));
|
|
845
|
+
}
|
|
846
|
+
function getRecentPagesList(db, opts) {
|
|
847
|
+
const rows = getRecentPages(db, opts);
|
|
848
|
+
return rows.map((r) => ({
|
|
849
|
+
mind: r.mind,
|
|
850
|
+
file: r.file,
|
|
851
|
+
modified: r.updated_at,
|
|
852
|
+
url: `/ext/pages/public/${r.mind}/${r.file}`
|
|
853
|
+
}));
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// packages/extensions/pages/src/routes.ts
|
|
650
857
|
var MIME_TYPES = {
|
|
651
858
|
".html": "text/html",
|
|
652
859
|
".js": "application/javascript",
|
|
@@ -663,28 +870,20 @@ var MIME_TYPES = {
|
|
|
663
870
|
".txt": "text/plain",
|
|
664
871
|
".xml": "application/xml"
|
|
665
872
|
};
|
|
666
|
-
var _pagesWatcher = null;
|
|
667
|
-
async function getPagesWatcher() {
|
|
668
|
-
if (_pagesWatcher) return _pagesWatcher;
|
|
669
|
-
const mod = await import("./pages-watcher-72OVPRMH.js");
|
|
670
|
-
_pagesWatcher = mod;
|
|
671
|
-
return _pagesWatcher;
|
|
672
|
-
}
|
|
673
873
|
function createRoutes2(ctx) {
|
|
674
874
|
return new Hono2().get("/", async (c) => {
|
|
675
|
-
|
|
676
|
-
const sites =
|
|
677
|
-
const recentPages =
|
|
875
|
+
if (!ctx.db) return c.json({ error: "Pages database not available" }, 503);
|
|
876
|
+
const sites = getSites(ctx.db);
|
|
877
|
+
const recentPages = getRecentPagesList(ctx.db);
|
|
678
878
|
return c.json({ sites, recentPages });
|
|
679
879
|
}).get("/feed", async (c) => {
|
|
680
|
-
|
|
681
|
-
let recentPages = await pw.getCachedRecentPages();
|
|
880
|
+
if (!ctx.db) return c.json({ error: "Pages database not available" }, 503);
|
|
682
881
|
const mind = c.req.query("mind");
|
|
683
|
-
if (mind) recentPages = recentPages.filter((p) => p.mind === mind);
|
|
684
882
|
const rawLimit = c.req.query("limit");
|
|
685
|
-
const limit = rawLimit ? parseInt(rawLimit, 10) : 8;
|
|
883
|
+
const limit = rawLimit ? parseInt(rawLimit, 10) || 8 : 8;
|
|
884
|
+
const recentPages = getRecentPagesList(ctx.db, { mind: mind || void 0, limit });
|
|
686
885
|
return c.json(
|
|
687
|
-
recentPages.
|
|
886
|
+
recentPages.map((p) => ({
|
|
688
887
|
id: `page-${p.mind}-${p.file}`,
|
|
689
888
|
title: `${p.mind}/${p.file}`,
|
|
690
889
|
url: p.url ?? `/minds/${p.mind}/pages/${p.file}`,
|
|
@@ -720,31 +919,6 @@ function createRoutes2(ctx) {
|
|
|
720
919
|
} catch (err) {
|
|
721
920
|
return c.json({ error: `Connection failed: ${err.message}` }, 502);
|
|
722
921
|
}
|
|
723
|
-
}).post("/notify", async (c) => {
|
|
724
|
-
const user = ctx.resolveUser(c);
|
|
725
|
-
if (!user) return c.json({ error: "Unauthorized" }, 401);
|
|
726
|
-
let body;
|
|
727
|
-
try {
|
|
728
|
-
body = await c.req.json();
|
|
729
|
-
} catch {
|
|
730
|
-
body = {};
|
|
731
|
-
}
|
|
732
|
-
const file = body.file ?? "page";
|
|
733
|
-
ctx.publishActivity(
|
|
734
|
-
{
|
|
735
|
-
type: "page_updated",
|
|
736
|
-
mind: user.username,
|
|
737
|
-
summary: `${user.username} updated ${file}`,
|
|
738
|
-
metadata: { file, iframeUrl: `/ext/pages/public/${user.username}/${file}` }
|
|
739
|
-
},
|
|
740
|
-
c
|
|
741
|
-
);
|
|
742
|
-
try {
|
|
743
|
-
const mod = await import("./pages-watcher-72OVPRMH.js");
|
|
744
|
-
mod.invalidateCache();
|
|
745
|
-
} catch {
|
|
746
|
-
}
|
|
747
|
-
return c.json({ ok: true });
|
|
748
922
|
}).get("/status/:name", async (c) => {
|
|
749
923
|
const user = ctx.resolveUser(c);
|
|
750
924
|
if (!user) return c.json({ error: "Unauthorized" }, 401);
|
|
@@ -768,90 +942,71 @@ function createRoutes2(ctx) {
|
|
|
768
942
|
var _voluteHome = null;
|
|
769
943
|
async function getVoluteHome() {
|
|
770
944
|
if (_voluteHome) return _voluteHome();
|
|
771
|
-
const mod = await import("./registry-
|
|
945
|
+
const mod = await import("./registry-PJ4S5PHQ.js");
|
|
772
946
|
_voluteHome = mod.voluteHome;
|
|
773
947
|
return _voluteHome();
|
|
774
948
|
}
|
|
775
949
|
function createPublicRoutes(ctx) {
|
|
776
950
|
return new Hono2().get("/:name/*", async (c) => {
|
|
777
951
|
const name = c.req.param("name");
|
|
952
|
+
if (name.includes("/") || name.includes("\\") || name === "." || name === "..")
|
|
953
|
+
return c.text("Not found", 404);
|
|
778
954
|
let pagesRoot;
|
|
779
955
|
if (name === "_system") {
|
|
780
956
|
const home = await getVoluteHome();
|
|
781
|
-
pagesRoot =
|
|
957
|
+
pagesRoot = resolve3(home, "shared", "pages");
|
|
782
958
|
} else {
|
|
783
|
-
|
|
784
|
-
if (!mindDirPath) return c.text("Not found", 404);
|
|
785
|
-
pagesRoot = resolve2(mindDirPath, "home", "public", "pages");
|
|
959
|
+
pagesRoot = resolve3(ctx.dataDir, "sites", name);
|
|
786
960
|
}
|
|
787
961
|
const prefix = `/public/${name}`;
|
|
788
962
|
const idx = c.req.path.indexOf(prefix);
|
|
789
963
|
const wildcard = idx >= 0 ? c.req.path.slice(idx + prefix.length) : "/";
|
|
790
|
-
const requestedPath =
|
|
791
|
-
if (requestedPath !== pagesRoot && !requestedPath.startsWith(pagesRoot
|
|
964
|
+
const requestedPath = resolve3(pagesRoot, wildcard.slice(1));
|
|
965
|
+
if (requestedPath !== pagesRoot && !requestedPath.startsWith(`${pagesRoot}/`))
|
|
792
966
|
return c.text("Forbidden", 403);
|
|
967
|
+
let fileToServe = requestedPath;
|
|
793
968
|
let fileStat = await stat(requestedPath).catch(() => null);
|
|
794
969
|
if (fileStat?.isDirectory()) {
|
|
795
|
-
const indexPath =
|
|
970
|
+
const indexPath = resolve3(requestedPath, "index.html");
|
|
796
971
|
fileStat = await stat(indexPath).catch(() => null);
|
|
797
972
|
if (fileStat?.isFile()) {
|
|
798
|
-
|
|
799
|
-
|
|
973
|
+
fileToServe = indexPath;
|
|
974
|
+
} else {
|
|
975
|
+
return c.text("Not found", 404);
|
|
800
976
|
}
|
|
977
|
+
} else if (!fileStat?.isFile()) {
|
|
801
978
|
return c.text("Not found", 404);
|
|
802
979
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
const body = await readFile(
|
|
980
|
+
const ext = extname(fileToServe);
|
|
981
|
+
const mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
982
|
+
try {
|
|
983
|
+
const body = await readFile(fileToServe);
|
|
807
984
|
return c.body(body, 200, { "Content-Type": mime });
|
|
985
|
+
} catch (err) {
|
|
986
|
+
const code = err.code;
|
|
987
|
+
if (code === "EACCES") return c.text("Forbidden", 403);
|
|
988
|
+
if (code === "ENOENT") return c.text("Not found", 404);
|
|
989
|
+
return c.text("Internal server error", 500);
|
|
808
990
|
}
|
|
809
|
-
return c.text("Not found", 404);
|
|
810
991
|
});
|
|
811
992
|
}
|
|
812
993
|
|
|
813
994
|
// packages/extensions/pages/src/index.ts
|
|
814
|
-
var assetsDir2 =
|
|
815
|
-
var skillsDir2 =
|
|
816
|
-
var _watcher = null;
|
|
817
|
-
async function getWatcher() {
|
|
818
|
-
if (_watcher) return _watcher;
|
|
819
|
-
_watcher = await import("./pages-watcher-72OVPRMH.js");
|
|
820
|
-
return _watcher;
|
|
821
|
-
}
|
|
822
|
-
var notifyHandler = async (args, ctx) => {
|
|
823
|
-
const mindName = ctx.mindName;
|
|
824
|
-
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
825
|
-
const file = args[0] || "page";
|
|
826
|
-
ctx.publishActivity({
|
|
827
|
-
type: "page_updated",
|
|
828
|
-
mind: mindName,
|
|
829
|
-
summary: `${mindName} updated ${file}`,
|
|
830
|
-
metadata: { file, iframeUrl: `/ext/pages/public/${mindName}/${file}` }
|
|
831
|
-
});
|
|
832
|
-
try {
|
|
833
|
-
const mod = await import("./pages-watcher-72OVPRMH.js");
|
|
834
|
-
mod.invalidateCache();
|
|
835
|
-
} catch {
|
|
836
|
-
}
|
|
837
|
-
return { output: `Notified: ${file}` };
|
|
838
|
-
};
|
|
995
|
+
var assetsDir2 = resolve4(import.meta.dirname, "../dist/ui");
|
|
996
|
+
var skillsDir2 = resolve4(import.meta.dirname, "../skills");
|
|
839
997
|
var src_default2 = createExtension({
|
|
840
998
|
id: "pages",
|
|
841
999
|
name: "Pages",
|
|
842
1000
|
version: "0.1.0",
|
|
843
1001
|
description: "Publish and serve web pages from mind directories",
|
|
1002
|
+
initDb,
|
|
844
1003
|
routes: (ctx) => createRoutes2(ctx),
|
|
845
1004
|
publicRoutes: (ctx) => createPublicRoutes(ctx),
|
|
846
|
-
commands:
|
|
847
|
-
notify: {
|
|
848
|
-
description: "Notify that a page was created or updated",
|
|
849
|
-
usage: "volute pages notify [filename]",
|
|
850
|
-
handler: notifyHandler
|
|
851
|
-
}
|
|
852
|
-
},
|
|
1005
|
+
commands: createCommands2(),
|
|
853
1006
|
skillsDir: skillsDir2,
|
|
854
1007
|
standardSkill: true,
|
|
1008
|
+
icon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="2" width="14" height="12" rx="1.5"/><path d="M1 5h14"/><circle cx="3" cy="3.5" r="0.5" fill="currentColor" stroke="none"/><circle cx="5" cy="3.5" r="0.5" fill="currentColor" stroke="none"/></svg>',
|
|
1009
|
+
color: "purple",
|
|
855
1010
|
ui: {
|
|
856
1011
|
assetsDir: assetsDir2,
|
|
857
1012
|
systemSection: {
|
|
@@ -859,144 +1014,24 @@ var src_default2 = createExtension({
|
|
|
859
1014
|
label: "Pages",
|
|
860
1015
|
urlPatterns: ["/pages", "/pages/:site", "/pages/:site/:path"]
|
|
861
1016
|
},
|
|
862
|
-
mindSections: [
|
|
863
|
-
{
|
|
864
|
-
id: "pages",
|
|
865
|
-
label: "Pages",
|
|
866
|
-
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="M2 8h12M8 2c-2 2-2 10 0 12M8 2c2 2 2 10 0 12"/></svg>'
|
|
867
|
-
}
|
|
868
|
-
],
|
|
1017
|
+
mindSections: [{ id: "pages", label: "Pages" }],
|
|
869
1018
|
feedSource: {
|
|
870
1019
|
endpoint: "/api/ext/pages/feed"
|
|
871
1020
|
}
|
|
872
|
-
},
|
|
873
|
-
onDaemonStart: () => {
|
|
874
|
-
getWatcher().then((w) => w.startSystemWatcher()).catch(
|
|
875
|
-
(err) => console.error("[pages] failed to start system watcher:", err.message)
|
|
876
|
-
);
|
|
877
|
-
},
|
|
878
|
-
onDaemonStop: () => {
|
|
879
|
-
getWatcher().then((w) => w.stopAllWatchers()).catch((err) => console.error("[pages] failed to stop watchers:", err.message));
|
|
880
|
-
},
|
|
881
|
-
onMindStart: (mindName) => {
|
|
882
|
-
getWatcher().then((w) => w.startWatcher(mindName)).catch(
|
|
883
|
-
(err) => console.error(`[pages] failed to start watcher for ${mindName}:`, err.message)
|
|
884
|
-
);
|
|
885
|
-
},
|
|
886
|
-
onMindStop: (mindName) => {
|
|
887
|
-
getWatcher().then((w) => w.stopWatcher(mindName)).catch(
|
|
888
|
-
(err) => console.error(`[pages] failed to stop watcher for ${mindName}:`, err.message)
|
|
889
|
-
);
|
|
890
1021
|
}
|
|
891
1022
|
});
|
|
892
1023
|
|
|
893
|
-
// src/lib/daemon/turn-tracker.ts
|
|
894
|
-
import { randomUUID } from "crypto";
|
|
895
|
-
import { eq } from "drizzle-orm";
|
|
896
|
-
var tlog = logger_default.child("turn-tracker");
|
|
897
|
-
var activeTurns = /* @__PURE__ */ new Map();
|
|
898
|
-
function key(mind, session) {
|
|
899
|
-
return `${mind}:${session ?? "*"}`;
|
|
900
|
-
}
|
|
901
|
-
async function createTurn(mind) {
|
|
902
|
-
const k = key(mind);
|
|
903
|
-
const existing = activeTurns.get(k);
|
|
904
|
-
if (existing) return existing.turnId;
|
|
905
|
-
const turnId = randomUUID();
|
|
906
|
-
try {
|
|
907
|
-
const db = await getDb();
|
|
908
|
-
await db.insert(turns).values({ id: turnId, mind, status: "active" });
|
|
909
|
-
} catch (err) {
|
|
910
|
-
tlog.error(`failed to create turn for ${mind}`, logger_default.errorData(err));
|
|
911
|
-
return void 0;
|
|
912
|
-
}
|
|
913
|
-
activeTurns.set(k, { turnId, lastToolUseEventId: void 0 });
|
|
914
|
-
return turnId;
|
|
915
|
-
}
|
|
916
|
-
function getActiveTurnId(mind, session) {
|
|
917
|
-
return (activeTurns.get(key(mind, session)) ?? activeTurns.get(key(mind)))?.turnId;
|
|
918
|
-
}
|
|
919
|
-
function trackToolUse(mind, session, eventId) {
|
|
920
|
-
const entry = activeTurns.get(key(mind, session)) ?? activeTurns.get(key(mind));
|
|
921
|
-
if (entry) entry.lastToolUseEventId = eventId;
|
|
922
|
-
}
|
|
923
|
-
function getLastToolUseEventId(mind, session) {
|
|
924
|
-
return (activeTurns.get(key(mind, session)) ?? activeTurns.get(key(mind)))?.lastToolUseEventId;
|
|
925
|
-
}
|
|
926
|
-
async function assignSession(mind, turnId, session) {
|
|
927
|
-
const wildcardKey = key(mind);
|
|
928
|
-
const entry = activeTurns.get(wildcardKey);
|
|
929
|
-
if (!entry || entry.turnId !== turnId) {
|
|
930
|
-
tlog.warn(`assignSession: no matching turn for ${mind} (turnId=${turnId}, session=${session})`);
|
|
931
|
-
return;
|
|
932
|
-
}
|
|
933
|
-
try {
|
|
934
|
-
const db = await getDb();
|
|
935
|
-
await db.update(turns).set({ session }).where(eq(turns.id, turnId));
|
|
936
|
-
} catch (err) {
|
|
937
|
-
tlog.error(`failed to assign session to turn ${turnId}`, logger_default.errorData(err));
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
activeTurns.delete(wildcardKey);
|
|
941
|
-
activeTurns.set(key(mind, session), entry);
|
|
942
|
-
}
|
|
943
|
-
async function completeTurn(mind, session) {
|
|
944
|
-
const k = key(mind, session);
|
|
945
|
-
const wildcardKey = key(mind);
|
|
946
|
-
const entry = activeTurns.get(k) ?? activeTurns.get(wildcardKey);
|
|
947
|
-
if (!entry) return void 0;
|
|
948
|
-
try {
|
|
949
|
-
const db = await getDb();
|
|
950
|
-
await db.update(turns).set({ status: "complete" }).where(eq(turns.id, entry.turnId));
|
|
951
|
-
} catch (err) {
|
|
952
|
-
tlog.error(`failed to complete turn ${entry.turnId}`, logger_default.errorData(err));
|
|
953
|
-
return void 0;
|
|
954
|
-
}
|
|
955
|
-
activeTurns.delete(k);
|
|
956
|
-
activeTurns.delete(wildcardKey);
|
|
957
|
-
return entry.turnId;
|
|
958
|
-
}
|
|
959
|
-
async function setSummaryEventId(turnId, summaryEventId) {
|
|
960
|
-
try {
|
|
961
|
-
const db = await getDb();
|
|
962
|
-
await db.update(turns).set({ summary_event_id: summaryEventId }).where(eq(turns.id, turnId));
|
|
963
|
-
} catch (err) {
|
|
964
|
-
tlog.error(`failed to set summary event for turn ${turnId}`, logger_default.errorData(err));
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
async function clearMind(mind) {
|
|
968
|
-
const toDelete = [];
|
|
969
|
-
const turnIds = [];
|
|
970
|
-
for (const [k, entry] of activeTurns.entries()) {
|
|
971
|
-
if (k.startsWith(`${mind}:`)) {
|
|
972
|
-
turnIds.push(entry.turnId);
|
|
973
|
-
toDelete.push(k);
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
for (const k of toDelete) activeTurns.delete(k);
|
|
977
|
-
if (turnIds.length > 0) {
|
|
978
|
-
try {
|
|
979
|
-
const db = await getDb();
|
|
980
|
-
for (const id of turnIds) {
|
|
981
|
-
await db.update(turns).set({ status: "complete" }).where(eq(turns.id, id));
|
|
982
|
-
}
|
|
983
|
-
} catch (err) {
|
|
984
|
-
tlog.error(`failed to complete orphaned turns for ${mind}`, logger_default.errorData(err));
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
|
|
989
1024
|
// src/lib/systems-config.ts
|
|
990
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
991
|
-
import { resolve as
|
|
1025
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
|
|
1026
|
+
import { resolve as resolve5 } from "path";
|
|
992
1027
|
var DEFAULT_API_URL = "https://volute.systems";
|
|
993
1028
|
function configPath() {
|
|
994
|
-
return
|
|
1029
|
+
return resolve5(voluteSystemDir(), "systems.json");
|
|
995
1030
|
}
|
|
996
1031
|
function readSystemsConfig() {
|
|
997
1032
|
const path = configPath();
|
|
998
|
-
if (!
|
|
999
|
-
const raw =
|
|
1033
|
+
if (!existsSync2(path)) return null;
|
|
1034
|
+
const raw = readFileSync2(path, "utf-8");
|
|
1000
1035
|
let data;
|
|
1001
1036
|
try {
|
|
1002
1037
|
data = JSON.parse(raw);
|
|
@@ -1032,19 +1067,19 @@ function deleteSystemsConfig() {
|
|
|
1032
1067
|
var VALID_EXTENSION_ID2 = /^[a-z0-9][a-z0-9_-]*$/;
|
|
1033
1068
|
var loaded = [];
|
|
1034
1069
|
function extensionsBaseDir() {
|
|
1035
|
-
return
|
|
1070
|
+
return resolve6(voluteHome(), "extensions");
|
|
1036
1071
|
}
|
|
1037
1072
|
function extensionDataDir(id) {
|
|
1038
|
-
return
|
|
1073
|
+
return resolve6(voluteSystemDir(), "extension-data", id);
|
|
1039
1074
|
}
|
|
1040
1075
|
function extensionsConfigPath() {
|
|
1041
|
-
return
|
|
1076
|
+
return resolve6(voluteHome(), "system", "extensions.json");
|
|
1042
1077
|
}
|
|
1043
1078
|
function readExtensionsConfig() {
|
|
1044
1079
|
const configPath2 = extensionsConfigPath();
|
|
1045
|
-
if (!
|
|
1080
|
+
if (!existsSync3(configPath2)) return [];
|
|
1046
1081
|
try {
|
|
1047
|
-
const data = JSON.parse(
|
|
1082
|
+
const data = JSON.parse(readFileSync3(configPath2, "utf-8"));
|
|
1048
1083
|
return Array.isArray(data) ? data : [];
|
|
1049
1084
|
} catch (err) {
|
|
1050
1085
|
logger_default.warn("failed to read extensions config, ignoring installed extensions", {
|
|
@@ -1062,7 +1097,7 @@ async function getLibsqlDatabase() {
|
|
|
1062
1097
|
return _LibsqlDatabase;
|
|
1063
1098
|
}
|
|
1064
1099
|
async function openExtensionDb(_id, dataDir) {
|
|
1065
|
-
const dbPath =
|
|
1100
|
+
const dbPath = resolve6(dataDir, "data.db");
|
|
1066
1101
|
const Database = await getLibsqlDatabase();
|
|
1067
1102
|
return new Database(dbPath);
|
|
1068
1103
|
}
|
|
@@ -1088,22 +1123,23 @@ async function buildContext(manifest, dataDir, authMw) {
|
|
|
1088
1123
|
},
|
|
1089
1124
|
getUser: async (id) => getUser(id),
|
|
1090
1125
|
getUserByUsername: async (username) => getUserByUsername(username),
|
|
1091
|
-
publishActivity: (event
|
|
1092
|
-
const
|
|
1093
|
-
const turnId = getActiveTurnId(event.mind, session);
|
|
1094
|
-
const sourceEventId = getLastToolUseEventId(event.mind, session);
|
|
1095
|
-
publish({
|
|
1126
|
+
publishActivity: (event) => {
|
|
1127
|
+
const enriched = {
|
|
1096
1128
|
...event,
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1129
|
+
metadata: {
|
|
1130
|
+
...event.metadata,
|
|
1131
|
+
...manifest.icon && !event.metadata?.icon ? { icon: manifest.icon } : {},
|
|
1132
|
+
...manifest.color && !event.metadata?.color ? { color: manifest.color } : {}
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
publish(enriched).catch(
|
|
1100
1136
|
(err) => logger_default.error(`extension ${manifest.id}: failed to publish activity`, logger_default.errorData(err))
|
|
1101
1137
|
);
|
|
1102
1138
|
},
|
|
1103
1139
|
getMindDir: (name) => {
|
|
1104
1140
|
try {
|
|
1105
1141
|
const dir = mindDir(name);
|
|
1106
|
-
return
|
|
1142
|
+
return existsSync3(dir) ? dir : null;
|
|
1107
1143
|
} catch (err) {
|
|
1108
1144
|
logger_default.warn(
|
|
1109
1145
|
`extension ${manifest.id}: failed to resolve mind dir for ${name}`,
|
|
@@ -1146,15 +1182,36 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1146
1182
|
const mindName = body.mind || user?.username;
|
|
1147
1183
|
const session = c.get("mindSession");
|
|
1148
1184
|
try {
|
|
1185
|
+
const activityPromises = [];
|
|
1149
1186
|
const result = await cmd.handler(body.args ?? [], {
|
|
1150
1187
|
...context,
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1188
|
+
publishActivity: (rawEvent) => {
|
|
1189
|
+
const event = {
|
|
1190
|
+
...rawEvent,
|
|
1191
|
+
metadata: {
|
|
1192
|
+
...rawEvent.metadata,
|
|
1193
|
+
...manifest.icon && !rawEvent.metadata?.icon ? { icon: manifest.icon } : {},
|
|
1194
|
+
...manifest.color && !rawEvent.metadata?.color ? { color: manifest.color } : {}
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
activityPromises.push(
|
|
1198
|
+
publish(event).catch((err) => {
|
|
1199
|
+
logger_default.error(
|
|
1200
|
+
`extension ${manifest.id}: failed to publish activity`,
|
|
1201
|
+
logger_default.errorData(err)
|
|
1202
|
+
);
|
|
1203
|
+
return 0;
|
|
1204
|
+
})
|
|
1205
|
+
);
|
|
1206
|
+
},
|
|
1154
1207
|
mindName,
|
|
1155
|
-
session
|
|
1208
|
+
session,
|
|
1209
|
+
stdin: body.stdin
|
|
1156
1210
|
});
|
|
1157
|
-
|
|
1211
|
+
const activityIds = (await Promise.all(activityPromises)).filter((id) => id > 0);
|
|
1212
|
+
const markers = activityIds.map((id) => `[volute:activity:${id}]`).join("");
|
|
1213
|
+
const output = result && typeof result === "object" && "output" in result ? { ...result, output: `${result.output}${markers}` } : markers ? { ...result, output: markers } : result;
|
|
1214
|
+
return c.json(output);
|
|
1158
1215
|
} catch (err) {
|
|
1159
1216
|
logger_default.error(`extension command ${manifest.id}/${cmdName} failed`, logger_default.errorData(err));
|
|
1160
1217
|
return c.json({ error: err.message }, 500);
|
|
@@ -1163,18 +1220,18 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1163
1220
|
}
|
|
1164
1221
|
}
|
|
1165
1222
|
let resolvedAssetsDir = manifest.ui?.assetsDir ?? "";
|
|
1166
|
-
if (resolvedAssetsDir && !
|
|
1223
|
+
if (resolvedAssetsDir && !existsSync3(resolvedAssetsDir)) {
|
|
1167
1224
|
let searchDir = dirname(new URL(import.meta.url).pathname);
|
|
1168
1225
|
for (let i = 0; i < 5; i++) {
|
|
1169
|
-
const candidate =
|
|
1170
|
-
if (
|
|
1226
|
+
const candidate = resolve6(searchDir, "packages", "extensions", manifest.id, "dist", "ui");
|
|
1227
|
+
if (existsSync3(candidate)) {
|
|
1171
1228
|
resolvedAssetsDir = candidate;
|
|
1172
1229
|
break;
|
|
1173
1230
|
}
|
|
1174
1231
|
searchDir = dirname(searchDir);
|
|
1175
1232
|
}
|
|
1176
1233
|
}
|
|
1177
|
-
if (resolvedAssetsDir &&
|
|
1234
|
+
if (resolvedAssetsDir && existsSync3(resolvedAssetsDir)) {
|
|
1178
1235
|
const assetsDir3 = resolvedAssetsDir;
|
|
1179
1236
|
const { readFile: readFile2, stat: fsStat } = await import("fs/promises");
|
|
1180
1237
|
const { extname: ext } = await import("path");
|
|
@@ -1191,11 +1248,11 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1191
1248
|
".woff2": "font/woff2"
|
|
1192
1249
|
};
|
|
1193
1250
|
const prefix = `/ext/${manifest.id}`;
|
|
1194
|
-
const indexPath =
|
|
1251
|
+
const indexPath = resolve6(assetsDir3, "index.html");
|
|
1195
1252
|
const serveExtAssets = async (c) => {
|
|
1196
1253
|
const urlPath = new URL(c.req.url).pathname;
|
|
1197
1254
|
const relativePath = urlPath.slice(prefix.length).replace(/^\//, "") || "index.html";
|
|
1198
|
-
const filePath =
|
|
1255
|
+
const filePath = resolve6(assetsDir3, relativePath);
|
|
1199
1256
|
if (filePath !== assetsDir3 && !filePath.startsWith(assetsDir3 + "/"))
|
|
1200
1257
|
return c.text("Forbidden", 403);
|
|
1201
1258
|
const s = await fsStat(filePath).catch(() => null);
|
|
@@ -1204,7 +1261,7 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1204
1261
|
const body = await readFile2(filePath);
|
|
1205
1262
|
return c.body(body, 200, { "Content-Type": mime });
|
|
1206
1263
|
}
|
|
1207
|
-
if (
|
|
1264
|
+
if (existsSync3(indexPath)) {
|
|
1208
1265
|
const body = await readFile2(indexPath, "utf-8");
|
|
1209
1266
|
return c.html(body);
|
|
1210
1267
|
}
|
|
@@ -1217,7 +1274,7 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1217
1274
|
if (skillsDir3) {
|
|
1218
1275
|
let entries;
|
|
1219
1276
|
try {
|
|
1220
|
-
entries =
|
|
1277
|
+
entries = readdirSync2(skillsDir3, { withFileTypes: true });
|
|
1221
1278
|
} catch (err) {
|
|
1222
1279
|
logger_default.error(`failed to read skills dir for extension ${manifest.id}`, logger_default.errorData(err));
|
|
1223
1280
|
entries = [];
|
|
@@ -1225,10 +1282,10 @@ async function loadExtension(manifest, app, authMw) {
|
|
|
1225
1282
|
for (const entry of entries) {
|
|
1226
1283
|
if (!entry.isDirectory()) continue;
|
|
1227
1284
|
try {
|
|
1228
|
-
const skillPath =
|
|
1285
|
+
const skillPath = resolve6(skillsDir3, entry.name);
|
|
1229
1286
|
const sourceHash = hashSkillDir(skillPath);
|
|
1230
|
-
const destDir =
|
|
1231
|
-
if (
|
|
1287
|
+
const destDir = resolve6(sharedSkillsDir(), entry.name);
|
|
1288
|
+
if (existsSync3(destDir)) {
|
|
1232
1289
|
const destHash = hashSkillDir(destDir);
|
|
1233
1290
|
if (sourceHash === destHash) continue;
|
|
1234
1291
|
}
|
|
@@ -1252,11 +1309,11 @@ function resolveSkillsDir(manifest) {
|
|
|
1252
1309
|
if (!manifest.skillsDir) return null;
|
|
1253
1310
|
let searchDir = dirname(new URL(import.meta.url).pathname);
|
|
1254
1311
|
for (let i = 0; i < 5; i++) {
|
|
1255
|
-
const candidate =
|
|
1256
|
-
if (
|
|
1312
|
+
const candidate = resolve6(searchDir, "packages", "extensions", manifest.id, "skills");
|
|
1313
|
+
if (existsSync3(candidate)) return candidate;
|
|
1257
1314
|
searchDir = dirname(searchDir);
|
|
1258
1315
|
}
|
|
1259
|
-
if (
|
|
1316
|
+
if (existsSync3(manifest.skillsDir)) return manifest.skillsDir;
|
|
1260
1317
|
logger_default.warn(`skills dir not found for extension ${manifest.id}: ${manifest.skillsDir}`);
|
|
1261
1318
|
return null;
|
|
1262
1319
|
}
|
|
@@ -1266,14 +1323,14 @@ function discoverBuiltinExtensions() {
|
|
|
1266
1323
|
async function discoverInstalledExtensions() {
|
|
1267
1324
|
const manifests = [];
|
|
1268
1325
|
const packages = readExtensionsConfig();
|
|
1269
|
-
const npmDir =
|
|
1326
|
+
const npmDir = resolve6(voluteHome(), "extensions", "_npm");
|
|
1270
1327
|
const { createRequire } = await import("module");
|
|
1271
1328
|
for (const pkg of packages) {
|
|
1272
1329
|
try {
|
|
1273
1330
|
let resolved = pkg;
|
|
1274
|
-
const npmPkgDir =
|
|
1275
|
-
if (
|
|
1276
|
-
const require2 = createRequire(
|
|
1331
|
+
const npmPkgDir = resolve6(npmDir, "node_modules", pkg);
|
|
1332
|
+
if (existsSync3(npmPkgDir)) {
|
|
1333
|
+
const require2 = createRequire(resolve6(npmDir, "noop.js"));
|
|
1277
1334
|
resolved = require2.resolve(pkg);
|
|
1278
1335
|
}
|
|
1279
1336
|
const mod = await import(resolved);
|
|
@@ -1316,19 +1373,19 @@ function validateManifest(manifest, source) {
|
|
|
1316
1373
|
}
|
|
1317
1374
|
async function discoverLocalExtensions() {
|
|
1318
1375
|
const baseDir = extensionsBaseDir();
|
|
1319
|
-
if (!
|
|
1376
|
+
if (!existsSync3(baseDir)) return [];
|
|
1320
1377
|
const manifests = [];
|
|
1321
1378
|
let entries;
|
|
1322
1379
|
try {
|
|
1323
|
-
entries =
|
|
1380
|
+
entries = readdirSync2(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name !== "_npm").map((d) => d.name);
|
|
1324
1381
|
} catch (err) {
|
|
1325
1382
|
logger_default.error("failed to read local extensions directory", logger_default.errorData(err));
|
|
1326
1383
|
return [];
|
|
1327
1384
|
}
|
|
1328
1385
|
for (const dir of entries) {
|
|
1329
|
-
const extDir =
|
|
1330
|
-
const candidates = [
|
|
1331
|
-
const entryPoint = candidates.find((p) =>
|
|
1386
|
+
const extDir = resolve6(baseDir, dir);
|
|
1387
|
+
const candidates = [resolve6(extDir, "src", "index.js"), resolve6(extDir, "index.js")];
|
|
1388
|
+
const entryPoint = candidates.find((p) => existsSync3(p));
|
|
1332
1389
|
if (!entryPoint) continue;
|
|
1333
1390
|
try {
|
|
1334
1391
|
const mod = await import(entryPoint);
|
|
@@ -1390,6 +1447,7 @@ function getLoadedExtensions() {
|
|
|
1390
1447
|
name: manifest.name,
|
|
1391
1448
|
version: manifest.version,
|
|
1392
1449
|
description: manifest.description,
|
|
1450
|
+
icon: manifest.icon,
|
|
1393
1451
|
systemSection: manifest.ui?.systemSection,
|
|
1394
1452
|
mindSections: manifest.ui?.mindSections,
|
|
1395
1453
|
feedSource: manifest.ui?.feedSource,
|
|
@@ -1404,7 +1462,7 @@ function getExtensionStandardSkills() {
|
|
|
1404
1462
|
const dir = resolveSkillsDir(manifest);
|
|
1405
1463
|
if (!dir) continue;
|
|
1406
1464
|
try {
|
|
1407
|
-
for (const entry of
|
|
1465
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
1408
1466
|
if (entry.isDirectory()) skills.push(entry.name);
|
|
1409
1467
|
}
|
|
1410
1468
|
} catch (err) {
|
|
@@ -1457,14 +1515,6 @@ function notifyExtensionsMindStop(mindName) {
|
|
|
1457
1515
|
}
|
|
1458
1516
|
|
|
1459
1517
|
export {
|
|
1460
|
-
createTurn,
|
|
1461
|
-
getActiveTurnId,
|
|
1462
|
-
trackToolUse,
|
|
1463
|
-
getLastToolUseEventId,
|
|
1464
|
-
assignSession,
|
|
1465
|
-
completeTurn,
|
|
1466
|
-
setSummaryEventId,
|
|
1467
|
-
clearMind,
|
|
1468
1518
|
readSystemsConfig,
|
|
1469
1519
|
writeSystemsConfig,
|
|
1470
1520
|
deleteSystemsConfig,
|