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,37 @@
1
+ /**
2
+ * Embedding backend abstraction.
3
+ *
4
+ * The default runs fully in-process via transformers.js (no Ollama, no manual
5
+ * model pull) so the server works with a bare `npx`. The interface is swappable
6
+ * so power users can plug in Ollama or a cloud API later — the active model id
7
+ * and dimension are recorded in `embedding_meta` so a backend switch can be
8
+ * detected and trigger a reindex (vectors from different models are not
9
+ * comparable).
10
+ */
11
+ export interface EmbeddingProvider {
12
+ /** Stable identifier of the model, e.g. "Xenova/all-MiniLM-L6-v2". */
13
+ readonly modelId: string;
14
+ /** Output dimensionality. Fixed for the lifetime of the provider. */
15
+ readonly dim: number;
16
+ /** Embed a batch of texts. Returns one vector (length `dim`) per input. */
17
+ embed(texts: string[]): Promise<number[][]>;
18
+ }
19
+ /**
20
+ * Default provider: mean-pooled, L2-normalized sentence embeddings computed
21
+ * locally with transformers.js. The model is downloaded and cached on first
22
+ * use, so the pipeline is created lazily (the server can start, and alternative
23
+ * backends can be used, without ever loading transformers.js).
24
+ */
25
+ export declare class LocalTransformersProvider implements EmbeddingProvider {
26
+ #private;
27
+ readonly modelId: string;
28
+ readonly dim: number;
29
+ constructor(modelId?: string, dim?: number);
30
+ embed(texts: string[]): Promise<number[][]>;
31
+ }
32
+ /**
33
+ * Choose an embedding backend. Today this always returns the in-process default;
34
+ * later it will read an env var (e.g. JOURNAL_EMBEDDING_BACKEND) to select an
35
+ * Ollama or cloud provider.
36
+ */
37
+ export declare function createEmbeddingProvider(): EmbeddingProvider;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Embedding backend abstraction.
3
+ *
4
+ * The default runs fully in-process via transformers.js (no Ollama, no manual
5
+ * model pull) so the server works with a bare `npx`. The interface is swappable
6
+ * so power users can plug in Ollama or a cloud API later — the active model id
7
+ * and dimension are recorded in `embedding_meta` so a backend switch can be
8
+ * detected and trigger a reindex (vectors from different models are not
9
+ * comparable).
10
+ */
11
+ const DEFAULT_MODEL = "Xenova/all-MiniLM-L6-v2";
12
+ const DEFAULT_DIM = 384;
13
+ /**
14
+ * Default provider: mean-pooled, L2-normalized sentence embeddings computed
15
+ * locally with transformers.js. The model is downloaded and cached on first
16
+ * use, so the pipeline is created lazily (the server can start, and alternative
17
+ * backends can be used, without ever loading transformers.js).
18
+ */
19
+ export class LocalTransformersProvider {
20
+ modelId;
21
+ dim;
22
+ #pipeline = null;
23
+ constructor(modelId = DEFAULT_MODEL, dim = DEFAULT_DIM) {
24
+ this.modelId = modelId;
25
+ this.dim = dim;
26
+ }
27
+ #getPipeline() {
28
+ if (!this.#pipeline) {
29
+ this.#pipeline = (async () => {
30
+ const { pipeline } = await import("@xenova/transformers");
31
+ const extractor = await pipeline("feature-extraction", this.modelId);
32
+ return extractor;
33
+ })();
34
+ }
35
+ return this.#pipeline;
36
+ }
37
+ async embed(texts) {
38
+ if (texts.length === 0)
39
+ return [];
40
+ const extractor = await this.#getPipeline();
41
+ const output = await extractor(texts, { pooling: "mean", normalize: true });
42
+ return output.tolist();
43
+ }
44
+ }
45
+ /**
46
+ * Choose an embedding backend. Today this always returns the in-process default;
47
+ * later it will read an env var (e.g. JOURNAL_EMBEDDING_BACKEND) to select an
48
+ * Ollama or cloud provider.
49
+ */
50
+ export function createEmbeddingProvider() {
51
+ return new LocalTransformersProvider();
52
+ }
53
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/embedding/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAWH,MAAM,aAAa,GAAG,yBAAyB,CAAC;AAChD,MAAM,WAAW,GAAG,GAAG,CAAC;AAQxB;;;;;GAKG;AACH,MAAM,OAAO,yBAAyB;IAC3B,OAAO,CAAS;IAChB,GAAG,CAAS;IACrB,SAAS,GAA8C,IAAI,CAAC;IAE5D,YAAY,UAAkB,aAAa,EAAE,MAAc,WAAW;QACpE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,YAAY;QACV,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,IAAI,CAAC,SAAS,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC3B,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;gBAC1D,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,oBAAoB,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;gBACrE,OAAO,SAAiD,CAAC;YAC3D,CAAC,CAAC,EAAE,CAAC;QACP,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAe;QACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5E,OAAO,MAAM,CAAC,MAAM,EAAE,CAAC;IACzB,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB;IACrC,OAAO,IAAI,yBAAyB,EAAE,CAAC;AACzC,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * donguri-journal — local-first, time-aware journaling memory server over MCP.
4
+ *
5
+ * A squirrel stashing acorns (capture) and digging them up later (recall).
6
+ * The front-end multimodal LLM is the companion/UI; this server is the
7
+ * persistent memory organ behind it.
8
+ *
9
+ * This entrypoint is deliberately thin: it builds the kernel context and
10
+ * registers a list of modules. The core tools live in the `core` module; opt-in
11
+ * features plug in the same way.
12
+ *
13
+ * NOTE: stdout is reserved for the MCP protocol. All logging goes to stderr.
14
+ */
15
+ import { mkdirSync } from "node:fs";
16
+ import { dirname } from "node:path";
17
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
18
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
19
+ import { JournalStore } from "./db/store.js";
20
+ import { createEmbeddingProvider } from "./embedding/provider.js";
21
+ import { loadConfig } from "./kernel/config.js";
22
+ import { createContext } from "./kernel/context.js";
23
+ import { registerModules } from "./kernel/module.js";
24
+ import { loadInstalledPlugins } from "./kernel/plugin.js";
25
+ import { managementModule } from "./management/module.js";
26
+ import { coreModule } from "./modules/core.js";
27
+ import { pluginsModule } from "./modules/plugins.js";
28
+ import { createOriginalStore } from "./originals/store.js";
29
+ const config = loadConfig();
30
+ mkdirSync(dirname(config.dbPath), { recursive: true });
31
+ const store = new JournalStore(config.dbPath, createEmbeddingProvider());
32
+ store.init();
33
+ const originals = createOriginalStore(config.originalsDir);
34
+ const server = new McpServer({ name: "donguri-journal", version: "0.1.0" });
35
+ const ctx = createContext({ server, store, originals, config });
36
+ // Built-in modules. Installed plugins are loaded separately (see below).
37
+ const modules = [coreModule, managementModule, pluginsModule];
38
+ async function main() {
39
+ await registerModules(ctx, modules);
40
+ await loadInstalledPlugins(ctx);
41
+ const transport = new StdioServerTransport();
42
+ await server.connect(transport);
43
+ ctx.log(`running on stdio (db: ${config.dbPath})`);
44
+ }
45
+ main().catch((err) => {
46
+ console.error("Fatal error starting donguri-journal:", err);
47
+ process.exit(1);
48
+ });
49
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAsB,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;AAC5B,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAEvD,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,CAAC;AACzE,KAAK,CAAC,IAAI,EAAE,CAAC;AACb,MAAM,SAAS,GAAG,mBAAmB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;AAE3D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAC5E,MAAM,GAAG,GAAG,aAAa,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;AAEhE,yEAAyE;AACzE,MAAM,OAAO,GAAoB,CAAC,UAAU,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAC;AAE/E,KAAK,UAAU,IAAI;IACjB,MAAM,eAAe,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACpC,MAAM,oBAAoB,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,GAAG,CAAC,GAAG,CAAC,yBAAyB,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;AACrD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,KAAK,CAAC,uCAAuC,EAAE,GAAG,CAAC,CAAC;IAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,17 @@
1
+ export interface JournalConfig {
2
+ /** SQLite database file path. */
3
+ dbPath: string;
4
+ /** Directory for content-addressed original artifacts. */
5
+ originalsDir: string;
6
+ /** Max accepted size of a single original artifact (decoded bytes). */
7
+ maxOriginalBytes: number;
8
+ /** Directory where installed plugins live (one subdirectory per plugin id). */
9
+ pluginsDir: string;
10
+ /** JSON file recording which plugins are installed / enabled. */
11
+ pluginsConfigPath: string;
12
+ /** Host the management UI binds to. Localhost-only by default. */
13
+ uiHost: string;
14
+ /** Port for the management UI. 0 = an ephemeral port chosen at listen time. */
15
+ uiPort: number;
16
+ }
17
+ export declare function loadConfig(): JournalConfig;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Centralized configuration, resolved once from the environment. Modules read
3
+ * config through the kernel context rather than touching process.env directly.
4
+ */
5
+ import { homedir } from "node:os";
6
+ import { join } from "node:path";
7
+ const DEFAULT_MAX_ORIGINAL_BYTES = 25 * 1024 * 1024;
8
+ function envOr(name, fallback) {
9
+ const value = process.env[name];
10
+ return value && value.length > 0 ? value : fallback;
11
+ }
12
+ export function loadConfig() {
13
+ const home = homedir();
14
+ const max = Number(process.env.JOURNAL_MAX_ORIGINAL_BYTES);
15
+ const uiPort = Number(process.env.JOURNAL_UI_PORT);
16
+ return {
17
+ dbPath: envOr("JOURNAL_DB_PATH", join(home, ".journal-mcp", "journal.db")),
18
+ originalsDir: envOr("JOURNAL_ORIGINALS_DIR", join(home, ".journal-mcp", "originals")),
19
+ maxOriginalBytes: Number.isFinite(max) && max > 0 ? Math.floor(max) : DEFAULT_MAX_ORIGINAL_BYTES,
20
+ pluginsDir: envOr("JOURNAL_PLUGINS_DIR", join(home, ".journal-mcp", "plugins")),
21
+ pluginsConfigPath: envOr("JOURNAL_PLUGINS_CONFIG", join(home, ".journal-mcp", "plugins.json")),
22
+ uiHost: envOr("JOURNAL_UI_HOST", "127.0.0.1"),
23
+ uiPort: Number.isInteger(uiPort) && uiPort >= 0 && uiPort <= 65535 ? uiPort : 0,
24
+ };
25
+ }
26
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/kernel/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAmBjC,MAAM,0BAA0B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEpD,SAAS,KAAK,CAAC,IAAY,EAAE,QAAgB;IAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,OAAO,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACnD,OAAO;QACL,MAAM,EAAE,KAAK,CAAC,iBAAiB,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;QAC1E,YAAY,EAAE,KAAK,CAAC,uBAAuB,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC;QACrF,gBAAgB,EACd,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,0BAA0B;QAChF,UAAU,EAAE,KAAK,CAAC,qBAAqB,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,SAAS,CAAC,CAAC;QAC/E,iBAAiB,EAAE,KAAK,CAAC,wBAAwB,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;QAC9F,MAAM,EAAE,KAAK,CAAC,iBAAiB,EAAE,WAAW,CAAC;QAC7C,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;KAChF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Kernel context — the small, stable surface that every module is given.
3
+ *
4
+ * Modules depend ONLY on this (not on core internals), so the core can evolve
5
+ * without breaking extensions. This is the basis of the "extensible by design"
6
+ * promise; keep it small.
7
+ */
8
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ import type { JournalStore } from "../db/store.js";
10
+ import type { OriginalStore } from "../originals/store.js";
11
+ import type { JournalConfig } from "./config.js";
12
+ export interface JournalContext {
13
+ /** Register MCP tools (and, later, other capabilities). */
14
+ readonly server: McpServer;
15
+ readonly store: JournalStore;
16
+ readonly originals: OriginalStore;
17
+ readonly config: JournalConfig;
18
+ /** Log to stderr — stdout is reserved for the MCP protocol. */
19
+ readonly log: (...args: unknown[]) => void;
20
+ }
21
+ export declare function createContext(parts: {
22
+ server: McpServer;
23
+ store: JournalStore;
24
+ originals: OriginalStore;
25
+ config: JournalConfig;
26
+ }): JournalContext;
@@ -0,0 +1,7 @@
1
+ export function createContext(parts) {
2
+ return {
3
+ ...parts,
4
+ log: (...args) => console.error("[donguri-journal]", ...args),
5
+ };
6
+ }
7
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/kernel/context.ts"],"names":[],"mappings":"AAsBA,MAAM,UAAU,aAAa,CAAC,KAK7B;IACC,OAAO;QACL,GAAG,KAAK;QACR,GAAG,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,GAAG,IAAI,CAAC;KACzE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Module contract. The core ships as a module, and opt-in features (management
3
+ * UI, album, Eagle connector, …) will plug in through the same interface — later
4
+ * loaded dynamically from installed plugins.
5
+ */
6
+ import type { JournalContext } from "./context.js";
7
+ export interface JournalModule {
8
+ /** Stable id, e.g. "core", "management-ui", "eagle". */
9
+ readonly id: string;
10
+ /** Register the module's tools / capabilities against the kernel context. */
11
+ register(ctx: JournalContext): void | Promise<void>;
12
+ }
13
+ export declare function registerModules(ctx: JournalContext, modules: JournalModule[]): Promise<void>;
@@ -0,0 +1,7 @@
1
+ export async function registerModules(ctx, modules) {
2
+ for (const module of modules) {
3
+ await module.register(ctx);
4
+ ctx.log(`module registered: ${module.id}`);
5
+ }
6
+ }
7
+ //# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/kernel/module.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAmB,EACnB,OAAwB;IAExB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC3B,GAAG,CAAC,GAAG,CAAC,sBAAsB,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC"}
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ import type { JournalContext } from "./context.js";
3
+ import type { JournalModule } from "./module.js";
4
+ export declare const manifestSchema: z.ZodObject<{
5
+ id: z.ZodString;
6
+ name: z.ZodString;
7
+ version: z.ZodString;
8
+ description: z.ZodOptional<z.ZodString>;
9
+ /** Entry file relative to the plugin directory. */
10
+ main: z.ZodDefault<z.ZodString>;
11
+ /** Capabilities the plugin declares it needs (shown for consent). */
12
+ capabilities: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
13
+ /** Kernel API version the plugin targets (for future compatibility checks). */
14
+ kernelApi: z.ZodOptional<z.ZodString>;
15
+ }, "strip", z.ZodTypeAny, {
16
+ id: string;
17
+ name: string;
18
+ version: string;
19
+ main: string;
20
+ capabilities: string[];
21
+ description?: string | undefined;
22
+ kernelApi?: string | undefined;
23
+ }, {
24
+ id: string;
25
+ name: string;
26
+ version: string;
27
+ description?: string | undefined;
28
+ main?: string | undefined;
29
+ capabilities?: string[] | undefined;
30
+ kernelApi?: string | undefined;
31
+ }>;
32
+ export type PluginManifest = z.infer<typeof manifestSchema>;
33
+ declare const pluginConfigSchema: z.ZodObject<{
34
+ plugins: z.ZodDefault<z.ZodArray<z.ZodObject<{
35
+ id: z.ZodString;
36
+ enabled: z.ZodBoolean;
37
+ }, "strip", z.ZodTypeAny, {
38
+ id: string;
39
+ enabled: boolean;
40
+ }, {
41
+ id: string;
42
+ enabled: boolean;
43
+ }>, "many">>;
44
+ }, "strip", z.ZodTypeAny, {
45
+ plugins: {
46
+ id: string;
47
+ enabled: boolean;
48
+ }[];
49
+ }, {
50
+ plugins?: {
51
+ id: string;
52
+ enabled: boolean;
53
+ }[] | undefined;
54
+ }>;
55
+ export type PluginConfig = z.infer<typeof pluginConfigSchema>;
56
+ export declare function loadPluginConfig(path: string): Promise<PluginConfig>;
57
+ export declare function savePluginConfig(path: string, cfg: PluginConfig): Promise<void>;
58
+ /** Read + validate a plugin manifest from a plugin directory. */
59
+ export declare function readManifest(dir: string): Promise<PluginManifest>;
60
+ /** Dynamically import a plugin's module and validate its shape. */
61
+ export declare function loadPluginModule(dir: string, manifest: PluginManifest): Promise<JournalModule>;
62
+ /** Load and register every enabled installed plugin. Never throws — a bad
63
+ * plugin is logged and skipped so the server still starts. */
64
+ export declare function loadInstalledPlugins(ctx: JournalContext): Promise<void>;
65
+ export declare function isValidPluginId(id: string): boolean;
66
+ export declare function pluginDir(ctx: JournalContext, id: string): string;
67
+ export declare function assertAbsoluteDir(source: string): void;
68
+ export {};
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Plugin loading.
3
+ *
4
+ * A plugin is a directory containing a `donguri.plugin.json` manifest and an ESM
5
+ * entry that default-exports a JournalModule. Installed plugins live under
6
+ * `config.pluginsDir/<id>`; which ones are enabled is recorded in
7
+ * `config.pluginsConfigPath`.
8
+ *
9
+ * SECURITY: installing a plugin runs third-party code in-process with the same
10
+ * access as the core. Installation therefore requires explicit owner
11
+ * confirmation (see the install_plugin tool), and manifests declare the
12
+ * capabilities they need. Capability *enforcement* (a restricted context) and
13
+ * process isolation are later hardening steps — today the declared capabilities
14
+ * are surfaced for consent but not yet sandboxed.
15
+ */
16
+ import { existsSync } from "node:fs";
17
+ import { mkdir, readFile, realpath, rename, writeFile } from "node:fs/promises";
18
+ import { isAbsolute, join, resolve, sep } from "node:path";
19
+ import { pathToFileURL } from "node:url";
20
+ import { z } from "zod";
21
+ const PLUGIN_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
22
+ export const manifestSchema = z.object({
23
+ id: z.string().regex(PLUGIN_ID, "id must be a lowercase slug (a-z, 0-9, -)"),
24
+ name: z.string().min(1),
25
+ version: z.string().min(1),
26
+ description: z.string().optional(),
27
+ /** Entry file relative to the plugin directory. */
28
+ main: z.string().default("index.js"),
29
+ /** Capabilities the plugin declares it needs (shown for consent). */
30
+ capabilities: z.array(z.string()).default([]),
31
+ /** Kernel API version the plugin targets (for future compatibility checks). */
32
+ kernelApi: z.string().optional(),
33
+ });
34
+ const pluginConfigSchema = z.object({
35
+ plugins: z.array(z.object({ id: z.string(), enabled: z.boolean() })).default([]),
36
+ });
37
+ export async function loadPluginConfig(path) {
38
+ if (!existsSync(path))
39
+ return { plugins: [] };
40
+ const raw = await readFile(path, "utf8");
41
+ try {
42
+ return pluginConfigSchema.parse(JSON.parse(raw));
43
+ }
44
+ catch (err) {
45
+ // Do NOT silently fall back to empty: a caller that then saves would wipe
46
+ // the enabled-plugins list. Surface it so callers can abort.
47
+ throw new Error(`plugin config at ${path} is unreadable/invalid: ${err instanceof Error ? err.message : String(err)}`);
48
+ }
49
+ }
50
+ export async function savePluginConfig(path, cfg) {
51
+ await mkdir(resolve(path, ".."), { recursive: true });
52
+ // Write atomically (tmp + rename) so an interrupted write can't corrupt it.
53
+ const tmp = `${path}.tmp`;
54
+ await writeFile(tmp, JSON.stringify(cfg, null, 2));
55
+ await rename(tmp, path);
56
+ }
57
+ /**
58
+ * Resolve the entry file and prove it stays inside the plugin directory. Uses
59
+ * realpath so a symlink under the plugin dir can't point `import()` outside, and
60
+ * checks on a path boundary (not a bare prefix) so a sibling like `<dir>-evil`
61
+ * can't pass either. Returns the real (canonical) entry path.
62
+ */
63
+ async function safeEntryPath(dir, main) {
64
+ const base = await realpath(dir);
65
+ let entry;
66
+ try {
67
+ entry = await realpath(resolve(dir, main));
68
+ }
69
+ catch {
70
+ throw new Error("manifest.main does not resolve to a file inside the plugin directory");
71
+ }
72
+ if (entry !== base && !entry.startsWith(base + sep)) {
73
+ throw new Error("manifest.main escapes the plugin directory");
74
+ }
75
+ return entry;
76
+ }
77
+ /** Read + validate a plugin manifest from a plugin directory. */
78
+ export async function readManifest(dir) {
79
+ const manifestPath = join(dir, "donguri.plugin.json");
80
+ if (!existsSync(manifestPath)) {
81
+ throw new Error(`no donguri.plugin.json in ${dir}`);
82
+ }
83
+ const manifest = manifestSchema.parse(JSON.parse(await readFile(manifestPath, "utf8")));
84
+ await safeEntryPath(dir, manifest.main);
85
+ return manifest;
86
+ }
87
+ /** Dynamically import a plugin's module and validate its shape. */
88
+ export async function loadPluginModule(dir, manifest) {
89
+ const entry = await safeEntryPath(dir, manifest.main);
90
+ const imported = (await import(pathToFileURL(entry).href));
91
+ const candidate = imported.default ?? imported.module;
92
+ if (!isJournalModule(candidate)) {
93
+ throw new Error(`plugin ${manifest.id} does not export a JournalModule`);
94
+ }
95
+ return candidate;
96
+ }
97
+ function isJournalModule(value) {
98
+ return (typeof value === "object" &&
99
+ value !== null &&
100
+ typeof value.id === "string" &&
101
+ typeof value.register === "function");
102
+ }
103
+ /** Load and register every enabled installed plugin. Never throws — a bad
104
+ * plugin is logged and skipped so the server still starts. */
105
+ export async function loadInstalledPlugins(ctx) {
106
+ let cfg;
107
+ try {
108
+ cfg = await loadPluginConfig(ctx.config.pluginsConfigPath);
109
+ }
110
+ catch (err) {
111
+ ctx.log("plugin config unreadable; loading no plugins:", err);
112
+ return;
113
+ }
114
+ for (const entry of cfg.plugins) {
115
+ if (!entry.enabled)
116
+ continue;
117
+ if (!PLUGIN_ID.test(entry.id)) {
118
+ ctx.log(`skipping plugin with invalid id: ${entry.id}`);
119
+ continue;
120
+ }
121
+ const dir = join(ctx.config.pluginsDir, entry.id);
122
+ try {
123
+ const manifest = await readManifest(dir);
124
+ const mod = await loadPluginModule(dir, manifest);
125
+ await mod.register(ctx);
126
+ ctx.log(`plugin loaded: ${manifest.id}@${manifest.version}`);
127
+ }
128
+ catch (err) {
129
+ ctx.log(`failed to load plugin ${entry.id}:`, err);
130
+ }
131
+ }
132
+ }
133
+ export function isValidPluginId(id) {
134
+ return PLUGIN_ID.test(id);
135
+ }
136
+ export function pluginDir(ctx, id) {
137
+ return join(ctx.config.pluginsDir, id);
138
+ }
139
+ export function assertAbsoluteDir(source) {
140
+ if (!isAbsolute(source) || !existsSync(source)) {
141
+ throw new Error("source must be an absolute path to an existing plugin directory");
142
+ }
143
+ }
144
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/kernel/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /** Shared MCP tool-result helpers used across modules. */
2
+ export declare function jsonResult(payload: unknown): {
3
+ content: {
4
+ type: "text";
5
+ text: string;
6
+ }[];
7
+ };
8
+ export declare function errorResult(message: string): {
9
+ content: {
10
+ type: "text";
11
+ text: string;
12
+ }[];
13
+ isError: boolean;
14
+ };
@@ -0,0 +1,11 @@
1
+ /** Shared MCP tool-result helpers used across modules. */
2
+ export function jsonResult(payload) {
3
+ return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
4
+ }
5
+ export function errorResult(message) {
6
+ return {
7
+ content: [{ type: "text", text: JSON.stringify({ error: message }, null, 2) }],
8
+ isError: true,
9
+ };
10
+ }
11
+ //# sourceMappingURL=result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/kernel/result.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAE1D,MAAM,UAAU,UAAU,CAAC,OAAgB;IACzC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AAC1F,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;QACvF,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { JournalModule } from "../kernel/module.js";
2
+ export declare const managementModule: JournalModule;
@@ -0,0 +1,39 @@
1
+ import { jsonResult } from "../kernel/result.js";
2
+ import { startManagementUi } from "./server.js";
3
+ export const managementModule = {
4
+ id: "management",
5
+ register(ctx) {
6
+ let running = null;
7
+ let starting = null;
8
+ ctx.server.registerTool("open_management_ui", {
9
+ title: "Open the management console",
10
+ description: "Start (or return the URL of) the local, browser-based management console for the owner " +
11
+ "to inspect their journal directly — outside this LLM conversation. It offers browsing, " +
12
+ "filtering, semantic recall, and storage stats over a localhost-only web page. Use this " +
13
+ "when the user wants to look through their journal themselves, manage it, or see storage " +
14
+ "usage in a UI. The URL contains a session token and binds to localhost only; share it " +
15
+ "with the user so they can open it in their browser. It is NOT for you to fetch data " +
16
+ "from — use query_entries / recall_related / storage_stats for that.",
17
+ inputSchema: {},
18
+ }, async () => {
19
+ if (running) {
20
+ return jsonResult({ url: running.url, port: running.port, already_running: true });
21
+ }
22
+ // Guard against concurrent calls racing two listeners onto the port.
23
+ if (!starting) {
24
+ starting = startManagementUi(ctx)
25
+ .then((ui) => {
26
+ running = ui;
27
+ ctx.log(`management UI listening on ${ctx.config.uiHost}:${ui.port}`);
28
+ return ui;
29
+ })
30
+ .finally(() => {
31
+ starting = null;
32
+ });
33
+ }
34
+ const ui = await starting;
35
+ return jsonResult({ url: ui.url, port: ui.port, already_running: false });
36
+ });
37
+ },
38
+ };
39
+ //# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/management/module.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAqB,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,CAAC,MAAM,gBAAgB,GAAkB;IAC7C,EAAE,EAAE,YAAY;IAChB,QAAQ,CAAC,GAAmB;QAC1B,IAAI,OAAO,GAAwB,IAAI,CAAC;QACxC,IAAI,QAAQ,GAAiC,IAAI,CAAC;QAElD,GAAG,CAAC,MAAM,CAAC,YAAY,CACrB,oBAAoB,EACpB;YACE,KAAK,EAAE,6BAA6B;YACpC,WAAW,EACT,yFAAyF;gBACzF,yFAAyF;gBACzF,yFAAyF;gBACzF,0FAA0F;gBAC1F,wFAAwF;gBACxF,sFAAsF;gBACtF,qEAAqE;YACvE,WAAW,EAAE,EAAE;SAChB,EACD,KAAK,IAAI,EAAE;YACT,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,UAAU,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC;YACrF,CAAC;YACD,qEAAqE;YACrE,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,iBAAiB,CAAC,GAAG,CAAC;qBAC9B,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;oBACX,OAAO,GAAG,EAAE,CAAC;oBACb,GAAG,CAAC,GAAG,CAAC,8BAA8B,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;oBACtE,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC;qBACD,OAAO,CAAC,GAAG,EAAE;oBACZ,QAAQ,GAAG,IAAI,CAAC;gBAClB,CAAC,CAAC,CAAC;YACP,CAAC;YACD,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC;YAC1B,OAAO,UAAU,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5E,CAAC,CACF,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { type Server } from "node:http";
2
+ import type { JournalContext } from "../kernel/context.js";
3
+ export interface ManagementUi {
4
+ url: string;
5
+ port: number;
6
+ token: string;
7
+ close: () => Promise<void>;
8
+ }
9
+ /**
10
+ * Build (but do not start) the management HTTP server. Exposed for tests, which
11
+ * drive it on an ephemeral port. Production goes through startManagementUi.
12
+ */
13
+ export declare function createManagementServer(ctx: JournalContext, token: string): Server;
14
+ /**
15
+ * Start the management UI on config.uiHost:uiPort (an ephemeral port by
16
+ * default). Returns the URL (with the session token) to hand to the owner.
17
+ */
18
+ export declare function startManagementUi(ctx: JournalContext): Promise<ManagementUi>;