volute 0.30.1 → 0.32.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 +15 -22
- package/dist/{accept-E3PAH3QJ.js → accept-74M7I4RZ.js} +5 -4
- package/dist/{activity-events-BKBPPUBP.js → activity-events-HETAODOK.js} +3 -2
- package/dist/{ai-service-VAJT5UBS.js → ai-service-ZIPCV3MX.js} +20 -5
- package/dist/api.d.ts +341 -397
- package/dist/{archive-WWDBWYN2.js → archive-INXYFVCW.js} +3 -2
- package/dist/auth-6DMGES3I.js +44 -0
- package/dist/{bridge-RO37CUFM.js → bridge-BVCBTGPF.js} +5 -4
- package/dist/{chat-TCUNPFGO.js → chat-XT4OBJBU.js} +8 -8
- package/dist/{chunk-P7VFDSSG.js → chunk-2FLJ63GU.js} +2 -2
- package/dist/{chunk-ZWKTUQEL.js → chunk-2NGTS5UU.js} +1 -1
- package/dist/{chunk-JGFRDMR6.js → chunk-ALEF47VT.js} +1 -1
- package/dist/{chunk-MDPCSXZ4.js → chunk-D5G5YOPL.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-DTC6EH5I.js → chunk-I5KY25PQ.js} +1 -9
- package/dist/{chunk-NSBFETWP.js → chunk-IYDIE3HG.js} +64 -26
- package/dist/{chunk-W5OOPLNP.js → chunk-JJ7W6WSB.js} +3 -3
- package/dist/{chunk-G3GBKZGG.js → chunk-LGB6JBHI.js} +54 -2
- package/dist/chunk-LRCG2JLP.js +251 -0
- package/dist/{chunk-FXHXHI2A.js → chunk-LSGWR54X.js} +3 -6
- package/dist/{chunk-S5LR3XYJ.js → chunk-M7UL5S3Q.js} +1 -1
- package/dist/chunk-PB65JZK2.js +85 -0
- package/dist/chunk-PVY5W6QN.js +41 -0
- package/dist/{chunk-QVAQ5454.js → chunk-QBQ424EM.js} +3007 -2126
- package/dist/{chunk-P27RV5WM.js → chunk-QZANELPX.js} +6 -2
- package/dist/{chunk-FSM45XD5.js → chunk-R7E6CRVQ.js} +1 -1
- package/dist/{chunk-HHTXM4JT.js → chunk-RPZZSXV3.js} +39 -195
- package/dist/{chunk-UPA6COHU.js → chunk-RSX4OPZY.js} +5 -5
- package/dist/{chunk-2C2VXEBB.js → chunk-S6NFERDC.js} +21 -57
- package/dist/chunk-SKLSMHXO.js +208 -0
- package/dist/{chunk-IKHDUZRH.js → chunk-SX5TKJBZ.js} +2 -2
- package/dist/chunk-TDRYEPH4.js +185 -0
- package/dist/chunk-TSXLLQZW.js +46 -0
- package/dist/{chunk-EFVHR7KH.js → chunk-UKVWJRKN.js} +24 -5
- package/dist/{chunk-2NDZC3S7.js → chunk-WKF5FEFK.js} +688 -389
- package/dist/cli.js +93 -24
- package/dist/{clock-G3ALCMLJ.js → clock-2UOZ6JPU.js} +11 -8
- package/dist/{cloud-sync-JV4LJOK3.js → cloud-sync-JN3NWKEM.js} +16 -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-7KVQV7EZ.js → conversations-3O5O6AS3.js} +8 -7
- package/dist/{create-JTLS7GX3.js → create-RNLNCORE.js} +5 -4
- package/dist/{create-VQSQHJQW.js → create-WBBYI6V7.js} +6 -2
- package/dist/daemon-client-6QXHZ7US.js +12 -0
- package/dist/{daemon-restart-4JGBHEJ4.js → daemon-restart-NGFHFAUF.js} +7 -7
- package/dist/daemon.js +2446 -1999
- package/dist/{db-HMFPIRO2.js → db-F34YLV7D.js} +2 -1
- package/dist/db-RA45JBFG.js +16 -0
- package/dist/{delete-JESHKE7F.js → delete-QTGWEDBI.js} +1 -1
- package/dist/delivery-manager-SDVXFD4W.js +28 -0
- package/dist/delivery-router-FL45JL7N.js +21 -0
- package/dist/down-TB3ESMNP.js +14 -0
- package/dist/{env-CLXXT7M2.js → env-RLYQBOOP.js} +5 -4
- package/dist/{export-EGA5M5PB.js → export-SUYRLI5Q.js} +4 -3
- package/dist/{extension-WZ4SUPJB.js → extension-FQ5D3NCC.js} +6 -6
- package/dist/{extensions-ECO4RPFQ.js → extensions-GDYWQXC4.js} +9 -7
- package/dist/{files-4VEJDASH.js → files-EAMPO2SJ.js} +6 -5
- package/dist/{history-EJMMLXDO.js → history-FO5PHBQ5.js} +9 -4
- package/dist/{import-YCGPMBSI.js → import-DDUFE7AY.js} +4 -3
- package/dist/{join-2GBJKZEN.js → join-I5QEE3LG.js} +1 -1
- package/dist/{list-Q6O7FGAN.js → list-DW2VRTOZ.js} +5 -4
- package/dist/{login-RL6AU2SM.js → login-7CHPW2PN.js} +5 -4
- package/dist/{login-RET5WESK.js → login-RIJF2F4G.js} +3 -2
- package/dist/{logout-CGAGJN3L.js → logout-5MLHZALK.js} +3 -2
- package/dist/{logout-JRPBEMMR.js → logout-UZJRGY4Z.js} +3 -2
- package/dist/message-delivery-2FIM7QKO.js +32 -0
- package/dist/{mind-LUWRQUQ5.js → mind-2B6M7Y25.js} +18 -18
- package/dist/{mind-activity-tracker-VYN2ZZ2M.js → mind-activity-tracker-NZZT2NTT.js} +4 -3
- package/dist/{mind-list-V5WW5DUA.js → mind-list-WUPMQDYQ.js} +3 -2
- package/dist/mind-manager-BNCMGYXW.js +28 -0
- package/dist/mind-service-AV273WT4.js +34 -0
- package/dist/{mind-sleep-R6PTNNW4.js → mind-sleep-B7BHJLH7.js} +5 -4
- package/dist/{mind-status-I4ISFJ6I.js → mind-status-L3EFFRPR.js} +3 -2
- package/dist/{mind-wake-67ZQEWAV.js → mind-wake-GY3RFX7Y.js} +5 -4
- package/dist/{package-OYUD4ZJ4.js → package-PK6JUFL3.js} +3 -3
- package/dist/read-5AMJRO3D.js +75 -0
- package/dist/{register-NZDSTLP3.js → register-V2JZZKFK.js} +5 -4
- package/dist/{registry-ODSALQQL.js → registry-PJ4S5PHQ.js} +8 -1
- package/dist/{reject-2HZOJEIJ.js → reject-33HEZMZ4.js} +5 -4
- package/dist/{restart-QHS3NT64.js → restart-3UCMRUVC.js} +5 -4
- package/dist/{sandbox-O5FUSF43.js → sandbox-JANNTX6U.js} +4 -3
- package/dist/schema-PA3M5ZKH.js +32 -0
- package/dist/seed-ALUQ55FF.js +112 -0
- package/dist/{send-OAN3RYYY.js → send-3MI36LEF.js} +58 -69
- package/dist/{setup-QMDK5RZX.js → setup-SZIARWI6.js} +5 -4
- package/dist/{setup-XJH3E7YM.js → setup-WENLVPVP.js} +9 -9
- package/dist/{skill-FZIN4W4Q.js → skill-TUVOTW4Z.js} +5 -4
- package/dist/skills/dreaming/SKILL.md +6 -4
- package/dist/skills/dreaming/references/INSTALL.md +4 -5
- package/dist/skills/dreaming/scripts/dream.ts +5 -27
- package/dist/skills/dreaming/scripts/wake-context-dreams.sh +1 -1
- package/dist/skills/imagegen/SKILL.md +6 -5
- package/dist/skills/imagegen/references/INSTALL.md +1 -1
- 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/volute-admin/SKILL.md +83 -0
- package/dist/skills/volute-mind/SKILL.md +12 -12
- package/dist/skills-XNZK6P4K.js +61 -0
- package/dist/sleep-manager-53DZOWW7.js +32 -0
- package/dist/spirit-N4W4UQRH.js +217 -0
- package/dist/{split-EXYGGGQN.js → split-STOROBYJ.js} +1 -1
- package/dist/{sprout-AXQ6H5DB.js → sprout-L2GFOVF7.js} +9 -8
- package/dist/{start-MTOVL6SY.js → start-K2NCUUCG.js} +5 -4
- package/dist/{status-ZRO37MWR.js → status-TCUMUO6M.js} +5 -5
- package/dist/{stop-OK5WEPVC.js → stop-H26JZDXF.js} +5 -4
- package/dist/system-chat-NPYFYZVI.js +32 -0
- package/dist/{systems-W3BBMSOZ.js → systems-DHBKVYEY.js} +6 -5
- package/dist/{tailscale-BM72RXCJ.js → tailscale-XHQBZROW.js} +2 -1
- package/dist/{template-hash-3HOR4UAJ.js → template-hash-A6VVKOXJ.js} +2 -1
- package/dist/up-6I6BHRTO.js +17 -0
- package/dist/{update-PLPHMMZ2.js → update-QVPRF6GR.js} +5 -5
- package/dist/{update-check-CVCN7MF6.js → update-check-ZD6OOIYQ.js} +3 -2
- package/dist/{upgrade-I6NPCYUU.js → upgrade-O4Q7WJM3.js} +12 -14
- package/dist/{version-notify-2NTWVEHL.js → version-notify-TCKWBZZG.js} +22 -23
- package/dist/web-assets/assets/index-Bui7U9Uu.css +1 -0
- package/dist/web-assets/assets/index-e36DIo1b.js +73 -0
- package/dist/web-assets/ext-theme.css +94 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0000_baseline.sql +152 -0
- package/drizzle/0001_add_conversation_private.sql +1 -0
- package/drizzle/0002_turns.sql +21 -0
- package/drizzle/0003_turn_feed_links.sql +11 -0
- package/drizzle/0004_spirits.sql +5 -0
- package/drizzle/meta/0000_snapshot.json +3 -223
- package/drizzle/meta/0001_snapshot.json +3 -294
- package/drizzle/meta/0002_snapshot.json +3 -335
- package/drizzle/meta/0003_snapshot.json +3 -413
- package/drizzle/meta/0004_snapshot.json +3 -406
- package/drizzle/meta/_journal.json +10 -101
- package/package.json +3 -3
- 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/notes/skills/notes/SKILL.md +8 -8
- package/packages/extensions/pages/skills/pages/SKILL.md +17 -44
- package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +40 -0
- package/templates/_base/.init/.local/bin/volute +27 -0
- 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/auto-commit.ts +82 -43
- package/templates/_base/src/lib/daemon-client.ts +40 -36
- 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/router.ts +17 -1
- package/templates/_base/src/lib/startup.ts +17 -12
- package/templates/_base/src/lib/transparency.ts +2 -2
- package/templates/_base/src/lib/volute-server.ts +2 -5
- package/templates/claude/.init/.claude/settings.json +1 -1
- package/templates/claude/.init/.config/routes.json +2 -2
- package/templates/claude/src/agent.ts +97 -14
- package/templates/claude/src/lib/hooks/auto-commit.ts +7 -3
- package/templates/claude/src/lib/message-channel.ts +7 -2
- package/templates/claude/src/server.ts +0 -9
- 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 +553 -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/package.json.tmpl +1 -1
- package/templates/pi/src/agent.ts +63 -9
- package/templates/pi/src/lib/event-handler.ts +6 -4
- package/templates/pi/src/lib/reply-instructions-extension.ts +32 -11
- package/dist/chunk-7D47T4RB.js +0 -84
- package/dist/chunk-CVH6Y2YG.js +0 -59
- package/dist/chunk-EFP3PE6C.js +0 -232
- package/dist/chunk-LIRWLNAK.js +0 -729
- package/dist/daemon-client-BCTFGVCZ.js +0 -9
- package/dist/down-NGBMGORS.js +0 -14
- package/dist/message-delivery-6YMVNOEC.js +0 -28
- package/dist/migrate-registry-to-db-FK35IPEH.js +0 -110
- package/dist/mind-manager-YFCOIAAX.js +0 -18
- package/dist/pages-watcher-Z3PKNROC.js +0 -21
- package/dist/read-WQMPTSN2.js +0 -46
- package/dist/seed-WUQMPLDM.js +0 -71
- package/dist/skills/sessions/SKILL.md +0 -49
- package/dist/sleep-manager-O7YQFCV5.js +0 -30
- package/dist/up-BXUAIDXB.js +0 -17
- package/dist/web-assets/assets/index--kREqKl9.js +0 -72
- package/dist/web-assets/assets/index-BXYTG0nJ.css +0 -1
- package/drizzle/0000_flaky_mariko_yashida.sql +0 -34
- package/drizzle/0001_careless_warpath.sql +0 -12
- package/drizzle/0002_wealthy_the_call.sql +0 -6
- package/drizzle/0003_clean_ego.sql +0 -12
- package/drizzle/0004_magical_silverclaw.sql +0 -1
- package/drizzle/0005_rename_agents_to_minds.sql +0 -11
- package/drizzle/0006_mind_history.sql +0 -20
- package/drizzle/0007_system_prompts.sql +0 -5
- package/drizzle/0008_volute_channels.sql +0 -24
- package/drizzle/0009_shared_skills.sql +0 -9
- package/drizzle/0010_delivery_queue.sql +0 -12
- package/drizzle/0011_rename_human_to_brain.sql +0 -1
- package/drizzle/0012_activity.sql +0 -11
- package/drizzle/0013_user_profiles.sql +0 -3
- package/drizzle/0014_conversation_reads.sql +0 -7
- package/drizzle/0015_notes.sql +0 -23
- package/drizzle/0016_note_reactions_and_replies.sql +0 -15
- package/drizzle/0017_minds.sql +0 -16
- package/drizzle/meta/0005_snapshot.json +0 -410
- package/drizzle/meta/0006_snapshot.json +0 -7
- package/drizzle/meta/0007_snapshot.json +0 -7
- package/drizzle/meta/0008_snapshot.json +0 -7
- package/drizzle/meta/0009_snapshot.json +0 -7
- package/drizzle/meta/0010_snapshot.json +0 -7
- package/drizzle/meta/0011_snapshot.json +0 -7
- package/drizzle/meta/0012_snapshot.json +0 -7
- package/drizzle/meta/0013_snapshot.json +0 -7
- package/packages/extensions/notes/dist/ui/assets/index-DgawVO5g.css +0 -1
- package/packages/extensions/notes/dist/ui/assets/index-qUWoeC4c.js +0 -2
- package/packages/extensions/notes/skills/notes/scripts/notes.mjs +0 -185
- 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/home/public/.gitkeep +0 -0
- 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,185 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* notes.mjs — manage notes via the daemon API
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* node .claude/skills/notes/scripts/notes.mjs write "title" "content" [--reply-to author/slug]
|
|
7
|
-
* node .claude/skills/notes/scripts/notes.mjs list [--author name] [--limit N]
|
|
8
|
-
* node .claude/skills/notes/scripts/notes.mjs read <author/slug>
|
|
9
|
-
* node .claude/skills/notes/scripts/notes.mjs comment <author/slug> "content"
|
|
10
|
-
* node .claude/skills/notes/scripts/notes.mjs react <author/slug> "emoji"
|
|
11
|
-
* node .claude/skills/notes/scripts/notes.mjs delete <author/slug>
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const mind = process.env.VOLUTE_MIND;
|
|
15
|
-
const port = process.env.VOLUTE_DAEMON_PORT;
|
|
16
|
-
const token = process.env.VOLUTE_DAEMON_TOKEN;
|
|
17
|
-
|
|
18
|
-
if (!mind || !port || !token) {
|
|
19
|
-
console.error("Missing VOLUTE_MIND, VOLUTE_DAEMON_PORT, or VOLUTE_DAEMON_TOKEN");
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const baseUrl = `http://localhost:${port}/api/ext/notes`;
|
|
24
|
-
const headers = {
|
|
25
|
-
"Content-Type": "application/json",
|
|
26
|
-
Authorization: `Bearer ${token}`,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
async function apiFetch(path, opts = {}) {
|
|
30
|
-
const url = `${baseUrl}${path}`;
|
|
31
|
-
try {
|
|
32
|
-
return await fetch(url, { headers, ...opts });
|
|
33
|
-
} catch {
|
|
34
|
-
console.error(`Failed to reach daemon at localhost:${port}`);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function getFlag(args, flag) {
|
|
40
|
-
const idx = args.indexOf(flag);
|
|
41
|
-
if (idx !== -1 && args[idx + 1]) return args[idx + 1];
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const [command, ...args] = process.argv.slice(2);
|
|
46
|
-
|
|
47
|
-
switch (command) {
|
|
48
|
-
case "write": {
|
|
49
|
-
const title = args[0];
|
|
50
|
-
const content = args[1];
|
|
51
|
-
if (!title || !content) {
|
|
52
|
-
console.error(
|
|
53
|
-
'Usage: node .claude/skills/notes/scripts/notes.mjs write "title" "content" [--reply-to author/slug]',
|
|
54
|
-
);
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
const replyTo = getFlag(args, "--reply-to");
|
|
58
|
-
const body = { title, content };
|
|
59
|
-
if (replyTo) body.reply_to = replyTo;
|
|
60
|
-
|
|
61
|
-
const res = await apiFetch("", { method: "POST", body: JSON.stringify(body) });
|
|
62
|
-
const data = await res.json();
|
|
63
|
-
if (!res.ok) {
|
|
64
|
-
console.error(`Error: ${data.error ?? res.statusText}`);
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
console.log(`Published: ${data.author_username}/${data.slug}`);
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
case "list": {
|
|
72
|
-
const author = getFlag(args, "--author");
|
|
73
|
-
const limit = getFlag(args, "--limit") ?? "10";
|
|
74
|
-
const params = new URLSearchParams({ limit });
|
|
75
|
-
if (author) params.set("author", author);
|
|
76
|
-
|
|
77
|
-
const res = await apiFetch(`?${params}`);
|
|
78
|
-
const notes = await res.json();
|
|
79
|
-
if (!res.ok) {
|
|
80
|
-
console.error(`Error: ${notes.error ?? res.statusText}`);
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
for (const note of notes) {
|
|
84
|
-
const date = new Date(note.created_at).toLocaleDateString();
|
|
85
|
-
console.log(` ${note.author_username}/${note.slug} "${note.title}" (${date})`);
|
|
86
|
-
}
|
|
87
|
-
if (notes.length === 0) console.log("No notes found.");
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
case "read": {
|
|
92
|
-
const ref = args[0];
|
|
93
|
-
if (!ref || !ref.includes("/")) {
|
|
94
|
-
console.error("Usage: node .claude/skills/notes/scripts/notes.mjs read <author/slug>");
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
const res = await apiFetch(`/${ref}`);
|
|
98
|
-
const data = await res.json();
|
|
99
|
-
if (!res.ok) {
|
|
100
|
-
console.error(`Error: ${data.error ?? res.statusText}`);
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
console.log(`# ${data.title}\n`);
|
|
104
|
-
console.log(`By ${data.author_username} — ${new Date(data.created_at).toLocaleString()}\n`);
|
|
105
|
-
console.log(data.content);
|
|
106
|
-
if (data.reactions?.length) {
|
|
107
|
-
console.log(
|
|
108
|
-
`\nReactions: ${data.reactions.map((r) => `${r.emoji} (${r.count})`).join(" ")}`,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
if (data.comments?.length) {
|
|
112
|
-
console.log(`\nComments (${data.comments.length}):`);
|
|
113
|
-
for (const c of data.comments) {
|
|
114
|
-
console.log(` ${c.author_username}: ${c.content}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
case "comment": {
|
|
121
|
-
const ref = args[0];
|
|
122
|
-
const content = args[1];
|
|
123
|
-
if (!ref || !ref.includes("/") || !content) {
|
|
124
|
-
console.error(
|
|
125
|
-
'Usage: node .claude/skills/notes/scripts/notes.mjs comment <author/slug> "content"',
|
|
126
|
-
);
|
|
127
|
-
process.exit(1);
|
|
128
|
-
}
|
|
129
|
-
const res = await apiFetch(`/${ref}/comments`, {
|
|
130
|
-
method: "POST",
|
|
131
|
-
body: JSON.stringify({ content }),
|
|
132
|
-
});
|
|
133
|
-
const data = await res.json();
|
|
134
|
-
if (!res.ok) {
|
|
135
|
-
console.error(`Error: ${data.error ?? res.statusText}`);
|
|
136
|
-
process.exit(1);
|
|
137
|
-
}
|
|
138
|
-
console.log("Comment added.");
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
case "react": {
|
|
143
|
-
const ref = args[0];
|
|
144
|
-
const emoji = args[1];
|
|
145
|
-
if (!ref || !ref.includes("/") || !emoji) {
|
|
146
|
-
console.error(
|
|
147
|
-
'Usage: node .claude/skills/notes/scripts/notes.mjs react <author/slug> "emoji"',
|
|
148
|
-
);
|
|
149
|
-
process.exit(1);
|
|
150
|
-
}
|
|
151
|
-
const res = await apiFetch(`/${ref}/reactions`, {
|
|
152
|
-
method: "POST",
|
|
153
|
-
body: JSON.stringify({ emoji }),
|
|
154
|
-
});
|
|
155
|
-
const data = await res.json();
|
|
156
|
-
if (!res.ok) {
|
|
157
|
-
console.error(`Error: ${data.error ?? res.statusText}`);
|
|
158
|
-
process.exit(1);
|
|
159
|
-
}
|
|
160
|
-
console.log(data.added ? "Reaction added." : "Reaction removed.");
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
case "delete": {
|
|
165
|
-
const ref = args[0];
|
|
166
|
-
if (!ref || !ref.includes("/")) {
|
|
167
|
-
console.error("Usage: node .claude/skills/notes/scripts/notes.mjs delete <author/slug>");
|
|
168
|
-
process.exit(1);
|
|
169
|
-
}
|
|
170
|
-
const res = await apiFetch(`/${ref}`, { method: "DELETE" });
|
|
171
|
-
if (!res.ok) {
|
|
172
|
-
const data = await res.json();
|
|
173
|
-
console.error(`Error: ${data.error ?? res.statusText}`);
|
|
174
|
-
process.exit(1);
|
|
175
|
-
}
|
|
176
|
-
console.log("Note deleted.");
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
default:
|
|
181
|
-
console.error(
|
|
182
|
-
"Usage: node .claude/skills/notes/scripts/notes.mjs <write|list|read|comment|react|delete> [args]",
|
|
183
|
-
);
|
|
184
|
-
process.exit(1);
|
|
185
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Startup context hook — generates orientation context for new sessions.
|
|
3
|
-
# Edit this script to customize what you see when your session starts.
|
|
4
|
-
# Input: JSON on stdin with { "source": "startup" | "SessionStart" }
|
|
5
|
-
# Output: JSON with hookSpecificOutput.additionalContext (for SessionStart hook)
|
|
6
|
-
# or plain text (for direct execution by pi template)
|
|
7
|
-
|
|
8
|
-
INPUT=$(cat)
|
|
9
|
-
SOURCE=$(echo "$INPUT" | jq -r '.source // "startup"')
|
|
10
|
-
|
|
11
|
-
CONTEXT="Session ${SOURCE} at $(date '+%Y-%m-%d %H:%M')."
|
|
12
|
-
|
|
13
|
-
# Active sessions
|
|
14
|
-
SESSIONS_DIR=".mind/sessions"
|
|
15
|
-
if [ -d "$SESSIONS_DIR" ]; then
|
|
16
|
-
SESSION_LIST=$(ls -1 "$SESSIONS_DIR"/*.json 2>/dev/null | xargs -I{} basename {} .json | sort)
|
|
17
|
-
if [ -n "$SESSION_LIST" ]; then
|
|
18
|
-
CONTEXT="$CONTEXT Active sessions: $(echo "$SESSION_LIST" | tr '\n' ', ' | sed 's/, $//')."
|
|
19
|
-
fi
|
|
20
|
-
fi
|
|
21
|
-
|
|
22
|
-
# Last journal entry
|
|
23
|
-
JOURNAL_DIR="home/memory/journal"
|
|
24
|
-
if [ -d "$JOURNAL_DIR" ]; then
|
|
25
|
-
LATEST=$(ls -1 "$JOURNAL_DIR"/*.md 2>/dev/null | sort | tail -1)
|
|
26
|
-
if [ -n "$LATEST" ]; then
|
|
27
|
-
CONTEXT="$CONTEXT Last journal entry: $(basename "$LATEST" .md)."
|
|
28
|
-
fi
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
# Pending channel invites
|
|
32
|
-
INBOX_DIR="home/inbox"
|
|
33
|
-
if [ -d "$INBOX_DIR" ]; then
|
|
34
|
-
INVITE_COUNT=$(ls -1 "$INBOX_DIR"/*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
35
|
-
if [ "$INVITE_COUNT" -gt 0 ] 2>/dev/null; then
|
|
36
|
-
CONTEXT="$CONTEXT Pending channel invites: ${INVITE_COUNT} (check inbox/)."
|
|
37
|
-
fi
|
|
38
|
-
fi
|
|
39
|
-
|
|
40
|
-
# Output in SessionStart hook format
|
|
41
|
-
jq -n --arg ctx "$CONTEXT" '{
|
|
42
|
-
hookSpecificOutput: {
|
|
43
|
-
hookEventName: "SessionStart",
|
|
44
|
-
additionalContext: $ctx
|
|
45
|
-
}
|
|
46
|
-
}'
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx tsx
|
|
2
|
-
/**
|
|
3
|
-
* Session reader — displays a human-readable log of another session's activity.
|
|
4
|
-
*
|
|
5
|
-
* Usage: npx tsx .config/scripts/session-reader.ts <session-name> [--lines N]
|
|
6
|
-
*
|
|
7
|
-
* Runs from the mind's home/ directory.
|
|
8
|
-
*/
|
|
9
|
-
import { existsSync } from "node:fs";
|
|
10
|
-
import { resolve } from "node:path";
|
|
11
|
-
import {
|
|
12
|
-
readSessionLog,
|
|
13
|
-
resolveAgentSdkJsonl,
|
|
14
|
-
resolvePiJsonl,
|
|
15
|
-
} from "../../src/lib/session-monitor.js";
|
|
16
|
-
|
|
17
|
-
const args = process.argv.slice(2);
|
|
18
|
-
let sessionName: string | undefined;
|
|
19
|
-
let lines = 50;
|
|
20
|
-
|
|
21
|
-
for (let i = 0; i < args.length; i++) {
|
|
22
|
-
if (args[i] === "--lines" && args[i + 1]) {
|
|
23
|
-
lines = parseInt(args[++i], 10);
|
|
24
|
-
} else if (!sessionName && !args[i].startsWith("-")) {
|
|
25
|
-
sessionName = args[i];
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (!sessionName) {
|
|
30
|
-
console.error("Usage: npx tsx .config/scripts/session-reader.ts <session-name> [--lines N]");
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Detect template type and resolve JSONL path
|
|
35
|
-
const cwd = process.cwd();
|
|
36
|
-
const agentSdkSessions = resolve(cwd, "../.mind/sessions");
|
|
37
|
-
const piSessions = resolve(cwd, "../.mind/pi-sessions");
|
|
38
|
-
|
|
39
|
-
let jsonlPath: string | null = null;
|
|
40
|
-
let format: "agent-sdk" | "pi";
|
|
41
|
-
|
|
42
|
-
if (existsSync(agentSdkSessions)) {
|
|
43
|
-
format = "agent-sdk";
|
|
44
|
-
jsonlPath = resolveAgentSdkJsonl(agentSdkSessions, sessionName, cwd);
|
|
45
|
-
} else if (existsSync(piSessions)) {
|
|
46
|
-
format = "pi";
|
|
47
|
-
jsonlPath = resolvePiJsonl(piSessions, sessionName);
|
|
48
|
-
} else {
|
|
49
|
-
console.error("No session directory found. Expected .mind/sessions/ or .mind/pi-sessions/");
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (!jsonlPath || !existsSync(jsonlPath)) {
|
|
54
|
-
console.error(`No session log found for "${sessionName}".`);
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const output = readSessionLog({ jsonlPath, format, lines });
|
|
59
|
-
console.log(output);
|
|
File without changes
|
|
@@ -1,400 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
closeSync,
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
openSync,
|
|
6
|
-
readdirSync,
|
|
7
|
-
readFileSync,
|
|
8
|
-
readSync,
|
|
9
|
-
statSync,
|
|
10
|
-
writeFileSync,
|
|
11
|
-
} from "node:fs";
|
|
12
|
-
import { dirname, resolve } from "node:path";
|
|
13
|
-
|
|
14
|
-
// --- Types ---
|
|
15
|
-
|
|
16
|
-
type CursorState = Record<string, Record<string, { offset: number }>>;
|
|
17
|
-
|
|
18
|
-
type ParsedEntry = {
|
|
19
|
-
role: "user" | "assistant";
|
|
20
|
-
timestamp?: string;
|
|
21
|
-
text?: string;
|
|
22
|
-
toolUses?: { name: string; primaryArg?: string }[];
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
type SessionSummary = {
|
|
26
|
-
firstUserText: string;
|
|
27
|
-
toolCounts: { edits: number; reads: number; commands: number; other: number };
|
|
28
|
-
messageCount: number;
|
|
29
|
-
timeSpan: { first?: string; last?: string };
|
|
30
|
-
lastAssistantText?: string;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
type Format = "agent-sdk" | "pi";
|
|
34
|
-
|
|
35
|
-
// --- Public API ---
|
|
36
|
-
|
|
37
|
-
export function getSessionUpdates(options: {
|
|
38
|
-
currentSession: string;
|
|
39
|
-
sessionsDir: string;
|
|
40
|
-
cursorFile: string;
|
|
41
|
-
jsonlResolver: (sessionName: string) => string | null;
|
|
42
|
-
format: Format;
|
|
43
|
-
}): string | null {
|
|
44
|
-
const sessionNames = listSessionNames(options.sessionsDir, options.format);
|
|
45
|
-
const others = sessionNames.filter((n) => n !== options.currentSession && !n.startsWith("new-"));
|
|
46
|
-
if (others.length === 0) return null;
|
|
47
|
-
|
|
48
|
-
const cursors = loadCursors(options.cursorFile);
|
|
49
|
-
const currentCursors = cursors[options.currentSession] ?? {};
|
|
50
|
-
const summaries: string[] = [];
|
|
51
|
-
|
|
52
|
-
for (const name of others) {
|
|
53
|
-
try {
|
|
54
|
-
const jsonlPath = options.jsonlResolver(name);
|
|
55
|
-
if (!jsonlPath || !existsSync(jsonlPath)) continue;
|
|
56
|
-
|
|
57
|
-
const stat = statSync(jsonlPath);
|
|
58
|
-
const prevOffset = currentCursors[name]?.offset ?? 0;
|
|
59
|
-
const fileSize = stat.size;
|
|
60
|
-
|
|
61
|
-
// Reset if offset past EOF (file was truncated/recreated)
|
|
62
|
-
const offset = prevOffset > fileSize ? 0 : prevOffset;
|
|
63
|
-
if (offset >= fileSize) {
|
|
64
|
-
currentCursors[name] = { offset: fileSize };
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const newBytes = readBytesFrom(jsonlPath, offset, fileSize - offset);
|
|
69
|
-
const lines = newBytes.split("\n").filter((l) => l.trim());
|
|
70
|
-
const entries = parseJsonlEntries(lines, options.format);
|
|
71
|
-
const summary = summarizeEntries(entries);
|
|
72
|
-
|
|
73
|
-
currentCursors[name] = { offset: fileSize };
|
|
74
|
-
|
|
75
|
-
if (!summary) continue;
|
|
76
|
-
|
|
77
|
-
const ago = summary.timeSpan.last ? formatTimeAgo(summary.timeSpan.last) : "recently";
|
|
78
|
-
const parts = [`- ${name} (${ago}, ${summary.messageCount} messages)`];
|
|
79
|
-
|
|
80
|
-
if (summary.firstUserText) {
|
|
81
|
-
parts[0] += `: "${truncate(summary.firstUserText, 100)}"`;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const actions: string[] = [];
|
|
85
|
-
if (summary.toolCounts.edits > 0) actions.push(`edited ${summary.toolCounts.edits} files`);
|
|
86
|
-
if (summary.toolCounts.commands > 0)
|
|
87
|
-
actions.push(`ran ${summary.toolCounts.commands} commands`);
|
|
88
|
-
if (summary.toolCounts.reads > 0) actions.push(`read ${summary.toolCounts.reads} files`);
|
|
89
|
-
if (summary.toolCounts.other > 0) actions.push(`${summary.toolCounts.other} other tool uses`);
|
|
90
|
-
if (actions.length > 0) {
|
|
91
|
-
parts[0] += ` -> ${actions.join(", ")}`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
summaries.push(parts[0]);
|
|
95
|
-
} catch {}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
cursors[options.currentSession] = currentCursors;
|
|
99
|
-
try {
|
|
100
|
-
saveCursors(options.cursorFile, cursors);
|
|
101
|
-
} catch {
|
|
102
|
-
// Non-fatal: worst case is duplicate summaries on next check
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (summaries.length === 0) return null;
|
|
106
|
-
|
|
107
|
-
// Cap total output at ~500 chars
|
|
108
|
-
let output = "[Session Activity]\n" + summaries.join("\n");
|
|
109
|
-
if (output.length > 500) {
|
|
110
|
-
output = output.slice(0, 497) + "...";
|
|
111
|
-
}
|
|
112
|
-
return output;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function readSessionLog(options: {
|
|
116
|
-
jsonlPath: string;
|
|
117
|
-
format: Format;
|
|
118
|
-
lines?: number;
|
|
119
|
-
}): string {
|
|
120
|
-
const maxLines = options.lines ?? 50;
|
|
121
|
-
if (!existsSync(options.jsonlPath)) return "No session log found.";
|
|
122
|
-
|
|
123
|
-
const content = readFileSync(options.jsonlPath, "utf-8");
|
|
124
|
-
const allLines = content.split("\n").filter((l) => l.trim());
|
|
125
|
-
const lines = allLines.slice(-maxLines);
|
|
126
|
-
const entries = parseJsonlEntries(lines, options.format);
|
|
127
|
-
|
|
128
|
-
const output: string[] = [];
|
|
129
|
-
for (const entry of entries) {
|
|
130
|
-
const ts = entry.timestamp ? `[${formatTimestamp(entry.timestamp)}]` : "";
|
|
131
|
-
if (entry.role === "user" && entry.text) {
|
|
132
|
-
output.push(`${ts} User: ${entry.text}`);
|
|
133
|
-
} else if (entry.role === "assistant") {
|
|
134
|
-
if (entry.text) {
|
|
135
|
-
output.push(`${ts} Assistant: ${entry.text}`);
|
|
136
|
-
}
|
|
137
|
-
if (entry.toolUses) {
|
|
138
|
-
for (const tool of entry.toolUses) {
|
|
139
|
-
const arg = tool.primaryArg ? ` ${tool.primaryArg}` : "";
|
|
140
|
-
output.push(`${ts} [${tool.name}${arg}]`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return output.length > 0 ? output.join("\n") : "No activity found.";
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// --- JSONL Path Resolvers ---
|
|
150
|
-
|
|
151
|
-
export function resolveAgentSdkJsonl(
|
|
152
|
-
sessionsDir: string,
|
|
153
|
-
sessionName: string,
|
|
154
|
-
cwd: string,
|
|
155
|
-
): string | null {
|
|
156
|
-
const sessionFile = resolve(sessionsDir, `${sessionName}.json`);
|
|
157
|
-
if (!existsSync(sessionFile)) return null;
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
const data = JSON.parse(readFileSync(sessionFile, "utf-8"));
|
|
161
|
-
const sessionId = data.sessionId;
|
|
162
|
-
if (!sessionId) return null;
|
|
163
|
-
|
|
164
|
-
const encoded = encodeCwd(cwd);
|
|
165
|
-
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
166
|
-
return resolve(home, ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
167
|
-
} catch {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export function encodeCwd(cwd: string): string {
|
|
173
|
-
return cwd.replace(/\//g, "-").replace(/\./g, "-");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export function resolvePiJsonl(sessionsDir: string, sessionName: string): string | null {
|
|
177
|
-
const sessionDir = resolve(sessionsDir, sessionName);
|
|
178
|
-
if (!existsSync(sessionDir)) return null;
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
const files = readdirSync(sessionDir)
|
|
182
|
-
.filter((f) => f.endsWith(".jsonl"))
|
|
183
|
-
.map((f) => ({
|
|
184
|
-
name: f,
|
|
185
|
-
mtime: statSync(resolve(sessionDir, f)).mtimeMs,
|
|
186
|
-
}))
|
|
187
|
-
.sort((a, b) => b.mtime - a.mtime);
|
|
188
|
-
|
|
189
|
-
if (files.length === 0) return null;
|
|
190
|
-
return resolve(sessionDir, files[0].name);
|
|
191
|
-
} catch {
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// --- Parsing ---
|
|
197
|
-
|
|
198
|
-
export function parseJsonlEntries(lines: string[], format: Format): ParsedEntry[] {
|
|
199
|
-
const entries: ParsedEntry[] = [];
|
|
200
|
-
|
|
201
|
-
for (const line of lines) {
|
|
202
|
-
let parsed: any;
|
|
203
|
-
try {
|
|
204
|
-
parsed = JSON.parse(line);
|
|
205
|
-
} catch {
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (format === "agent-sdk") {
|
|
210
|
-
if (parsed.type === "user" && parsed.message?.role === "user") {
|
|
211
|
-
const text = extractTextFromContent(parsed.message.content);
|
|
212
|
-
if (text) entries.push({ role: "user", timestamp: parsed.timestamp, text });
|
|
213
|
-
} else if (parsed.type === "assistant" && parsed.message?.role === "assistant") {
|
|
214
|
-
const text = extractTextFromContent(parsed.message.content);
|
|
215
|
-
const toolUses = extractToolUses(parsed.message.content, format);
|
|
216
|
-
if (text || toolUses.length > 0) {
|
|
217
|
-
entries.push({
|
|
218
|
-
role: "assistant",
|
|
219
|
-
timestamp: parsed.timestamp,
|
|
220
|
-
text: text || undefined,
|
|
221
|
-
toolUses,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
} else {
|
|
226
|
-
// pi format
|
|
227
|
-
if (parsed.type === "message" && parsed.message?.role === "user") {
|
|
228
|
-
const text = extractTextFromContent(parsed.message.content);
|
|
229
|
-
if (text) entries.push({ role: "user", timestamp: parsed.timestamp, text });
|
|
230
|
-
} else if (parsed.type === "message" && parsed.message?.role === "assistant") {
|
|
231
|
-
const text = extractTextFromContent(parsed.message.content);
|
|
232
|
-
const toolUses = extractToolUses(parsed.message.content, format);
|
|
233
|
-
if (text || toolUses.length > 0) {
|
|
234
|
-
entries.push({
|
|
235
|
-
role: "assistant",
|
|
236
|
-
timestamp: parsed.timestamp,
|
|
237
|
-
text: text || undefined,
|
|
238
|
-
toolUses,
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return entries;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export function summarizeEntries(entries: ParsedEntry[]): SessionSummary | null {
|
|
249
|
-
if (entries.length === 0) return null;
|
|
250
|
-
|
|
251
|
-
let firstUserText = "";
|
|
252
|
-
let lastAssistantText: string | undefined;
|
|
253
|
-
const toolCounts = { edits: 0, reads: 0, commands: 0, other: 0 };
|
|
254
|
-
let messageCount = 0;
|
|
255
|
-
const timestamps: string[] = [];
|
|
256
|
-
|
|
257
|
-
for (const entry of entries) {
|
|
258
|
-
messageCount++;
|
|
259
|
-
if (entry.timestamp) timestamps.push(entry.timestamp);
|
|
260
|
-
|
|
261
|
-
if (entry.role === "user" && entry.text && !firstUserText) {
|
|
262
|
-
firstUserText = entry.text;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (entry.role === "assistant") {
|
|
266
|
-
if (entry.text) lastAssistantText = entry.text;
|
|
267
|
-
if (entry.toolUses) {
|
|
268
|
-
for (const tool of entry.toolUses) {
|
|
269
|
-
const cat = categorizeTool(tool.name);
|
|
270
|
-
toolCounts[cat]++;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return {
|
|
277
|
-
firstUserText,
|
|
278
|
-
toolCounts,
|
|
279
|
-
messageCount,
|
|
280
|
-
timeSpan: {
|
|
281
|
-
first: timestamps[0],
|
|
282
|
-
last: timestamps[timestamps.length - 1],
|
|
283
|
-
},
|
|
284
|
-
lastAssistantText,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// --- Helpers ---
|
|
289
|
-
|
|
290
|
-
function extractTextFromContent(content: any[]): string | null {
|
|
291
|
-
if (!Array.isArray(content)) return null;
|
|
292
|
-
const texts: string[] = [];
|
|
293
|
-
for (const part of content) {
|
|
294
|
-
if (part.type === "text" && part.text) {
|
|
295
|
-
texts.push(part.text);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return texts.length > 0 ? texts.join("\n") : null;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function extractToolUses(content: any[], format: Format): { name: string; primaryArg?: string }[] {
|
|
302
|
-
if (!Array.isArray(content)) return [];
|
|
303
|
-
const tools: { name: string; primaryArg?: string }[] = [];
|
|
304
|
-
|
|
305
|
-
for (const part of content) {
|
|
306
|
-
const isToolUse = format === "agent-sdk" ? part.type === "tool_use" : part.type === "toolCall";
|
|
307
|
-
|
|
308
|
-
if (isToolUse) {
|
|
309
|
-
const name = part.name || "unknown";
|
|
310
|
-
const input = format === "agent-sdk" ? part.input : part.arguments;
|
|
311
|
-
const primaryArg = extractPrimaryArg(name, input);
|
|
312
|
-
tools.push({ name, primaryArg });
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return tools;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function extractPrimaryArg(_name: string, input: any): string | undefined {
|
|
320
|
-
if (!input || typeof input !== "object") return undefined;
|
|
321
|
-
// Common patterns for primary argument
|
|
322
|
-
return (
|
|
323
|
-
input.file_path || input.path || input.command || input.pattern || input.query || input.url
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function categorizeTool(name: string): "edits" | "reads" | "commands" | "other" {
|
|
328
|
-
const lowerName = name.toLowerCase();
|
|
329
|
-
if (["edit", "write", "notebookedit"].includes(lowerName)) return "edits";
|
|
330
|
-
if (["read", "glob", "grep", "ls"].includes(lowerName)) return "reads";
|
|
331
|
-
if (["bash", "exec", "execute_shell_command"].includes(lowerName)) return "commands";
|
|
332
|
-
return "other";
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function listSessionNames(sessionsDir: string, format: Format): string[] {
|
|
336
|
-
if (!existsSync(sessionsDir)) return [];
|
|
337
|
-
try {
|
|
338
|
-
const entries = readdirSync(sessionsDir);
|
|
339
|
-
if (format === "agent-sdk") {
|
|
340
|
-
return entries.filter((e) => e.endsWith(".json")).map((e) => e.replace(/\.json$/, ""));
|
|
341
|
-
}
|
|
342
|
-
// pi: subdirectories
|
|
343
|
-
return entries.filter((e) => {
|
|
344
|
-
try {
|
|
345
|
-
return statSync(resolve(sessionsDir, e)).isDirectory();
|
|
346
|
-
} catch {
|
|
347
|
-
return false;
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
} catch {
|
|
351
|
-
return [];
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function loadCursors(cursorFile: string): CursorState {
|
|
356
|
-
try {
|
|
357
|
-
return JSON.parse(readFileSync(cursorFile, "utf-8"));
|
|
358
|
-
} catch {
|
|
359
|
-
return {};
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function saveCursors(cursorFile: string, cursors: CursorState): void {
|
|
364
|
-
mkdirSync(dirname(cursorFile), { recursive: true });
|
|
365
|
-
writeFileSync(cursorFile, JSON.stringify(cursors, null, 2));
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function readBytesFrom(filePath: string, offset: number, length: number): string {
|
|
369
|
-
const buf = Buffer.alloc(length);
|
|
370
|
-
const fd = openSync(filePath, "r");
|
|
371
|
-
try {
|
|
372
|
-
readSync(fd, buf, 0, length, offset);
|
|
373
|
-
} finally {
|
|
374
|
-
closeSync(fd);
|
|
375
|
-
}
|
|
376
|
-
return buf.toString("utf-8");
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function truncate(s: string, max: number): string {
|
|
380
|
-
if (s.length <= max) return s;
|
|
381
|
-
return s.slice(0, max - 3) + "...";
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function formatTimeAgo(timestamp: string): string {
|
|
385
|
-
const diff = Date.now() - new Date(timestamp).getTime();
|
|
386
|
-
if (isNaN(diff) || diff < 0) return "just now";
|
|
387
|
-
const minutes = Math.floor(diff / 60000);
|
|
388
|
-
if (minutes < 1) return "just now";
|
|
389
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
390
|
-
const hours = Math.floor(minutes / 60);
|
|
391
|
-
if (hours < 24) return `${hours}h ago`;
|
|
392
|
-
const days = Math.floor(hours / 24);
|
|
393
|
-
return `${days}d ago`;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function formatTimestamp(timestamp: string): string {
|
|
397
|
-
const d = new Date(timestamp);
|
|
398
|
-
if (isNaN(d.getTime())) return timestamp;
|
|
399
|
-
return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
|
|
400
|
-
}
|