donguri-journal 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +181 -0
  3. package/README.md +184 -0
  4. package/dist/db/schema.d.ts +21 -0
  5. package/dist/db/schema.js +53 -0
  6. package/dist/db/schema.js.map +1 -0
  7. package/dist/db/store.d.ts +136 -0
  8. package/dist/db/store.js +0 -0
  9. package/dist/db/store.js.map +1 -0
  10. package/dist/embedding/provider.d.ts +37 -0
  11. package/dist/embedding/provider.js +53 -0
  12. package/dist/embedding/provider.js.map +1 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +49 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/kernel/config.d.ts +17 -0
  17. package/dist/kernel/config.js +26 -0
  18. package/dist/kernel/config.js.map +1 -0
  19. package/dist/kernel/context.d.ts +26 -0
  20. package/dist/kernel/context.js +7 -0
  21. package/dist/kernel/context.js.map +1 -0
  22. package/dist/kernel/module.d.ts +13 -0
  23. package/dist/kernel/module.js +7 -0
  24. package/dist/kernel/module.js.map +1 -0
  25. package/dist/kernel/plugin.d.ts +68 -0
  26. package/dist/kernel/plugin.js +144 -0
  27. package/dist/kernel/plugin.js.map +1 -0
  28. package/dist/kernel/result.d.ts +14 -0
  29. package/dist/kernel/result.js +11 -0
  30. package/dist/kernel/result.js.map +1 -0
  31. package/dist/management/module.d.ts +2 -0
  32. package/dist/management/module.js +39 -0
  33. package/dist/management/module.js.map +1 -0
  34. package/dist/management/server.d.ts +18 -0
  35. package/dist/management/server.js +216 -0
  36. package/dist/management/server.js.map +1 -0
  37. package/dist/management/ui.d.ts +10 -0
  38. package/dist/management/ui.js +159 -0
  39. package/dist/management/ui.js.map +1 -0
  40. package/dist/modules/core.d.ts +2 -0
  41. package/dist/modules/core.js +386 -0
  42. package/dist/modules/core.js.map +1 -0
  43. package/dist/modules/plugins.d.ts +2 -0
  44. package/dist/modules/plugins.js +177 -0
  45. package/dist/modules/plugins.js.map +1 -0
  46. package/dist/originals/store.d.ts +50 -0
  47. package/dist/originals/store.js +185 -0
  48. package/dist/originals/store.js.map +1 -0
  49. package/dist/review/charts.d.ts +16 -0
  50. package/dist/review/charts.js +69 -0
  51. package/dist/review/charts.js.map +1 -0
  52. package/dist/review/patterns.d.ts +33 -0
  53. package/dist/review/patterns.js +73 -0
  54. package/dist/review/patterns.js.map +1 -0
  55. package/dist/review/review.d.ts +30 -0
  56. package/dist/review/review.js +82 -0
  57. package/dist/review/review.js.map +1 -0
  58. package/dist/review/window.d.ts +18 -0
  59. package/dist/review/window.js +57 -0
  60. package/dist/review/window.js.map +1 -0
  61. package/package.json +62 -0
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Core module — registers the built-in tools (capture / query / recall / review
3
+ * / patterns / reindex / originals / management). It is just another
4
+ * `JournalModule`; opt-in modules register the same way.
5
+ */
6
+ import { statSync } from "node:fs";
7
+ import { z } from "zod";
8
+ import { errorResult, jsonResult } from "../kernel/result.js";
9
+ import { surfacePatterns } from "../review/patterns.js";
10
+ import { generateReview } from "../review/review.js";
11
+ /** Decode strict, canonical base64; returns null for malformed input. */
12
+ function decodeBase64Strict(input) {
13
+ const cleaned = input.replace(/\s+/g, "");
14
+ if (cleaned.length === 0 || cleaned.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(cleaned)) {
15
+ return null;
16
+ }
17
+ const buf = Buffer.from(cleaned, "base64");
18
+ return buf.toString("base64") === cleaned ? buf : null;
19
+ }
20
+ /** Result with an optional PNG chart followed by the structured JSON payload. */
21
+ function richResult(png, payload) {
22
+ const content = [];
23
+ if (png) {
24
+ content.push({ type: "image", data: png.toString("base64"), mimeType: "image/png" });
25
+ }
26
+ content.push({ type: "text", text: JSON.stringify(payload, null, 2) });
27
+ return { content };
28
+ }
29
+ export const coreModule = {
30
+ id: "core",
31
+ register(ctx) {
32
+ const { server, store, originals, config } = ctx;
33
+ server.registerTool("capture", {
34
+ title: "Capture a memory (stash an acorn)",
35
+ description: "Stash something into the user's long-term journal memory. Call this PROACTIVELY and " +
36
+ "with low friction whenever the user shares something worth remembering — a thought, an " +
37
+ "event, a decision, a link, a photo, a voice note — without interrogating them for " +
38
+ "details. For images / audio / URLs, YOU (the multimodal model) extract a faithful text " +
39
+ "description and pass it as `body`; this server does not process media. Put a pointer to " +
40
+ "the original (file path / URL) in `original_ref`. When the user shares an actual file " +
41
+ "(an attached image, an audio clip), ALSO send the raw bytes as base64 in `original_data` " +
42
+ "(with `original_mime` / `original_filename`): the server stores the verbatim original " +
43
+ "locally and sets `original_ref` for you, so it can be re-viewed later via get_original. " +
44
+ "Set `occurred_at` when the event happened at a different time than now (e.g. 'yesterday', " +
45
+ "'last week'); otherwise it defaults to the capture time. Identical captures are " +
46
+ "de-duplicated automatically.",
47
+ inputSchema: {
48
+ body: z
49
+ .string()
50
+ .min(1)
51
+ .describe("The text to index and recall later. For non-text sources, this is your faithful " +
52
+ "extracted description/transcript of the original."),
53
+ source_kind: z
54
+ .enum(["text", "image", "audio", "url", "note"])
55
+ .optional()
56
+ .describe("What the memory originated from. Defaults to 'text'."),
57
+ original_ref: z
58
+ .string()
59
+ .optional()
60
+ .describe("Pointer to an externally-held original (file path or URL). Omit when sending " +
61
+ "`original_data` — the server fills this in with the stored reference."),
62
+ original_data: z
63
+ .string()
64
+ .optional()
65
+ .describe("Base64-encoded raw bytes of the original artifact (image/audio/file). When present, " +
66
+ "the server saves it verbatim and overwrites `original_ref` with the stored ref."),
67
+ original_mime: z
68
+ .string()
69
+ .optional()
70
+ .describe("MIME type of `original_data`, e.g. 'image/png', 'audio/mpeg'."),
71
+ original_filename: z
72
+ .string()
73
+ .optional()
74
+ .describe("Optional original filename; used to pick the stored file extension."),
75
+ extraction_state: z
76
+ .enum(["verbatim", "llm_extracted"])
77
+ .optional()
78
+ .describe("'verbatim' when `body` is the original text; 'llm_extracted' when you derived it " +
79
+ "from media/a URL (lossy, may be re-extracted later). Defaults to 'verbatim'."),
80
+ tags: z.array(z.string()).optional().describe("Optional labels for structured lookup."),
81
+ meta: z
82
+ .record(z.unknown())
83
+ .optional()
84
+ .describe("Optional structured metadata (e.g. mood, location, people)."),
85
+ occurred_at: z
86
+ .string()
87
+ .datetime({ offset: true })
88
+ .optional()
89
+ .describe("ISO-8601 timestamp (e.g. 2026-06-20T09:00:00Z) of when the event actually " +
90
+ "happened, if different from now. Must include a time so range queries stay correct."),
91
+ },
92
+ }, async (args) => {
93
+ const { original_data, original_mime, original_filename, ...entry } = args;
94
+ if (!original_data || original_data.length === 0) {
95
+ // Text-only capture (or an external original_ref passed through as-is).
96
+ return jsonResult(await store.insert(entry));
97
+ }
98
+ // Coarse guard avoids decoding an absurd payload; the exact size check is
99
+ // on the decoded length, so a valid base64 right at the limit is not
100
+ // over-rejected.
101
+ const tooLarge = errorResult(`original_data exceeds the maximum allowed size (${config.maxOriginalBytes} bytes)`);
102
+ if (original_data.length > config.maxOriginalBytes * 2) {
103
+ return tooLarge;
104
+ }
105
+ const bytes = decodeBase64Strict(original_data);
106
+ if (!bytes) {
107
+ return errorResult("original_data is not valid base64");
108
+ }
109
+ if (bytes.length > config.maxOriginalBytes) {
110
+ return tooLarge;
111
+ }
112
+ // Insert the entry FIRST, then save + attach the original, so a dedupe
113
+ // (or an insert failure) can never leave a saved-but-unreferenced original.
114
+ entry.original_ref = undefined;
115
+ const result = await store.insert(entry);
116
+ const existingRef = result.deduped ? store.getOriginalRef(result.id) : null;
117
+ if (existingRef) {
118
+ return jsonResult({ ...result, original_ref: existingRef });
119
+ }
120
+ const saved = await originals.save({
121
+ data: bytes,
122
+ mime: original_mime,
123
+ filename: original_filename,
124
+ });
125
+ const attached = store.attachOriginalIfAbsent(result.id, saved.ref);
126
+ if (attached) {
127
+ return jsonResult({ ...result, original_ref: saved.ref });
128
+ }
129
+ // Not attached: either the row already had an original (return that), or
130
+ // the row vanished between insert and save — report honestly rather than
131
+ // claim a ref we didn't store. The unreferenced content-addressed blob is
132
+ // left for a future orphan sweep (never wrongly deleted, as it may be shared).
133
+ const existing = store.getOriginalRef(result.id);
134
+ if (existing) {
135
+ return jsonResult({ ...result, original_ref: existing });
136
+ }
137
+ return errorResult("The entry was removed before its original could be attached.");
138
+ });
139
+ server.registerTool("query_entries", {
140
+ title: "Query entries by time / tag / kind",
141
+ description: "Structured lookup over the journal for PRECISE, filterable questions: a date or date " +
142
+ "range, a tag, a source kind. Use this for 'what did I write last week', 'show my notes " +
143
+ "tagged work', BuJo-style weekly/monthly reviews — anything answerable by filters rather " +
144
+ "than meaning. Choose `time_field`: 'created_at' (when captured) or 'occurred_at' (when " +
145
+ "the event happened). For meaning-based 'have I thought about X before' questions, use " +
146
+ "recall_related instead.",
147
+ inputSchema: {
148
+ since: z
149
+ .string()
150
+ .datetime({ offset: true })
151
+ .optional()
152
+ .describe("ISO-8601 timestamp; lower bound (inclusive)."),
153
+ until: z
154
+ .string()
155
+ .datetime({ offset: true })
156
+ .optional()
157
+ .describe("ISO-8601 timestamp; upper bound (inclusive)."),
158
+ source_kind: z.string().optional().describe("Filter by source kind."),
159
+ tag: z.string().optional().describe("Filter to entries carrying this tag."),
160
+ time_field: z
161
+ .enum(["created_at", "occurred_at"])
162
+ .optional()
163
+ .describe("Which timestamp to filter and sort by. Defaults to 'created_at'."),
164
+ limit: z.number().int().optional().describe("Max rows (1-500, default 50)."),
165
+ },
166
+ }, async (args) => {
167
+ const entries = store.query(args);
168
+ return jsonResult({ count: entries.length, entries });
169
+ });
170
+ server.registerTool("recall_related", {
171
+ title: "Recall related memories (dig up acorns)",
172
+ description: "Semantic search: find past entries related in MEANING to a query, even with different " +
173
+ "wording. Call this PROACTIVELY when the user reflects, wonders, or revisits a topic " +
174
+ "('have I felt this before?', 'what was that idea about...') so you can surface relevant " +
175
+ "past memories. For exact date/tag filtering use query_entries instead. Results are " +
176
+ "ordered by similarity (smaller `distance` = closer).",
177
+ inputSchema: {
178
+ query: z.string().min(1).describe("Natural-language description of what to recall."),
179
+ k: z
180
+ .number()
181
+ .int()
182
+ .optional()
183
+ .describe("How many neighbours to return (1-100, default 10)."),
184
+ },
185
+ }, async (args) => {
186
+ const hits = await store.recall(args.query, args.k);
187
+ return jsonResult({ count: hits.length, hits });
188
+ });
189
+ server.registerTool("generate_review", {
190
+ title: "Generate a time-window review",
191
+ description: "Produce a reflective review of a period — call this when the user wants to look back " +
192
+ "('how was my week?', 'review this month', daily/weekly/monthly check-ins, BuJo-style " +
193
+ "migration). Returns structured aggregates (totals, busiest day, source kinds, top tags), " +
194
+ "presentation hints, and — when there are entries to plot — an attached PNG chart of " +
195
+ "activity over time (otherwise structured data only). Show the chart if present and weave " +
196
+ "the aggregates into a short reflective summary — do not just dump the numbers. Pick " +
197
+ "`period` (day/week/month), or pass BOTH `since` and `until` for an explicit custom range " +
198
+ "(one without the other is an error). `time_field` selects when-captured vs when-it-happened.",
199
+ inputSchema: {
200
+ period: z
201
+ .enum(["day", "week", "month"])
202
+ .optional()
203
+ .describe("Calendar window containing `anchor`. Defaults to 'week'. Ignored if since+until given."),
204
+ anchor: z
205
+ .string()
206
+ .datetime({ offset: true })
207
+ .optional()
208
+ .describe("ISO-8601 point in time the period is computed around. Defaults to now."),
209
+ since: z
210
+ .string()
211
+ .datetime({ offset: true })
212
+ .optional()
213
+ .describe("ISO-8601 lower bound for an explicit custom window (use with `until`)."),
214
+ until: z
215
+ .string()
216
+ .datetime({ offset: true })
217
+ .optional()
218
+ .describe("ISO-8601 upper bound for an explicit custom window (use with `since`)."),
219
+ time_field: z
220
+ .enum(["created_at", "occurred_at"])
221
+ .optional()
222
+ .describe("Which timestamp to window by. Defaults to 'created_at'."),
223
+ },
224
+ }, async (args) => {
225
+ const out = await generateReview(store, args);
226
+ return richResult(out.chartPng, {
227
+ structured: out.structured,
228
+ presentation_hints: out.presentation_hints,
229
+ });
230
+ });
231
+ server.registerTool("surface_patterns", {
232
+ title: "Surface recurring themes (echoes)",
233
+ description: "Detect recurring themes — recent entries that echo something the user wrote BEFORE. Call " +
234
+ "this when the user reflects on habits or patterns ('do I keep coming back to this?', " +
235
+ "'am I in a rut?') or proactively during reviews. For each recent entry it finds " +
236
+ "semantically similar older entries; returns the echo clusters (with distances), " +
237
+ "presentation hints, and — when any echoes are found — an attached PNG chart of the " +
238
+ "strongest echoes (otherwise structured data only). Each echo is a CANDIDATE recurrence — " +
239
+ "judge relevance yourself and present gently, don't over-claim.",
240
+ inputSchema: {
241
+ lookback_days: z
242
+ .number()
243
+ .int()
244
+ .optional()
245
+ .describe("How far back to treat entries as 'recent' (1-3650, default 30)."),
246
+ max_recent: z
247
+ .number()
248
+ .int()
249
+ .optional()
250
+ .describe("Max recent entries to examine (1-200, default 50)."),
251
+ per_entry: z
252
+ .number()
253
+ .int()
254
+ .optional()
255
+ .describe("Neighbours considered per recent entry (1-20, default 5)."),
256
+ max_distance: z
257
+ .number()
258
+ .optional()
259
+ .describe("Distance cutoff; only closer echoes are kept (default 1.3, smaller = stricter)."),
260
+ },
261
+ }, async (args) => {
262
+ const out = await surfacePatterns(store, args);
263
+ return richResult(out.chartPng, {
264
+ structured: out.structured,
265
+ presentation_hints: out.presentation_hints,
266
+ });
267
+ });
268
+ server.registerTool("reindex", {
269
+ title: "Rebuild the semantic search index",
270
+ description: "Maintenance operation: rebuild the vector index from the original entries using the " +
271
+ "current embedding backend. Use this after switching the embedding model — the server " +
272
+ "logs a warning on startup when the active model no longer matches what the index was " +
273
+ "built with, because vectors from different models are not comparable. Originals are " +
274
+ "never touched; only the disposable index is rebuilt, then re-recall works again. May " +
275
+ "take a while for large journals. Not part of normal capture/recall — only run it when " +
276
+ "the user asks to reindex or after a backend change.",
277
+ inputSchema: {},
278
+ }, async () => {
279
+ const result = await store.reindex();
280
+ return jsonResult(result);
281
+ });
282
+ server.registerTool("get_original", {
283
+ title: "Fetch a stored original artifact (re-view the acorn)",
284
+ description: "Retrieve the verbatim original (image / audio / file) that was saved at capture time, by " +
285
+ "its `original_ref` (e.g. 'local:<sha256>' from an entry). Images are returned inline " +
286
+ "so you (the multimodal LLM) can look again and, if needed, RE-EXTRACT text from them; " +
287
+ "other types return metadata only. Use this after recall_related / query_entries " +
288
+ "surfaces an entry whose original you want to actually see again.",
289
+ inputSchema: {
290
+ original_ref: z
291
+ .string()
292
+ .min(1)
293
+ .describe("The entry's original_ref, e.g. 'local:<sha256>'."),
294
+ },
295
+ }, async ({ original_ref }) => {
296
+ const loaded = await originals.get(original_ref);
297
+ if (!loaded) {
298
+ return jsonResult({ found: false, original_ref });
299
+ }
300
+ // Never return the absolute filesystem path (it leaks home dir /
301
+ // username). Images come back inline; other types return metadata only
302
+ // (opening non-renderable originals is the management UI's job).
303
+ const base = { found: true, original_ref, mime: loaded.mime, bytes: loaded.data.length };
304
+ const content = [];
305
+ if (loaded.mime?.startsWith("image/")) {
306
+ content.push({
307
+ type: "image",
308
+ data: loaded.data.toString("base64"),
309
+ mimeType: loaded.mime,
310
+ });
311
+ }
312
+ content.push({ type: "text", text: JSON.stringify(base, null, 2) });
313
+ return { content };
314
+ });
315
+ server.registerTool("delete_entry", {
316
+ title: "Delete an entry",
317
+ description: "Delete a journal entry by id. `mode: 'soft'` (default) sets a recoverable tombstone — the " +
318
+ "entry stops appearing in query/recall but can be restored. `mode: 'hard'` permanently " +
319
+ "purges the entry, its vector, and its original (when no other entry references it) and " +
320
+ "VACUUMs the database — use this to truly erase something captured by mistake (e.g. a " +
321
+ "secret). Hard delete is irreversible; confirm with the user before using it.",
322
+ inputSchema: {
323
+ id: z.number().int().describe("The entry id to delete."),
324
+ mode: z
325
+ .enum(["soft", "hard"])
326
+ .optional()
327
+ .describe("'soft' (default, recoverable) or 'hard' (permanent erase)."),
328
+ },
329
+ }, async (args) => {
330
+ const mode = args.mode ?? "soft";
331
+ if (mode === "soft") {
332
+ return jsonResult({ id: args.id, mode, deleted: store.softDelete(args.id) });
333
+ }
334
+ // Hard delete: erase the orphaned original FIRST so a failure can't leave its
335
+ // bytes behind, then purge the row + vector and VACUUM.
336
+ const peek = store.peekHardDelete(args.id);
337
+ if (!peek.exists) {
338
+ return jsonResult({ id: args.id, mode, deleted: false });
339
+ }
340
+ let originalErased = null;
341
+ if (peek.orphan && peek.original_ref) {
342
+ try {
343
+ originalErased = await originals.delete(peek.original_ref);
344
+ }
345
+ catch (err) {
346
+ // Log details to stderr; keep the tool output generic (no raw exception).
347
+ ctx.log("failed to erase original during hard delete:", err);
348
+ return errorResult("Failed to erase the original; the entry was left intact so you can retry.");
349
+ }
350
+ }
351
+ // The original is already gone; if purge fails, the entry stays and the
352
+ // operation is safely retryable (a re-run re-purges; deleting an
353
+ // already-missing original is a no-op).
354
+ let deleted;
355
+ try {
356
+ deleted = store.purgeEntry(args.id);
357
+ }
358
+ catch (err) {
359
+ ctx.log("failed to purge entry after erasing original:", err);
360
+ return errorResult("Erased the original but failed to purge the entry; run delete again to finish.");
361
+ }
362
+ return jsonResult({ id: args.id, mode, deleted, original_erased: originalErased });
363
+ });
364
+ server.registerTool("storage_stats", {
365
+ title: "Storage statistics",
366
+ description: "Report how big the journal is: entry counts (active vs soft-deleted), vector count, " +
367
+ "breakdown by source kind and by month, the originals count + total bytes, and the database " +
368
+ "file size. Use this for capacity questions like 'how much have I stored?'.",
369
+ inputSchema: {},
370
+ }, async () => {
371
+ const entries = store.entryStats();
372
+ const originalsStats = await originals.stats();
373
+ // null (not 0) distinguishes a stat error from a genuinely empty DB; the
374
+ // absolute path is withheld to avoid leaking local filesystem details.
375
+ let dbBytes = null;
376
+ try {
377
+ dbBytes = statSync(config.dbPath).size;
378
+ }
379
+ catch {
380
+ dbBytes = null;
381
+ }
382
+ return jsonResult({ entries, originals: originalsStats, db_bytes: dbBytes });
383
+ });
384
+ },
385
+ };
386
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/modules/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { JournalModule } from "../kernel/module.js";
2
+ export declare const pluginsModule: JournalModule;
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Plugins module — agent-driven plugin lifecycle over MCP (no user CLI).
3
+ *
4
+ * Local install is supported now: point install_plugin at a plugin directory,
5
+ * review the declared capabilities, then confirm. On confirm the plugin is
6
+ * copied in, recorded as enabled, and loaded at runtime — because registering
7
+ * tools emits `tools/list_changed`, its tools appear without restarting the
8
+ * client. A hosted registry (list_available_plugins) comes in a later step.
9
+ */
10
+ import { existsSync } from "node:fs";
11
+ import { cp, mkdir, rm } from "node:fs/promises";
12
+ import { z } from "zod";
13
+ import { assertAbsoluteDir, isValidPluginId, loadPluginConfig, loadPluginModule, pluginDir, readManifest, savePluginConfig, } from "../kernel/plugin.js";
14
+ import { errorResult, jsonResult } from "../kernel/result.js";
15
+ export const pluginsModule = {
16
+ id: "plugins",
17
+ register(ctx) {
18
+ const { server, config } = ctx;
19
+ server.registerTool("list_installed_plugins", {
20
+ title: "List installed plugins",
21
+ description: "List the plugins installed into this journal, with their enabled state, version, and " +
22
+ "declared capabilities. Use this to see what extra tools are available or to check " +
23
+ "before installing/uninstalling.",
24
+ inputSchema: {},
25
+ }, async () => {
26
+ let cfg;
27
+ try {
28
+ cfg = await loadPluginConfig(config.pluginsConfigPath);
29
+ }
30
+ catch (err) {
31
+ return errorResult(err instanceof Error ? err.message : String(err));
32
+ }
33
+ const plugins = [];
34
+ for (const entry of cfg.plugins) {
35
+ if (!isValidPluginId(entry.id)) {
36
+ plugins.push({ id: entry.id, enabled: entry.enabled, invalid: true });
37
+ continue;
38
+ }
39
+ let name;
40
+ let version;
41
+ let capabilities = [];
42
+ try {
43
+ const manifest = await readManifest(pluginDir(ctx, entry.id));
44
+ name = manifest.name;
45
+ version = manifest.version;
46
+ capabilities = manifest.capabilities;
47
+ }
48
+ catch {
49
+ // Manifest unreadable (dir removed manually): still list the id.
50
+ }
51
+ plugins.push({ id: entry.id, enabled: entry.enabled, name, version, capabilities });
52
+ }
53
+ return jsonResult({ count: plugins.length, plugins });
54
+ });
55
+ server.registerTool("install_plugin", {
56
+ title: "Install a plugin",
57
+ description: "Install a donguri-journal plugin from a local directory. This runs third-party code " +
58
+ "in-process, so it is a TWO-STEP, consented action: first call with just `source` to " +
59
+ "get the plugin's manifest and DECLARED CAPABILITIES; present those to the user; only " +
60
+ "after they approve, call again with `confirm: true`. On confirm the plugin is copied " +
61
+ "in, enabled, and loaded immediately (its tools become available without a restart).",
62
+ inputSchema: {
63
+ source: z
64
+ .string()
65
+ .min(1)
66
+ .describe("Absolute path to the plugin directory (contains donguri.plugin.json)."),
67
+ confirm: z
68
+ .boolean()
69
+ .optional()
70
+ .describe("Set true ONLY after the user approves the manifest + capabilities."),
71
+ },
72
+ }, async ({ source, confirm }) => {
73
+ try {
74
+ assertAbsoluteDir(source);
75
+ }
76
+ catch (err) {
77
+ return errorResult(err instanceof Error ? err.message : String(err));
78
+ }
79
+ let manifest;
80
+ try {
81
+ manifest = await readManifest(source);
82
+ }
83
+ catch (err) {
84
+ return errorResult(`invalid plugin: ${err instanceof Error ? err.message : String(err)}`);
85
+ }
86
+ if (!confirm) {
87
+ return jsonResult({
88
+ requires_confirmation: true,
89
+ manifest: {
90
+ id: manifest.id,
91
+ name: manifest.name,
92
+ version: manifest.version,
93
+ description: manifest.description,
94
+ capabilities: manifest.capabilities,
95
+ },
96
+ message: "Show the user the name, version and capabilities. Install runs third-party code — " +
97
+ "call install_plugin again with confirm: true only after they approve.",
98
+ });
99
+ }
100
+ const dest = pluginDir(ctx, manifest.id);
101
+ if (existsSync(dest)) {
102
+ return errorResult(`plugin '${manifest.id}' is already installed; uninstall it first`);
103
+ }
104
+ let cfgBefore;
105
+ try {
106
+ cfgBefore = await loadPluginConfig(config.pluginsConfigPath);
107
+ }
108
+ catch (err) {
109
+ return errorResult(err instanceof Error ? err.message : String(err));
110
+ }
111
+ try {
112
+ await mkdir(config.pluginsDir, { recursive: true });
113
+ await cp(source, dest, { recursive: true });
114
+ const next = {
115
+ plugins: [
116
+ ...cfgBefore.plugins.filter((p) => p.id !== manifest.id),
117
+ { id: manifest.id, enabled: true },
118
+ ],
119
+ };
120
+ await savePluginConfig(config.pluginsConfigPath, next);
121
+ const mod = await loadPluginModule(dest, manifest);
122
+ await mod.register(ctx);
123
+ }
124
+ catch (err) {
125
+ // Full rollback covering mkdir/cp/save/load: remove any copied files
126
+ // and restore the prior config.
127
+ await rm(dest, { recursive: true, force: true }).catch(() => { });
128
+ await savePluginConfig(config.pluginsConfigPath, cfgBefore).catch(() => { });
129
+ ctx.log(`install failed for ${manifest.id}:`, err);
130
+ return errorResult(`failed to install plugin: ${err instanceof Error ? err.message : String(err)}`);
131
+ }
132
+ return jsonResult({
133
+ installed: manifest.id,
134
+ version: manifest.version,
135
+ message: "Installed and active — its tools are now available.",
136
+ });
137
+ });
138
+ server.registerTool("uninstall_plugin", {
139
+ title: "Uninstall a plugin",
140
+ description: "Remove an installed plugin by id: deletes it from disk and the plugin registry. Note: " +
141
+ "tools it already registered stay available until the server restarts.",
142
+ inputSchema: {
143
+ id: z.string().min(1).describe("The plugin id to uninstall."),
144
+ },
145
+ }, async ({ id }) => {
146
+ if (!isValidPluginId(id)) {
147
+ return errorResult("invalid plugin id");
148
+ }
149
+ let cfg;
150
+ try {
151
+ cfg = await loadPluginConfig(config.pluginsConfigPath);
152
+ }
153
+ catch (err) {
154
+ return errorResult(err instanceof Error ? err.message : String(err));
155
+ }
156
+ const wasInstalled = cfg.plugins.some((p) => p.id === id);
157
+ try {
158
+ // Delete from disk BEFORE updating the registry, so a failed rm can't
159
+ // leave an unlisted-but-present directory that blocks reinstall.
160
+ await rm(pluginDir(ctx, id), { recursive: true, force: true });
161
+ await savePluginConfig(config.pluginsConfigPath, {
162
+ plugins: cfg.plugins.filter((p) => p.id !== id),
163
+ });
164
+ }
165
+ catch (err) {
166
+ ctx.log(`uninstall failed for ${id}:`, err);
167
+ return errorResult(`failed to uninstall plugin: ${err instanceof Error ? err.message : String(err)}`);
168
+ }
169
+ return jsonResult({
170
+ uninstalled: id,
171
+ was_installed: wasInstalled,
172
+ note: "Removed from disk. Any already-registered tools remain until the server restarts.",
173
+ });
174
+ });
175
+ },
176
+ };
177
+ //# sourceMappingURL=plugins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.js","sourceRoot":"","sources":["../../src/modules/plugins.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ export interface SaveOriginalInput {
2
+ data: Buffer;
3
+ mime?: string;
4
+ filename?: string;
5
+ }
6
+ export interface SavedOriginal {
7
+ /** Reference stored in entries.original_ref, e.g. "local:<sha256>". */
8
+ ref: string;
9
+ bytes: number;
10
+ mime?: string;
11
+ }
12
+ export interface LoadedOriginal {
13
+ data: Buffer;
14
+ mime?: string;
15
+ /** Absolute path on disk (originals live on the same machine as the server). */
16
+ path: string;
17
+ }
18
+ export interface OriginalsStats {
19
+ count: number;
20
+ bytes: number;
21
+ }
22
+ export interface OriginalStore {
23
+ readonly kind: string;
24
+ save(input: SaveOriginalInput): Promise<SavedOriginal>;
25
+ /** Load a previously saved original, or null if the ref is unknown to this store. */
26
+ get(ref: string): Promise<LoadedOriginal | null>;
27
+ /** Remove an original (and its sidecar). Returns true if a blob was deleted. */
28
+ delete(ref: string): Promise<boolean>;
29
+ /** Aggregate count + total bytes of stored originals. */
30
+ stats(): Promise<OriginalsStats>;
31
+ }
32
+ /**
33
+ * Content-addressed store under a local directory. Each original is stored as a
34
+ * blob named `<sha256>` plus a `<sha256>.json` sidecar holding its MIME type and
35
+ * original filename.
36
+ */
37
+ export declare class LocalDirStore implements OriginalStore {
38
+ #private;
39
+ readonly kind = "local";
40
+ constructor(baseDir: string);
41
+ save(input: SaveOriginalInput): Promise<SavedOriginal>;
42
+ get(ref: string): Promise<LoadedOriginal | null>;
43
+ delete(ref: string): Promise<boolean>;
44
+ stats(): Promise<OriginalsStats>;
45
+ }
46
+ /**
47
+ * Choose an original store. Today this is always the local directory; an Eagle
48
+ * backend (when JOURNAL_EAGLE_API is set) can be slotted in here later.
49
+ */
50
+ export declare function createOriginalStore(baseDir?: string): OriginalStore;