volute 0.35.0 → 0.36.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/dist/{activity-events-ZW4SDL2C.js → activity-events-PWOGSMRL.js} +4 -4
- package/dist/{ai-service-LURBEDDB.js → ai-service-GSZWIETO.js} +5 -5
- package/dist/{archive-ESU2FUN4.js → archive-Y2YEOCGB.js} +3 -3
- package/dist/{auth-WX4TESEI.js → auth-YTQME4EV.js} +5 -5
- package/dist/{chat-QXAJF3FU.js → chat-ED7YOGKO.js} +4 -4
- package/dist/{chunk-AOB6GVRM.js → chunk-46DYYHN6.js} +8 -3
- package/dist/{chunk-5XJYUFZH.js → chunk-6F3YNULE.js} +68 -22
- package/dist/{chunk-BDYXIWA5.js → chunk-75AJ54GM.js} +13 -2
- package/dist/{chunk-5N7Y5WAM.js → chunk-7PTQGPJY.js} +28 -12
- package/dist/{chunk-CORXD635.js → chunk-B35VNNSS.js} +3 -3
- package/dist/{chunk-PWQ2ITYG.js → chunk-BOLJUV77.js} +4 -4
- package/dist/{chunk-ZSR72JB3.js → chunk-CU6OFXMM.js} +1 -1
- package/dist/{chunk-2TGZJFAT.js → chunk-DJT5Y4UF.js} +3 -3
- package/dist/{chunk-XRQSAMX2.js → chunk-DMV5P2LU.js} +3 -3
- package/dist/{chunk-WJPROOU5.js → chunk-DQ7VBXAP.js} +635 -3692
- package/dist/{chunk-IJHIXLVN.js → chunk-GBDVNPN2.js} +13 -11
- package/dist/{chunk-WZRZFFCL.js → chunk-IIWF2IPD.js} +146 -186
- package/dist/{chunk-F7ZNLYKZ.js → chunk-KAB6UGOL.js} +2 -2
- package/dist/{chunk-VHJRZM2S.js → chunk-L72WYMF7.js} +2 -2
- package/dist/{chunk-NJK5SDGR.js → chunk-LGNUFVMR.js} +1 -1
- package/dist/{chunk-FT5KETXZ.js → chunk-M5RYAA5I.js} +2 -2
- package/dist/{chunk-J6CJQDWI.js → chunk-N2AUHW4C.js} +2 -2
- package/dist/{chunk-BMZQYACC.js → chunk-NUX47Y2V.js} +19 -4
- package/dist/{chunk-AN2W47GW.js → chunk-PJ4IPTIN.js} +2 -2
- package/dist/{chunk-N446KRP7.js → chunk-PY557GDR.js} +2 -2
- package/dist/{chunk-MDJGMOSD.js → chunk-PZYJBOQP.js} +6 -6
- package/dist/chunk-RG5TOL4O.js +18 -0
- package/dist/{chunk-N5LMGYXX.js → chunk-SWW6AUVW.js} +2 -2
- package/dist/{chunk-BKF4WQCY.js → chunk-T2TP6ZC6.js} +20 -8
- package/dist/{chunk-VY3RB2V7.js → chunk-TWAN7ZNO.js} +3 -3
- package/dist/{chunk-QCH6K235.js → chunk-UI7RPV2B.js} +1 -1
- package/dist/{chunk-QWTR6AWZ.js → chunk-X2J7QUFH.js} +2 -2
- package/dist/{chunk-A2ZLHBHG.js → chunk-YDBAY3NA.js} +2 -2
- package/dist/{chunk-BV65KRHM.js → chunk-YTWZORJN.js} +2 -2
- package/dist/{chunk-OTC67N2Z.js → chunk-ZTVKQOU7.js} +1 -1
- package/dist/cli.js +16 -16
- package/dist/{cloud-sync-6JL4C24T.js → cloud-sync-BOCZSDIA.js} +19 -20
- package/dist/connectors/discord-bridge.js +3 -3
- package/dist/connectors/slack-bridge.js +3 -3
- package/dist/connectors/telegram-bridge.js +3 -3
- package/dist/{conversations-2PW57WO2.js → conversations-HH3CJD4E.js} +15 -9
- package/dist/{create-UVCK2CS6.js → create-QBEPSD2Z.js} +1 -1
- package/dist/{daemon-restart-HSZ3BCX5.js → daemon-restart-SIR3UR4B.js} +10 -10
- package/dist/daemon.js +446 -328
- package/dist/{db-BVBJ57TU.js → db-URORGSXQ.js} +2 -2
- package/dist/delivery-manager-WTGIPBGY.js +30 -0
- package/dist/{delivery-router-HEJSJAHQ.js → delivery-router-VSULHXNH.js} +4 -4
- package/dist/down-DGGLZ5TA.js +17 -0
- package/dist/{exec-PY7THYH4.js → exec-X3C6ZZTQ.js} +4 -4
- package/dist/{export-OAS6QVBN.js → export-HTFOHOKL.js} +3 -3
- package/dist/{extension-D74CNM7G.js → extension-AKZ46YSL.js} +22 -3
- package/dist/{extensions-XDDFY72A.js → extensions-OOSFVH7U.js} +21 -20
- package/dist/{files-CWTK6V3H.js → files-H2YLRD37.js} +3 -3
- package/dist/{import-5A3T7QV4.js → import-OL5BZX7S.js} +6 -6
- package/dist/{isolation-TK5RX2WM.js → isolation-N74RWOUX.js} +3 -3
- package/dist/{list-PDMQM7ZV.js → list-GJ4RUQQT.js} +7 -1
- package/dist/{login-7TE6CIZF.js → login-JXRVMBRB.js} +2 -2
- package/dist/{logout-T4XS6LRU.js → logout-FW243JBU.js} +2 -2
- package/dist/message-delivery-YORUXKDQ.js +40 -0
- package/dist/{mind-5IEYKV7I.js → mind-6VJJHF65.js} +6 -6
- package/dist/{mind-activity-tracker-QBLIV7ZJ.js → mind-activity-tracker-66UVYIFW.js} +5 -5
- package/dist/{mind-history-IE2QH7U5.js → mind-history-MII2SK7F.js} +81 -14
- package/dist/mind-manager-TJ2SUPRX.js +30 -0
- package/dist/mind-service-E7FM2WZF.js +36 -0
- package/dist/{package-D2FSVFAX.js → package-3W2MEXHB.js} +5 -5
- package/dist/{read-67VRP2DO.js → read-ZUDG4JWU.js} +4 -4
- package/dist/{registry-GBSNW3HG.js → registry-YPHK534W.js} +2 -2
- package/dist/{sandbox-R37VIU36.js → sandbox-LP6YRAXS.js} +5 -5
- package/dist/scheduler-FRJ5DK24.js +30 -0
- package/dist/{schema-XVZ2CLKW.js → schema-MISD3JFG.js} +3 -1
- package/dist/{seed-EQORWX77.js → seed-CEC4RC23.js} +1 -1
- package/dist/{seed-cmd-ZM2XGVU2.js → seed-cmd-WTTG7SRQ.js} +2 -2
- package/dist/{seed-create-DRWGGHEI.js → seed-create-M6RCC6RP.js} +3 -3
- package/dist/{seed-sprout-JYXGXOP3.js → seed-sprout-ZKCHFJKH.js} +10 -10
- package/dist/{send-JBJJQ7CA.js → send-LXUT2GGR.js} +3 -3
- package/dist/{service-WNPCNHOX.js → service-M6N3RUYU.js} +5 -5
- package/dist/{setup-BJ4YAY26.js → setup-PJOF5UV5.js} +7 -7
- package/dist/{setup-RHJRFURI.js → setup-PMJHCZQX.js} +5 -3
- package/dist/skills/tending/SKILL.md +52 -0
- package/dist/{skills-EKMCQ46K.js → skills-2PTRTBQP.js} +7 -7
- package/dist/sleep-manager-WAZWMFJT.js +34 -0
- package/dist/spirit-6KVDIROQ.js +24 -0
- package/dist/{sprout-HE4TITMK.js → sprout-WX2FFYLP.js} +1 -1
- package/dist/src-FQE4BHRG.js +617 -0
- package/dist/src-GW6FP6VL.js +425 -0
- package/dist/src-QEOLMAYC.js +2133 -0
- package/dist/{status-ZK34WYIM.js → status-3IVSLJDN.js} +6 -6
- package/dist/system-chat-2IFS5HCX.js +34 -0
- package/dist/{tailscale-ZIZ2HWJ5.js → tailscale-DZU4WM3E.js} +3 -3
- package/dist/{template-hash-A7FNHTB7.js → template-hash-6ITI3WC4.js} +2 -2
- package/dist/up-4SCIUIMG.js +19 -0
- package/dist/{update-ANE5ZM7F.js → update-RIQYUPVN.js} +6 -6
- package/dist/{update-check-UV55CBEP.js → update-check-4TIJKVGD.js} +3 -3
- package/dist/{version-notify-FXSEMXWW.js → version-notify-UXSHBZ35.js} +21 -22
- package/dist/{volute-config-D2XVS2YI.js → volute-config-V7UFFBG3.js} +1 -1
- package/dist/web-assets/assets/index-C-eYso8Y.js +75 -0
- package/dist/web-assets/assets/index-CCv_fSte.css +1 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0006_channels.sql +17 -0
- package/drizzle/0007_drop_conversation_name_title.sql +11 -0
- package/drizzle/0008_performance_indexes.sql +6 -0
- package/drizzle/meta/0006_snapshot.json +7 -0
- package/drizzle/meta/0007_snapshot.json +7 -0
- package/drizzle/meta/_journal.json +21 -0
- package/package.json +5 -5
- package/templates/_base/home/.config/routes.json +2 -2
- package/templates/_base/home/VOLUTE.md +1 -2
- package/templates/_base/src/lib/format-prefix.ts +1 -7
- package/templates/claude/.init/.config/routes.json +2 -2
- package/templates/codex/.init/.config/routes.json +2 -2
- package/templates/pi/.init/.config/routes.json +2 -2
- package/dist/delivery-manager-H5ZVBMCQ.js +0 -31
- package/dist/down-74VXM45A.js +0 -17
- package/dist/message-delivery-GRC4W6P7.js +0 -41
- package/dist/mind-manager-HFLB5653.js +0 -31
- package/dist/mind-service-X2CAA6W6.js +0 -37
- package/dist/scheduler-Y7O4CJXL.js +0 -31
- package/dist/sleep-manager-7KFK3USC.js +0 -35
- package/dist/spirit-ZFRDXMG7.js +0 -23
- package/dist/system-chat-IDPHYHY4.js +0 -35
- package/dist/up-77ICEDEW.js +0 -19
- package/dist/web-assets/assets/index-BhxWKvbB.css +0 -1
- package/dist/web-assets/assets/index-CHVKJ9II.js +0 -75
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createExtension
|
|
4
|
+
} from "./chunk-RG5TOL4O.js";
|
|
5
|
+
import "./chunk-7KJOFUNN.js";
|
|
6
|
+
|
|
7
|
+
// packages/extensions/notes/src/index.ts
|
|
8
|
+
import { resolve } from "path";
|
|
9
|
+
|
|
10
|
+
// packages/extensions/notes/src/notes.ts
|
|
11
|
+
function slugify(text) {
|
|
12
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
13
|
+
}
|
|
14
|
+
async function createNote(db, getUser, authorId, title, content, replyToId) {
|
|
15
|
+
let slug = slugify(title) || "untitled";
|
|
16
|
+
const existing = db.prepare("SELECT slug FROM notes WHERE author_id = ?").all(authorId);
|
|
17
|
+
const existingSlugs = new Set(existing.map((r) => r.slug));
|
|
18
|
+
if (existingSlugs.has(slug)) {
|
|
19
|
+
let i = 2;
|
|
20
|
+
while (existingSlugs.has(`${slug}-${i}`)) i++;
|
|
21
|
+
slug = `${slug}-${i}`;
|
|
22
|
+
}
|
|
23
|
+
const row = db.prepare(
|
|
24
|
+
`INSERT INTO notes (author_id, title, slug, content, reply_to_id)
|
|
25
|
+
VALUES (?, ?, ?, ?, ?)
|
|
26
|
+
RETURNING *`
|
|
27
|
+
).get(authorId, title, slug, content, replyToId ?? null);
|
|
28
|
+
const author = await getUser(authorId);
|
|
29
|
+
return {
|
|
30
|
+
...row,
|
|
31
|
+
author_username: author?.username ?? "unknown",
|
|
32
|
+
author_display_name: author?.display_name ?? null,
|
|
33
|
+
comment_count: 0
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async function getNote(db, getUser, getUserByUsername, authorUsername, slug) {
|
|
37
|
+
const author = await getUserByUsername(authorUsername);
|
|
38
|
+
if (!author) return null;
|
|
39
|
+
const row = db.prepare("SELECT * FROM notes WHERE author_id = ? AND slug = ?").get(author.id, slug);
|
|
40
|
+
if (!row) return null;
|
|
41
|
+
const comments = await getComments(db, getUser, row.id);
|
|
42
|
+
const reactions = await getReactions(db, getUser, row.id);
|
|
43
|
+
let reply_to = null;
|
|
44
|
+
if (row.reply_to_id) {
|
|
45
|
+
const parent = db.prepare("SELECT * FROM notes WHERE id = ?").get(row.reply_to_id);
|
|
46
|
+
if (parent) {
|
|
47
|
+
const parentAuthor = await getUser(parent.author_id);
|
|
48
|
+
reply_to = {
|
|
49
|
+
author_username: parentAuthor?.username ?? "unknown",
|
|
50
|
+
slug: parent.slug,
|
|
51
|
+
title: parent.title
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const replyRows = db.prepare("SELECT * FROM notes WHERE reply_to_id = ? ORDER BY created_at").all(row.id);
|
|
56
|
+
const replies = [];
|
|
57
|
+
for (const r of replyRows) {
|
|
58
|
+
const replyAuthor = await getUser(r.author_id);
|
|
59
|
+
replies.push({
|
|
60
|
+
author_username: replyAuthor?.username ?? "unknown",
|
|
61
|
+
slug: r.slug,
|
|
62
|
+
title: r.title,
|
|
63
|
+
created_at: r.created_at
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
...row,
|
|
68
|
+
author_username: authorUsername,
|
|
69
|
+
author_display_name: author.display_name ?? null,
|
|
70
|
+
comment_count: comments.length,
|
|
71
|
+
comments,
|
|
72
|
+
reactions,
|
|
73
|
+
reply_to,
|
|
74
|
+
replies
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function listNotes(db, getUser, getUserByUsername, opts) {
|
|
78
|
+
const limit = opts?.limit ?? 50;
|
|
79
|
+
const offset = opts?.offset ?? 0;
|
|
80
|
+
let authorId;
|
|
81
|
+
if (opts?.authorUsername) {
|
|
82
|
+
const author = await getUserByUsername(opts.authorUsername);
|
|
83
|
+
if (!author) return [];
|
|
84
|
+
authorId = author.id;
|
|
85
|
+
}
|
|
86
|
+
const rows = authorId ? db.prepare(
|
|
87
|
+
"SELECT * FROM notes WHERE author_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?"
|
|
88
|
+
).all(authorId, limit, offset) : db.prepare("SELECT * FROM notes ORDER BY created_at DESC LIMIT ? OFFSET ?").all(limit, offset);
|
|
89
|
+
if (rows.length === 0) return [];
|
|
90
|
+
const noteIds = rows.map((r) => r.id);
|
|
91
|
+
const commentCounts = db.prepare(
|
|
92
|
+
`SELECT note_id, COUNT(*) as count FROM note_comments
|
|
93
|
+
WHERE note_id IN (${noteIds.map(() => "?").join(",")})
|
|
94
|
+
GROUP BY note_id`
|
|
95
|
+
).all(...noteIds);
|
|
96
|
+
const countMap = new Map(commentCounts.map((r) => [r.note_id, r.count]));
|
|
97
|
+
const allReactions = db.prepare(
|
|
98
|
+
`SELECT note_id, emoji, COUNT(*) as count FROM note_reactions
|
|
99
|
+
WHERE note_id IN (${noteIds.map(() => "?").join(",")})
|
|
100
|
+
GROUP BY note_id, emoji`
|
|
101
|
+
).all(...noteIds);
|
|
102
|
+
const reactionMap = /* @__PURE__ */ new Map();
|
|
103
|
+
for (const r of allReactions) {
|
|
104
|
+
if (!reactionMap.has(r.note_id)) reactionMap.set(r.note_id, []);
|
|
105
|
+
reactionMap.get(r.note_id).push({ emoji: r.emoji, count: r.count });
|
|
106
|
+
}
|
|
107
|
+
const replyToIds = [
|
|
108
|
+
...new Set(rows.filter((r) => r.reply_to_id).map((r) => r.reply_to_id))
|
|
109
|
+
];
|
|
110
|
+
const replyToMap = /* @__PURE__ */ new Map();
|
|
111
|
+
if (replyToIds.length > 0) {
|
|
112
|
+
const parents = db.prepare(`SELECT * FROM notes WHERE id IN (${replyToIds.map(() => "?").join(",")})`).all(...replyToIds);
|
|
113
|
+
for (const parent of parents) {
|
|
114
|
+
const parentAuthor = await getUser(parent.author_id);
|
|
115
|
+
replyToMap.set(parent.id, {
|
|
116
|
+
author_username: parentAuthor?.username ?? "unknown",
|
|
117
|
+
slug: parent.slug,
|
|
118
|
+
title: parent.title
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const authorCache = /* @__PURE__ */ new Map();
|
|
123
|
+
const result = [];
|
|
124
|
+
for (const r of rows) {
|
|
125
|
+
if (!authorCache.has(r.author_id)) {
|
|
126
|
+
const u = await getUser(r.author_id);
|
|
127
|
+
authorCache.set(r.author_id, {
|
|
128
|
+
username: u?.username ?? "unknown",
|
|
129
|
+
display_name: u?.display_name ?? null
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const authorInfo = authorCache.get(r.author_id);
|
|
133
|
+
const reactions = reactionMap.get(r.id);
|
|
134
|
+
const topReactions = reactions ? reactions.sort((a, b) => b.count - a.count).slice(0, 3).map((rx) => ({ ...rx, usernames: [] })) : void 0;
|
|
135
|
+
result.push({
|
|
136
|
+
...r,
|
|
137
|
+
author_username: authorInfo.username,
|
|
138
|
+
author_display_name: authorInfo.display_name,
|
|
139
|
+
comment_count: countMap.get(r.id) ?? 0,
|
|
140
|
+
reactions: topReactions,
|
|
141
|
+
reply_to: r.reply_to_id ? replyToMap.get(r.reply_to_id) ?? null : null
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
async function updateNote(db, getUser, getUserByUsername, authorUsername, slug, updates) {
|
|
147
|
+
const author = await getUserByUsername(authorUsername);
|
|
148
|
+
if (!author) return null;
|
|
149
|
+
const existing = db.prepare("SELECT id FROM notes WHERE author_id = ? AND slug = ?").get(author.id, slug);
|
|
150
|
+
if (!existing) return null;
|
|
151
|
+
const sets = ["updated_at = datetime('now')"];
|
|
152
|
+
const params = [];
|
|
153
|
+
if (updates.title !== void 0) {
|
|
154
|
+
sets.push("title = ?");
|
|
155
|
+
params.push(updates.title);
|
|
156
|
+
}
|
|
157
|
+
if (updates.content !== void 0) {
|
|
158
|
+
sets.push("content = ?");
|
|
159
|
+
params.push(updates.content);
|
|
160
|
+
}
|
|
161
|
+
params.push(existing.id);
|
|
162
|
+
db.prepare(`UPDATE notes SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
163
|
+
const full = await getNote(db, getUser, getUserByUsername, authorUsername, slug);
|
|
164
|
+
if (!full) return null;
|
|
165
|
+
const { comments, replies, ...note } = full;
|
|
166
|
+
return note;
|
|
167
|
+
}
|
|
168
|
+
async function deleteNote(db, getUserByUsername, authorUsername, slug, authorId) {
|
|
169
|
+
const author = await getUserByUsername(authorUsername);
|
|
170
|
+
if (!author) return false;
|
|
171
|
+
const existing = db.prepare("SELECT id, author_id FROM notes WHERE author_id = ? AND slug = ?").get(author.id, slug);
|
|
172
|
+
if (!existing || existing.author_id !== authorId) return false;
|
|
173
|
+
db.prepare("DELETE FROM notes WHERE id = ?").run(existing.id);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
async function addComment(db, getUser, noteId, authorId, content) {
|
|
177
|
+
const row = db.prepare(`INSERT INTO note_comments (note_id, author_id, content) VALUES (?, ?, ?) RETURNING *`).get(noteId, authorId, content);
|
|
178
|
+
const author = await getUser(authorId);
|
|
179
|
+
return {
|
|
180
|
+
...row,
|
|
181
|
+
author_username: author?.username ?? "unknown",
|
|
182
|
+
author_display_name: author?.display_name ?? null
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async function getComments(db, getUser, noteId) {
|
|
186
|
+
const rows = db.prepare("SELECT * FROM note_comments WHERE note_id = ? ORDER BY created_at").all(noteId);
|
|
187
|
+
const result = [];
|
|
188
|
+
for (const row of rows) {
|
|
189
|
+
const author = await getUser(row.author_id);
|
|
190
|
+
result.push({
|
|
191
|
+
...row,
|
|
192
|
+
author_username: author?.username ?? "unknown",
|
|
193
|
+
author_display_name: author?.display_name ?? null
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
async function deleteComment(db, commentId, authorId) {
|
|
199
|
+
const existing = db.prepare("SELECT id, author_id FROM note_comments WHERE id = ?").get(commentId);
|
|
200
|
+
if (!existing || existing.author_id !== authorId) return false;
|
|
201
|
+
db.prepare("DELETE FROM note_comments WHERE id = ?").run(existing.id);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
function toggleReaction(db, noteId, userId, emoji) {
|
|
205
|
+
const existing = db.prepare("SELECT id FROM note_reactions WHERE note_id = ? AND user_id = ? AND emoji = ?").get(noteId, userId, emoji);
|
|
206
|
+
if (existing) {
|
|
207
|
+
db.prepare("DELETE FROM note_reactions WHERE id = ?").run(existing.id);
|
|
208
|
+
return { added: false };
|
|
209
|
+
}
|
|
210
|
+
db.prepare("INSERT INTO note_reactions (note_id, user_id, emoji) VALUES (?, ?, ?)").run(
|
|
211
|
+
noteId,
|
|
212
|
+
userId,
|
|
213
|
+
emoji
|
|
214
|
+
);
|
|
215
|
+
return { added: true };
|
|
216
|
+
}
|
|
217
|
+
async function getReactions(db, getUser, noteId) {
|
|
218
|
+
const rows = db.prepare("SELECT * FROM note_reactions WHERE note_id = ? ORDER BY emoji").all(noteId);
|
|
219
|
+
const userCache = /* @__PURE__ */ new Map();
|
|
220
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
221
|
+
for (const r of rows) {
|
|
222
|
+
if (!grouped.has(r.emoji)) grouped.set(r.emoji, []);
|
|
223
|
+
grouped.get(r.emoji).push(r.user_id);
|
|
224
|
+
}
|
|
225
|
+
const result = [];
|
|
226
|
+
for (const [emoji, userIds] of grouped) {
|
|
227
|
+
const usernames = [];
|
|
228
|
+
for (const uid of userIds) {
|
|
229
|
+
if (!userCache.has(uid)) {
|
|
230
|
+
const u = await getUser(uid);
|
|
231
|
+
userCache.set(uid, u?.username ?? "unknown");
|
|
232
|
+
}
|
|
233
|
+
usernames.push(userCache.get(uid));
|
|
234
|
+
}
|
|
235
|
+
result.push({ emoji, count: userIds.length, usernames });
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
async function resolveNoteId(db, getUserByUsername, authorSlug) {
|
|
240
|
+
const [authorName, slug] = authorSlug.split("/", 2);
|
|
241
|
+
if (!authorName || !slug) return null;
|
|
242
|
+
const author = await getUserByUsername(authorName);
|
|
243
|
+
if (!author) return null;
|
|
244
|
+
const row = db.prepare("SELECT id FROM notes WHERE author_id = ? AND slug = ?").get(author.id, slug);
|
|
245
|
+
return row?.id ?? null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// packages/extensions/notes/src/commands.ts
|
|
249
|
+
function createCommands() {
|
|
250
|
+
return {
|
|
251
|
+
write: {
|
|
252
|
+
description: "Write a new note",
|
|
253
|
+
args: [
|
|
254
|
+
{ name: "title", required: true, description: "Note title" },
|
|
255
|
+
{ name: "content", description: "Note content (or pipe via stdin)" }
|
|
256
|
+
],
|
|
257
|
+
flags: {
|
|
258
|
+
"reply-to": { type: "string", description: "Reply to a note (author/slug)" }
|
|
259
|
+
},
|
|
260
|
+
examples: [
|
|
261
|
+
'volute notes write "My Title" "Content here"',
|
|
262
|
+
'echo "piped content" | volute notes write "Title"',
|
|
263
|
+
'volute notes write "Reply" "content" --reply-to author/slug'
|
|
264
|
+
],
|
|
265
|
+
handler: async ({ args, flags }, ctx) => {
|
|
266
|
+
if (!ctx.db) return { error: "Notes extension requires a database" };
|
|
267
|
+
const mindName = ctx.mindName;
|
|
268
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
269
|
+
const user = await ctx.getUserByUsername(mindName);
|
|
270
|
+
if (!user) return { error: `Unknown mind: ${mindName}` };
|
|
271
|
+
const title = args.title;
|
|
272
|
+
const content = args.content ?? ctx.stdin;
|
|
273
|
+
if (!title || !content)
|
|
274
|
+
return { error: 'Usage: volute notes write "title" "content" [--reply-to author/slug]' };
|
|
275
|
+
let replyToId;
|
|
276
|
+
const replyTo = flags["reply-to"];
|
|
277
|
+
if (replyTo) {
|
|
278
|
+
const id = await resolveNoteId(ctx.db, ctx.getUserByUsername, replyTo);
|
|
279
|
+
if (id === null) return { error: `Reply target not found: ${replyTo}` };
|
|
280
|
+
replyToId = id;
|
|
281
|
+
}
|
|
282
|
+
const note = await createNote(ctx.db, ctx.getUser, user.id, title, content, replyToId);
|
|
283
|
+
ctx.publishActivity({
|
|
284
|
+
type: "note_created",
|
|
285
|
+
mind: user.username,
|
|
286
|
+
summary: `${user.username} wrote "${title}"`,
|
|
287
|
+
metadata: { author: user.username, slug: note.slug, bodyHtml: content.slice(0, 500) }
|
|
288
|
+
});
|
|
289
|
+
return { output: `Published: ${note.author_username}/${note.slug}` };
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
list: {
|
|
293
|
+
description: "List notes",
|
|
294
|
+
flags: {
|
|
295
|
+
author: { type: "string", description: "Filter by author name" },
|
|
296
|
+
limit: { type: "number", description: "Max number of notes to show (default: 10)" }
|
|
297
|
+
},
|
|
298
|
+
handler: async ({ flags }, ctx) => {
|
|
299
|
+
if (!ctx.db) return { error: "Notes extension requires a database" };
|
|
300
|
+
const author = flags.author;
|
|
301
|
+
const limit = flags.limit ?? 10;
|
|
302
|
+
const notes = await listNotes(ctx.db, ctx.getUser, ctx.getUserByUsername, {
|
|
303
|
+
authorUsername: author,
|
|
304
|
+
limit
|
|
305
|
+
});
|
|
306
|
+
if (notes.length === 0) return { output: "No notes found." };
|
|
307
|
+
const lines = notes.map((n) => {
|
|
308
|
+
const date = new Date(n.created_at).toLocaleDateString();
|
|
309
|
+
return ` ${n.author_username}/${n.slug} "${n.title}" (${date})`;
|
|
310
|
+
});
|
|
311
|
+
return { output: lines.join("\n") };
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
read: {
|
|
315
|
+
description: "Read a note",
|
|
316
|
+
args: [{ name: "ref", required: true, description: "Note reference (author/slug)" }],
|
|
317
|
+
handler: async ({ args }, ctx) => {
|
|
318
|
+
if (!ctx.db) return { error: "Notes extension requires a database" };
|
|
319
|
+
const ref = args.ref;
|
|
320
|
+
if (!ref || !ref.includes("/")) return { error: "Usage: volute notes read <author/slug>" };
|
|
321
|
+
const [author, slug] = ref.split("/", 2);
|
|
322
|
+
const note = await getNote(ctx.db, ctx.getUser, ctx.getUserByUsername, author, slug);
|
|
323
|
+
if (!note) return { error: "Note not found" };
|
|
324
|
+
const lines = [
|
|
325
|
+
`# ${note.title}
|
|
326
|
+
`,
|
|
327
|
+
`By ${note.author_username} \u2014 ${new Date(note.created_at).toLocaleString()}
|
|
328
|
+
`,
|
|
329
|
+
note.content
|
|
330
|
+
];
|
|
331
|
+
if (note.reactions?.length) {
|
|
332
|
+
lines.push(
|
|
333
|
+
`
|
|
334
|
+
Reactions: ${note.reactions.map((r) => `${r.emoji} (${r.count})`).join(" ")}`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
if (note.comments?.length) {
|
|
338
|
+
lines.push(`
|
|
339
|
+
Comments (${note.comments.length}):`);
|
|
340
|
+
for (const c of note.comments) {
|
|
341
|
+
lines.push(` ${c.author_username}: ${c.content}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return { output: lines.join("\n") };
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
comment: {
|
|
348
|
+
description: "Comment on a note",
|
|
349
|
+
args: [
|
|
350
|
+
{ name: "ref", required: true, description: "Note reference (author/slug)" },
|
|
351
|
+
{ name: "content", description: "Comment text (or pipe via stdin)" }
|
|
352
|
+
],
|
|
353
|
+
handler: async ({ args }, ctx) => {
|
|
354
|
+
if (!ctx.db) return { error: "Notes extension requires a database" };
|
|
355
|
+
const mindName = ctx.mindName;
|
|
356
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
357
|
+
const user = await ctx.getUserByUsername(mindName);
|
|
358
|
+
if (!user) return { error: `Unknown mind: ${mindName}` };
|
|
359
|
+
const ref = args.ref;
|
|
360
|
+
const content = args.content ?? ctx.stdin;
|
|
361
|
+
if (!ref || !ref.includes("/") || !content) {
|
|
362
|
+
return { error: 'Usage: volute notes comment <author/slug> "content"' };
|
|
363
|
+
}
|
|
364
|
+
const [author, slug] = ref.split("/", 2);
|
|
365
|
+
const note = await getNote(ctx.db, ctx.getUser, ctx.getUserByUsername, author, slug);
|
|
366
|
+
if (!note) return { error: "Note not found" };
|
|
367
|
+
await addComment(ctx.db, ctx.getUser, note.id, user.id, content);
|
|
368
|
+
return { output: "Comment added." };
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
react: {
|
|
372
|
+
description: "React to a note",
|
|
373
|
+
args: [
|
|
374
|
+
{ name: "ref", required: true, description: "Note reference (author/slug)" },
|
|
375
|
+
{ name: "emoji", required: true, description: "Emoji to react with" }
|
|
376
|
+
],
|
|
377
|
+
handler: async ({ args }, ctx) => {
|
|
378
|
+
if (!ctx.db) return { error: "Notes extension requires a database" };
|
|
379
|
+
const mindName = ctx.mindName;
|
|
380
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
381
|
+
const user = await ctx.getUserByUsername(mindName);
|
|
382
|
+
if (!user) return { error: `Unknown mind: ${mindName}` };
|
|
383
|
+
const ref = args.ref;
|
|
384
|
+
const emoji = args.emoji;
|
|
385
|
+
if (!ref || !ref.includes("/") || !emoji) {
|
|
386
|
+
return { error: 'Usage: volute notes react <author/slug> "emoji"' };
|
|
387
|
+
}
|
|
388
|
+
const [author, slug] = ref.split("/", 2);
|
|
389
|
+
const note = await getNote(ctx.db, ctx.getUser, ctx.getUserByUsername, author, slug);
|
|
390
|
+
if (!note) return { error: "Note not found" };
|
|
391
|
+
const result = toggleReaction(ctx.db, note.id, user.id, emoji);
|
|
392
|
+
return { output: result.added ? "Reaction added." : "Reaction removed." };
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
delete: {
|
|
396
|
+
description: "Delete your own note",
|
|
397
|
+
args: [{ name: "ref", required: true, description: "Note reference (author/slug)" }],
|
|
398
|
+
handler: async ({ args }, ctx) => {
|
|
399
|
+
if (!ctx.db) return { error: "Notes extension requires a database" };
|
|
400
|
+
const mindName = ctx.mindName;
|
|
401
|
+
if (!mindName) return { error: "No mind specified (use --mind or VOLUTE_MIND)" };
|
|
402
|
+
const user = await ctx.getUserByUsername(mindName);
|
|
403
|
+
if (!user) return { error: `Unknown mind: ${mindName}` };
|
|
404
|
+
const ref = args.ref;
|
|
405
|
+
if (!ref || !ref.includes("/"))
|
|
406
|
+
return { error: "Usage: volute notes delete <author/slug>" };
|
|
407
|
+
const [author, slug] = ref.split("/", 2);
|
|
408
|
+
const deleted = await deleteNote(ctx.db, ctx.getUserByUsername, author, slug, user.id);
|
|
409
|
+
if (!deleted) return { error: "Note not found or not authorized" };
|
|
410
|
+
return { output: "Note deleted." };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// packages/extensions/notes/src/db.ts
|
|
417
|
+
function initDb(db) {
|
|
418
|
+
db.exec(`
|
|
419
|
+
CREATE TABLE IF NOT EXISTS notes (
|
|
420
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
421
|
+
author_id INTEGER NOT NULL,
|
|
422
|
+
title TEXT NOT NULL,
|
|
423
|
+
slug TEXT NOT NULL,
|
|
424
|
+
content TEXT NOT NULL,
|
|
425
|
+
reply_to_id INTEGER,
|
|
426
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
427
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
428
|
+
);
|
|
429
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_notes_author_slug ON notes(author_id, slug);
|
|
430
|
+
CREATE INDEX IF NOT EXISTS idx_notes_created_at ON notes(created_at);
|
|
431
|
+
CREATE INDEX IF NOT EXISTS idx_notes_reply_to ON notes(reply_to_id);
|
|
432
|
+
|
|
433
|
+
CREATE TABLE IF NOT EXISTS note_comments (
|
|
434
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
435
|
+
note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
|
|
436
|
+
author_id INTEGER NOT NULL,
|
|
437
|
+
content TEXT NOT NULL,
|
|
438
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
439
|
+
);
|
|
440
|
+
CREATE INDEX IF NOT EXISTS idx_note_comments_note_id ON note_comments(note_id);
|
|
441
|
+
|
|
442
|
+
CREATE TABLE IF NOT EXISTS note_reactions (
|
|
443
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
444
|
+
note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
|
|
445
|
+
user_id INTEGER NOT NULL,
|
|
446
|
+
emoji TEXT NOT NULL,
|
|
447
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
448
|
+
);
|
|
449
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_note_reactions_unique ON note_reactions(note_id, user_id, emoji);
|
|
450
|
+
CREATE INDEX IF NOT EXISTS idx_note_reactions_note_id ON note_reactions(note_id);
|
|
451
|
+
`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// packages/extensions/notes/src/routes.ts
|
|
455
|
+
import { Hono } from "hono";
|
|
456
|
+
async function parseJson(c) {
|
|
457
|
+
try {
|
|
458
|
+
return await c.req.json();
|
|
459
|
+
} catch {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function resolveUserId(c) {
|
|
464
|
+
const user = c.get("user");
|
|
465
|
+
if (!user || user.id === 0) return null;
|
|
466
|
+
return { id: user.id, username: user.username };
|
|
467
|
+
}
|
|
468
|
+
function createRoutes(ctx) {
|
|
469
|
+
if (!ctx.db) throw new Error("Notes extension requires a database");
|
|
470
|
+
const db = ctx.db;
|
|
471
|
+
const { getUser, getUserByUsername } = ctx;
|
|
472
|
+
const app = new Hono().get("/", async (c) => {
|
|
473
|
+
const author = c.req.query("author");
|
|
474
|
+
const rawLimit = c.req.query("limit");
|
|
475
|
+
const rawOffset = c.req.query("offset");
|
|
476
|
+
const limit = rawLimit ? parseInt(rawLimit, 10) : void 0;
|
|
477
|
+
const offset = rawOffset ? parseInt(rawOffset, 10) : void 0;
|
|
478
|
+
if (limit !== void 0 && Number.isNaN(limit) || offset !== void 0 && Number.isNaN(offset)) {
|
|
479
|
+
return c.json({ error: "Invalid limit or offset parameter" }, 400);
|
|
480
|
+
}
|
|
481
|
+
const result = await listNotes(db, getUser, getUserByUsername, {
|
|
482
|
+
authorUsername: author,
|
|
483
|
+
limit,
|
|
484
|
+
offset
|
|
485
|
+
});
|
|
486
|
+
return c.json(result);
|
|
487
|
+
}).post("/", async (c) => {
|
|
488
|
+
const actor = resolveUserId(c);
|
|
489
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
490
|
+
const body = await parseJson(c);
|
|
491
|
+
if (!body) return c.json({ error: "Invalid JSON body" }, 400);
|
|
492
|
+
if (!body.title || !body.content) {
|
|
493
|
+
return c.json({ error: "title and content are required" }, 400);
|
|
494
|
+
}
|
|
495
|
+
let replyToId;
|
|
496
|
+
if (body.reply_to) {
|
|
497
|
+
const id = await resolveNoteId(db, getUserByUsername, body.reply_to);
|
|
498
|
+
if (id === null) return c.json({ error: `Reply target not found: ${body.reply_to}` }, 404);
|
|
499
|
+
replyToId = id;
|
|
500
|
+
}
|
|
501
|
+
const note = await createNote(db, getUser, actor.id, body.title, body.content, replyToId);
|
|
502
|
+
ctx.publishActivity({
|
|
503
|
+
type: "note_created",
|
|
504
|
+
mind: actor.username,
|
|
505
|
+
summary: `${actor.username} wrote "${body.title}"`,
|
|
506
|
+
metadata: {
|
|
507
|
+
author: actor.username,
|
|
508
|
+
slug: note.slug,
|
|
509
|
+
bodyHtml: body.content.slice(0, 500)
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
return c.json(note, 201);
|
|
513
|
+
}).get("/:author/:slug", async (c) => {
|
|
514
|
+
const { author, slug } = c.req.param();
|
|
515
|
+
const note = await getNote(db, getUser, getUserByUsername, author, slug);
|
|
516
|
+
if (!note) return c.json({ error: "Note not found" }, 404);
|
|
517
|
+
return c.json(note);
|
|
518
|
+
}).put("/:author/:slug", async (c) => {
|
|
519
|
+
const actor = resolveUserId(c);
|
|
520
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
521
|
+
const { author, slug } = c.req.param();
|
|
522
|
+
if (actor.username !== author) return c.json({ error: "Forbidden" }, 403);
|
|
523
|
+
const body = await parseJson(c);
|
|
524
|
+
if (!body) return c.json({ error: "Invalid JSON body" }, 400);
|
|
525
|
+
const note = await updateNote(db, getUser, getUserByUsername, author, slug, body);
|
|
526
|
+
if (!note) return c.json({ error: "Note not found" }, 404);
|
|
527
|
+
return c.json(note);
|
|
528
|
+
}).delete("/:author/:slug", async (c) => {
|
|
529
|
+
const actor = resolveUserId(c);
|
|
530
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
531
|
+
const { author, slug } = c.req.param();
|
|
532
|
+
const deleted = await deleteNote(db, getUserByUsername, author, slug, actor.id);
|
|
533
|
+
if (!deleted) return c.json({ error: "Note not found or not authorized" }, 404);
|
|
534
|
+
return c.json({ ok: true });
|
|
535
|
+
}).post("/:author/:slug/reactions", async (c) => {
|
|
536
|
+
const actor = resolveUserId(c);
|
|
537
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
538
|
+
const { author, slug } = c.req.param();
|
|
539
|
+
const note = await getNote(db, getUser, getUserByUsername, author, slug);
|
|
540
|
+
if (!note) return c.json({ error: "Note not found" }, 404);
|
|
541
|
+
const body = await parseJson(c);
|
|
542
|
+
if (!body) return c.json({ error: "Invalid JSON body" }, 400);
|
|
543
|
+
if (!body.emoji) return c.json({ error: "emoji is required" }, 400);
|
|
544
|
+
const result = toggleReaction(db, note.id, actor.id, body.emoji);
|
|
545
|
+
const reactions = await getReactions(db, getUser, note.id);
|
|
546
|
+
return c.json({ ...result, reactions });
|
|
547
|
+
}).post("/:author/:slug/comments", async (c) => {
|
|
548
|
+
const actor = resolveUserId(c);
|
|
549
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
550
|
+
const { author, slug } = c.req.param();
|
|
551
|
+
const note = await getNote(db, getUser, getUserByUsername, author, slug);
|
|
552
|
+
if (!note) return c.json({ error: "Note not found" }, 404);
|
|
553
|
+
const body = await parseJson(c);
|
|
554
|
+
if (!body) return c.json({ error: "Invalid JSON body" }, 400);
|
|
555
|
+
if (!body.content) return c.json({ error: "content is required" }, 400);
|
|
556
|
+
const comment = await addComment(db, getUser, note.id, actor.id, body.content);
|
|
557
|
+
return c.json(comment, 201);
|
|
558
|
+
}).delete("/:author/:slug/comments/:id", async (c) => {
|
|
559
|
+
const actor = resolveUserId(c);
|
|
560
|
+
if (!actor) return c.json({ error: "Unauthorized" }, 401);
|
|
561
|
+
const commentId = parseInt(c.req.param("id"), 10);
|
|
562
|
+
if (Number.isNaN(commentId)) return c.json({ error: "Invalid comment ID" }, 400);
|
|
563
|
+
const deleted = await deleteComment(db, commentId, actor.id);
|
|
564
|
+
if (!deleted) return c.json({ error: "Comment not found or not authorized" }, 404);
|
|
565
|
+
return c.json({ ok: true });
|
|
566
|
+
}).get("/feed", async (c) => {
|
|
567
|
+
const rawFeedLimit = c.req.query("limit");
|
|
568
|
+
const limit = rawFeedLimit ? parseInt(rawFeedLimit, 10) : 8;
|
|
569
|
+
if (Number.isNaN(limit)) return c.json({ error: "Invalid limit parameter" }, 400);
|
|
570
|
+
const mind = c.req.query("mind");
|
|
571
|
+
const notes = await listNotes(db, getUser, getUserByUsername, {
|
|
572
|
+
limit,
|
|
573
|
+
...mind ? { authorUsername: mind } : {}
|
|
574
|
+
});
|
|
575
|
+
return c.json(
|
|
576
|
+
notes.map((n) => ({
|
|
577
|
+
id: `note-${n.author_username}-${n.slug}`,
|
|
578
|
+
title: n.title,
|
|
579
|
+
url: `/minds/${n.author_username}/notes/${n.slug}`,
|
|
580
|
+
date: n.created_at,
|
|
581
|
+
author: n.author_username,
|
|
582
|
+
bodyHtml: n.content,
|
|
583
|
+
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>',
|
|
584
|
+
color: "yellow"
|
|
585
|
+
}))
|
|
586
|
+
);
|
|
587
|
+
});
|
|
588
|
+
return app;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// packages/extensions/notes/src/index.ts
|
|
592
|
+
var assetsDir = resolve(import.meta.dirname, "../dist/ui");
|
|
593
|
+
var skillsDir = resolve(import.meta.dirname, "../skills");
|
|
594
|
+
var src_default = createExtension({
|
|
595
|
+
id: "notes",
|
|
596
|
+
name: "Notes",
|
|
597
|
+
version: "0.1.0",
|
|
598
|
+
description: "Public notes for sharing thoughts, reflections, and ideas",
|
|
599
|
+
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>',
|
|
600
|
+
color: "yellow",
|
|
601
|
+
routes: (ctx) => createRoutes(ctx),
|
|
602
|
+
commands: createCommands(),
|
|
603
|
+
initDb,
|
|
604
|
+
skillsDir,
|
|
605
|
+
standardSkill: true,
|
|
606
|
+
ui: {
|
|
607
|
+
assetsDir,
|
|
608
|
+
systemSection: { id: "notes", label: "Notes", urlPatterns: ["/notes", "/notes/:author/:slug"] },
|
|
609
|
+
mindSections: [{ id: "notes", label: "Notes" }],
|
|
610
|
+
feedSource: {
|
|
611
|
+
endpoint: "/api/ext/notes/feed"
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
export {
|
|
616
|
+
src_default as default
|
|
617
|
+
};
|