negotium 0.1.7 → 0.1.9

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 (270) hide show
  1. package/README.md +26 -2
  2. package/dist/canonical-mcp-bridge.js +14 -0
  3. package/dist/canonical-mcp-bridge.js.map +9 -0
  4. package/dist/chunk-10ct8814.js +429 -0
  5. package/dist/chunk-10ct8814.js.map +11 -0
  6. package/dist/chunk-18fzftjf.js +214 -0
  7. package/dist/chunk-18fzftjf.js.map +10 -0
  8. package/dist/chunk-1k7t7bnx.js +97 -0
  9. package/dist/chunk-1k7t7bnx.js.map +10 -0
  10. package/dist/chunk-1msje2zf.js +23 -0
  11. package/dist/chunk-1msje2zf.js.map +10 -0
  12. package/dist/chunk-2dgkah1z.js +706 -0
  13. package/dist/chunk-2dgkah1z.js.map +14 -0
  14. package/dist/chunk-3t4dtm26.js +61 -0
  15. package/dist/chunk-3t4dtm26.js.map +10 -0
  16. package/dist/chunk-3vn2vnj1.js +51 -0
  17. package/dist/chunk-3vn2vnj1.js.map +10 -0
  18. package/dist/chunk-6hknphtc.js +2054 -0
  19. package/dist/chunk-6hknphtc.js.map +29 -0
  20. package/dist/chunk-6tfzpcvk.js +2149 -0
  21. package/dist/chunk-6tfzpcvk.js.map +15 -0
  22. package/dist/chunk-8g0c4as0.js +23 -0
  23. package/dist/chunk-8g0c4as0.js.map +10 -0
  24. package/dist/chunk-9spx3h9n.js +279 -0
  25. package/dist/chunk-9spx3h9n.js.map +10 -0
  26. package/dist/chunk-c500xe9z.js +33 -0
  27. package/dist/chunk-c500xe9z.js.map +10 -0
  28. package/dist/chunk-e9b2xpff.js +147 -0
  29. package/dist/chunk-e9b2xpff.js.map +10 -0
  30. package/dist/chunk-edb07sjp.js +567 -0
  31. package/dist/chunk-edb07sjp.js.map +14 -0
  32. package/dist/chunk-eexqsee7.js +368 -0
  33. package/dist/chunk-eexqsee7.js.map +11 -0
  34. package/dist/chunk-efyxk97v.js +500 -0
  35. package/dist/chunk-efyxk97v.js.map +13 -0
  36. package/dist/chunk-epqxwkvh.js +3534 -0
  37. package/dist/chunk-epqxwkvh.js.map +20 -0
  38. package/dist/chunk-f1pzh3xh.js +115 -0
  39. package/dist/chunk-f1pzh3xh.js.map +10 -0
  40. package/dist/chunk-f5xgprg1.js +59 -0
  41. package/dist/chunk-f5xgprg1.js.map +10 -0
  42. package/dist/chunk-f97qybwk.js +584 -0
  43. package/dist/chunk-f97qybwk.js.map +11 -0
  44. package/dist/chunk-fvc7ncxd.js +252 -0
  45. package/dist/chunk-fvc7ncxd.js.map +10 -0
  46. package/dist/chunk-g7pvfj5e.js +574 -0
  47. package/dist/chunk-g7pvfj5e.js.map +14 -0
  48. package/dist/chunk-h8vmb9gf.js +315 -0
  49. package/dist/chunk-h8vmb9gf.js.map +10 -0
  50. package/dist/chunk-hgw7n8cm.js +43 -0
  51. package/dist/chunk-hgw7n8cm.js.map +10 -0
  52. package/dist/chunk-hsq7j9jk.js +126 -0
  53. package/dist/chunk-hsq7j9jk.js.map +10 -0
  54. package/dist/chunk-hwy3n31w.js +21 -0
  55. package/dist/chunk-hwy3n31w.js.map +9 -0
  56. package/dist/chunk-j0kjscb7.js +53 -0
  57. package/dist/chunk-j0kjscb7.js.map +10 -0
  58. package/dist/chunk-jzz91jwh.js +70 -0
  59. package/dist/chunk-jzz91jwh.js.map +10 -0
  60. package/dist/chunk-k9z47mab.js +247 -0
  61. package/dist/chunk-k9z47mab.js.map +12 -0
  62. package/dist/chunk-kbm35t7w.js +1331 -0
  63. package/dist/chunk-kbm35t7w.js.map +16 -0
  64. package/dist/chunk-m4veth0c.js +1649 -0
  65. package/dist/chunk-m4veth0c.js.map +17 -0
  66. package/dist/chunk-mhty3nna.js +150 -0
  67. package/dist/chunk-mhty3nna.js.map +11 -0
  68. package/dist/chunk-mmgh670x.js +248 -0
  69. package/dist/chunk-mmgh670x.js.map +11 -0
  70. package/dist/chunk-n0kk5xbq.js +2358 -0
  71. package/dist/chunk-n0kk5xbq.js.map +26 -0
  72. package/dist/chunk-nmv41qx9.js +2847 -0
  73. package/dist/chunk-nmv41qx9.js.map +23 -0
  74. package/dist/chunk-nqdp52aj.js +113 -0
  75. package/dist/chunk-nqdp52aj.js.map +12 -0
  76. package/dist/chunk-ns2m25dz.js +139 -0
  77. package/dist/chunk-ns2m25dz.js.map +10 -0
  78. package/dist/chunk-pkdmypdd.js +147 -0
  79. package/dist/chunk-pkdmypdd.js.map +11 -0
  80. package/dist/chunk-rmyfmrrr.js +1634 -0
  81. package/dist/chunk-rmyfmrrr.js.map +15 -0
  82. package/dist/chunk-sa874j5c.js +9 -0
  83. package/dist/chunk-sa874j5c.js.map +10 -0
  84. package/dist/chunk-sgsz2fpq.js +289 -0
  85. package/dist/chunk-sgsz2fpq.js.map +10 -0
  86. package/dist/chunk-sn51wnf1.js +3020 -0
  87. package/dist/chunk-sn51wnf1.js.map +32 -0
  88. package/dist/chunk-sqhr1a7e.js +587 -0
  89. package/dist/chunk-sqhr1a7e.js.map +11 -0
  90. package/dist/chunk-tmj5r2zv.js +2933 -0
  91. package/dist/chunk-tmj5r2zv.js.map +20 -0
  92. package/dist/chunk-vefj3880.js +56 -0
  93. package/dist/chunk-vefj3880.js.map +10 -0
  94. package/dist/chunk-vp2db42d.js +121 -0
  95. package/dist/chunk-vp2db42d.js.map +10 -0
  96. package/dist/chunk-wry79q55.js +108 -0
  97. package/dist/chunk-wry79q55.js.map +10 -0
  98. package/dist/chunk-x1arr7ze.js +63 -0
  99. package/dist/chunk-x1arr7ze.js.map +10 -0
  100. package/dist/chunk-z2xy5n2q.js +103 -0
  101. package/dist/chunk-z2xy5n2q.js.map +10 -0
  102. package/dist/hosted-agent.js +53 -0
  103. package/dist/hosted-agent.js.map +10 -0
  104. package/dist/main.js +132 -20
  105. package/dist/main.js.map +4 -4
  106. package/dist/runtime/scripts/faster-whisper-wrapper.py +87 -0
  107. package/dist/runtime/scripts/mcp-patchright-http.mjs +349 -0
  108. package/dist/runtime/src/agents/api-topic-agent-switch.ts +178 -0
  109. package/dist/runtime/src/agents/archiver.ts +396 -0
  110. package/dist/runtime/src/agents/auth-check.ts +64 -0
  111. package/dist/runtime/src/agents/claude-provider.ts +737 -0
  112. package/dist/runtime/src/agents/claude-registry.ts +115 -0
  113. package/dist/runtime/src/agents/codex-native-multi-agent.ts +177 -0
  114. package/dist/runtime/src/agents/codex-provider.ts +785 -0
  115. package/dist/runtime/src/agents/codex-registry.ts +115 -0
  116. package/dist/runtime/src/agents/codex-tree-kill.ts +365 -0
  117. package/dist/runtime/src/agents/contracts.ts +69 -0
  118. package/dist/runtime/src/agents/deep-map.ts +13 -0
  119. package/dist/runtime/src/agents/execution-host.ts +91 -0
  120. package/dist/runtime/src/agents/fixtures/claude-attachments.jsonl +2 -0
  121. package/dist/runtime/src/agents/fixtures/codex-shell.jsonl +5 -0
  122. package/dist/runtime/src/agents/fork.ts +65 -0
  123. package/dist/runtime/src/agents/hosted-agent.ts +49 -0
  124. package/dist/runtime/src/agents/idle-archiver.ts +128 -0
  125. package/dist/runtime/src/agents/index.ts +205 -0
  126. package/dist/runtime/src/agents/maestro-provider.ts +206 -0
  127. package/dist/runtime/src/agents/maestro-registry.ts +31 -0
  128. package/dist/runtime/src/agents/mcp-tools/ask-user.ts +280 -0
  129. package/dist/runtime/src/agents/mcp-tools/common.ts +21 -0
  130. package/dist/runtime/src/agents/mcp-tools/self-config.ts +220 -0
  131. package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +525 -0
  132. package/dist/runtime/src/agents/mcp-tools/visuals.ts +88 -0
  133. package/dist/runtime/src/agents/model-catalog.ts +151 -0
  134. package/dist/runtime/src/agents/registry.ts +24 -0
  135. package/dist/runtime/src/agents/rollout/claude.ts +331 -0
  136. package/dist/runtime/src/agents/rollout/codex.ts +569 -0
  137. package/dist/runtime/src/agents/rollout/shared.ts +205 -0
  138. package/dist/runtime/src/agents/self-config-core.ts +415 -0
  139. package/dist/runtime/src/agents/task-events.ts +44 -0
  140. package/dist/runtime/src/agents/tool-format.ts +352 -0
  141. package/dist/runtime/src/agents/topic-agent-switch.ts +193 -0
  142. package/dist/runtime/src/agents/topic-cleanup.ts +231 -0
  143. package/dist/runtime/src/agents/vault-tool-policy.ts +33 -0
  144. package/dist/runtime/src/application/execute-external-user-turn.ts +23 -0
  145. package/dist/runtime/src/application/submit-user-message.ts +50 -0
  146. package/dist/runtime/src/application/switch-topic-effort.ts +48 -0
  147. package/dist/runtime/src/application/switch-topic-model.ts +79 -0
  148. package/dist/runtime/src/application/topic-service.ts +139 -0
  149. package/dist/runtime/src/application/vault-command.ts +170 -0
  150. package/dist/runtime/src/bus.ts +319 -0
  151. package/dist/runtime/src/index.ts +333 -0
  152. package/dist/runtime/src/mcp/agent-health-server.ts +309 -0
  153. package/dist/runtime/src/mcp/background-bash-server.ts +346 -0
  154. package/dist/runtime/src/mcp/canonical-bridge-config.ts +84 -0
  155. package/dist/runtime/src/mcp/canonical-proxy-server.ts +171 -0
  156. package/dist/runtime/src/mcp/dm-ipc.ts +60 -0
  157. package/dist/runtime/src/mcp/mcp-helpers.ts +40 -0
  158. package/dist/runtime/src/mcp/paddleocr-server.ts +105 -0
  159. package/dist/runtime/src/mcp/peer-bridge.ts +114 -0
  160. package/dist/runtime/src/mcp/runtime-spec.ts +137 -0
  161. package/dist/runtime/src/mcp/session-comm/bridge-ipc-config.ts +27 -0
  162. package/dist/runtime/src/mcp/session-comm/peer-forward.ts +165 -0
  163. package/dist/runtime/src/mcp/session-comm/runtime.ts +115 -0
  164. package/dist/runtime/src/mcp/session-comm/server.ts +649 -0
  165. package/dist/runtime/src/mcp/session-comm/topic-config.ts +131 -0
  166. package/dist/runtime/src/mcp/session-comm/topics.ts +229 -0
  167. package/dist/runtime/src/mcp/stdio-protect.ts +84 -0
  168. package/dist/runtime/src/mcp/system-health-server.ts +130 -0
  169. package/dist/runtime/src/mcp/task-server.ts +173 -0
  170. package/dist/runtime/src/mcp/token-stats-server.ts +98 -0
  171. package/dist/runtime/src/mcp/vault-http.ts +219 -0
  172. package/dist/runtime/src/mcp/vault-run.ts +153 -0
  173. package/dist/runtime/src/mcp/vault-server.ts +104 -0
  174. package/dist/runtime/src/mcp/wiki-server.ts +755 -0
  175. package/dist/runtime/src/media/file-events.ts +33 -0
  176. package/dist/runtime/src/media/text-extractor.ts +312 -0
  177. package/dist/runtime/src/media/video.ts +92 -0
  178. package/dist/runtime/src/outbox/file-ops.ts +210 -0
  179. package/dist/runtime/src/outbox/index.ts +8 -0
  180. package/dist/runtime/src/outbox/utils.ts +51 -0
  181. package/dist/runtime/src/platform/background-bash/manager.ts +279 -0
  182. package/dist/runtime/src/platform/config.ts +371 -0
  183. package/dist/runtime/src/platform/constants.ts +18 -0
  184. package/dist/runtime/src/platform/debug-users.ts +14 -0
  185. package/dist/runtime/src/platform/delay.ts +1 -0
  186. package/dist/runtime/src/platform/error.ts +9 -0
  187. package/dist/runtime/src/platform/file-utils.ts +16 -0
  188. package/dist/runtime/src/platform/jsonl.ts +202 -0
  189. package/dist/runtime/src/platform/lifecycle.ts +174 -0
  190. package/dist/runtime/src/platform/logger.ts +31 -0
  191. package/dist/runtime/src/platform/maestro-bootstrap-env.ts +3 -0
  192. package/dist/runtime/src/platform/mcp-config.ts +878 -0
  193. package/dist/runtime/src/platform/modules.ts +145 -0
  194. package/dist/runtime/src/platform/node-plugins.ts +46 -0
  195. package/dist/runtime/src/platform/playwright/manager.ts +1233 -0
  196. package/dist/runtime/src/prompts/agents/wiki-archiver.md +261 -0
  197. package/dist/runtime/src/prompts/builders.ts +368 -0
  198. package/dist/runtime/src/prompts/sessions/channel-system.md +48 -0
  199. package/dist/runtime/src/prompts/sessions/manager-system.md +37 -0
  200. package/dist/runtime/src/prompts/sessions/topic-system.md +47 -0
  201. package/dist/runtime/src/prompts/sessions/visual-design.md +86 -0
  202. package/dist/runtime/src/query/active-rooms.ts +534 -0
  203. package/dist/runtime/src/query/control.ts +313 -0
  204. package/dist/runtime/src/query/index.ts +24 -0
  205. package/dist/runtime/src/query/session-inbox-cleanup.ts +44 -0
  206. package/dist/runtime/src/query/session-inbox-path.ts +42 -0
  207. package/dist/runtime/src/query/state.ts +94 -0
  208. package/dist/runtime/src/query/types.ts +83 -0
  209. package/dist/runtime/src/runtime/ask-callbacks.ts +153 -0
  210. package/dist/runtime/src/runtime/attachments.ts +151 -0
  211. package/dist/runtime/src/runtime/background-sessions.ts +83 -0
  212. package/dist/runtime/src/runtime/channel-context.ts +107 -0
  213. package/dist/runtime/src/runtime/errors.ts +69 -0
  214. package/dist/runtime/src/runtime/event-heartbeat.ts +77 -0
  215. package/dist/runtime/src/runtime/file-hooks.ts +81 -0
  216. package/dist/runtime/src/runtime/footer.ts +23 -0
  217. package/dist/runtime/src/runtime/inbox.ts +946 -0
  218. package/dist/runtime/src/runtime/tasks.ts +56 -0
  219. package/dist/runtime/src/runtime/topic-config.ts +29 -0
  220. package/dist/runtime/src/runtime/turn-runner.ts +2298 -0
  221. package/dist/runtime/src/runtime/usage-alert.ts +84 -0
  222. package/dist/runtime/src/runtime/visual-html.ts +109 -0
  223. package/dist/runtime/src/runtime/visual-store.ts +430 -0
  224. package/dist/runtime/src/runtime/visuals.ts +171 -0
  225. package/dist/runtime/src/security/sanitize.ts +27 -0
  226. package/dist/runtime/src/security/sensitive-path.ts +34 -0
  227. package/dist/runtime/src/storage/activity-log.ts +152 -0
  228. package/dist/runtime/src/storage/api-messages.ts +617 -0
  229. package/dist/runtime/src/storage/api-topic-brief.ts +127 -0
  230. package/dist/runtime/src/storage/api-topic-config.ts +192 -0
  231. package/dist/runtime/src/storage/api-topics.ts +844 -0
  232. package/dist/runtime/src/storage/app-settings.ts +44 -0
  233. package/dist/runtime/src/storage/browser-profiles.ts +133 -0
  234. package/dist/runtime/src/storage/conversations.ts +268 -0
  235. package/dist/runtime/src/storage/forum/agent-settings.ts +20 -0
  236. package/dist/runtime/src/storage/forum/index.ts +4 -0
  237. package/dist/runtime/src/storage/forum/repository.ts +283 -0
  238. package/dist/runtime/src/storage/forum/schema.ts +300 -0
  239. package/dist/runtime/src/storage/forum-db.ts +23 -0
  240. package/dist/runtime/src/storage/runtime-events.ts +130 -0
  241. package/dist/runtime/src/storage/runtime-leases.ts +189 -0
  242. package/dist/runtime/src/storage/runtime-process-leases.ts +216 -0
  243. package/dist/runtime/src/storage/runtime-topic-state.ts +148 -0
  244. package/dist/runtime/src/storage/runtime-turn-requests.ts +301 -0
  245. package/dist/runtime/src/storage/self-schedules.ts +298 -0
  246. package/dist/runtime/src/storage/session-asks.ts +347 -0
  247. package/dist/runtime/src/storage/sqlite.ts +129 -0
  248. package/dist/runtime/src/storage/tasks.ts +179 -0
  249. package/dist/runtime/src/storage/token-stats.ts +144 -0
  250. package/dist/runtime/src/storage/topic-archive-state.ts +59 -0
  251. package/dist/runtime/src/storage/topic-archive.ts +74 -0
  252. package/dist/runtime/src/storage/topic-settings.ts +67 -0
  253. package/dist/runtime/src/storage/topic-transcript.ts +98 -0
  254. package/dist/runtime/src/storage/vault-crypto.ts +65 -0
  255. package/dist/runtime/src/storage/vault.ts +225 -0
  256. package/dist/runtime/src/storage/wiki-summary-names.ts +33 -0
  257. package/dist/runtime/src/storage/wiki.ts +85 -0
  258. package/dist/runtime/src/topics/create.ts +123 -0
  259. package/dist/runtime/src/topics/derive.ts +398 -0
  260. package/dist/runtime/src/topics/lifecycle.ts +311 -0
  261. package/dist/runtime/src/topics/links.ts +7 -0
  262. package/dist/runtime/src/topics/personal-general.ts +65 -0
  263. package/dist/runtime/src/topics/session.ts +381 -0
  264. package/dist/runtime/src/types/api.ts +497 -0
  265. package/dist/runtime/src/types.ts +257 -0
  266. package/dist/runtime/src/version.ts +1 -0
  267. package/dist/runtime/tsconfig.json +13 -0
  268. package/package.json +28 -3
  269. package/dist/main.d.ts +0 -3
  270. package/dist/main.d.ts.map +0 -1
@@ -0,0 +1,755 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
+ import { argv, exit } from "node:process";
6
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+ import {
9
+ CallToolRequestSchema,
10
+ type CallToolResult,
11
+ ListToolsRequestSchema,
12
+ type ListToolsResult,
13
+ type Tool,
14
+ } from "@modelcontextprotocol/sdk/types.js";
15
+ import { SHARED_WIKI_DIR } from "#platform/config";
16
+ import {
17
+ wikiBriefStorageKey,
18
+ wikiSummaryFilename,
19
+ wikiSummarySlug,
20
+ } from "#storage/wiki-summary-names";
21
+
22
+ // --- Configuration --------------------------------------------------------
23
+
24
+ // Otium has one shared filesystem wiki for the workspace. userId/topicId still
25
+ // influence DB keys and filenames, but never the wiki root.
26
+ const WIKI_ROOT = SHARED_WIKI_DIR;
27
+
28
+ // --- CLI parsing -----------------------------------------------------------
29
+
30
+ type WikiSurface = "all" | "wiki" | "skills";
31
+
32
+ function parseArgv(): { userId: string; topicId?: string; surface: WikiSurface } {
33
+ let userId = "default";
34
+ let topicId: string | undefined;
35
+ let surface: WikiSurface = "all";
36
+
37
+ for (let i = 2; i < argv.length; i++) {
38
+ const a = argv[i];
39
+ if (a.startsWith("--user-id=")) userId = a.slice("--user-id=".length);
40
+ else if (a.startsWith("--topic-id=")) topicId = a.slice("--topic-id=".length);
41
+ else if (a === "--surface=wiki") surface = "wiki";
42
+ else if (a === "--surface=skills") surface = "skills";
43
+ }
44
+
45
+ return { userId, topicId, surface };
46
+ }
47
+
48
+ const { topicId, surface } = parseArgv();
49
+
50
+ const WIKI_DIR = WIKI_ROOT;
51
+
52
+ const SKILLS_DIR = resolve(WIKI_DIR, "skills");
53
+ const TOPICS_DIR = resolve(WIKI_DIR, "topic");
54
+ const SUMMARIES_DIR = resolve(WIKI_DIR, "summaries");
55
+ const ARTICLES_DIR = resolve(WIKI_DIR, "articles");
56
+ const ARCHIVE_DIR = resolve(WIKI_DIR, "archive");
57
+
58
+ // --- DB bridge (topicId mode only) ----------------------------------------
59
+ // Imported lazily so the DB connection is only opened when needed AND the
60
+ // import uses the shared @/ path alias that works under both bun and node.
61
+
62
+ let getTopicBrief:
63
+ | ((id: string) => {
64
+ briefMd: string;
65
+ latestSummaryMd?: string;
66
+ summaryDate?: string;
67
+ updatedAt: string;
68
+ } | null)
69
+ | undefined;
70
+ let setTopicBrief:
71
+ | ((id: string, f: { briefMd?: string; latestSummaryMd?: string; summaryDate?: string }) => void)
72
+ | undefined;
73
+
74
+ async function ensureDbBridge() {
75
+ if (!topicId) return;
76
+ if (getTopicBrief) return; // already loaded
77
+ try {
78
+ const mod = await import("#storage/api-topic-brief");
79
+ getTopicBrief = mod.getTopicBrief;
80
+ setTopicBrief = mod.setTopicBrief;
81
+ } catch {
82
+ // DB not available — degrade gracefully (file-only).
83
+ }
84
+ }
85
+
86
+ // --- Helpers ---------------------------------------------------------------
87
+
88
+ function ensureDir(dir: string): void {
89
+ mkdirSync(dir, { recursive: true });
90
+ }
91
+
92
+ function safeExt<T, K extends string = string>(
93
+ obj: Record<string, unknown> | undefined | null,
94
+ keys: readonly K[],
95
+ fallback: T,
96
+ ): T {
97
+ if (!obj || typeof obj !== "object") return fallback;
98
+ for (const key of keys) {
99
+ const value = obj[key];
100
+ if (value !== undefined && value !== null) return value as T;
101
+ }
102
+ return fallback;
103
+ }
104
+
105
+ function topicNameFrom(topicIdStr: string): string {
106
+ return topicIdStr;
107
+ }
108
+
109
+ function slugify(topic: string): string {
110
+ return wikiSummarySlug(topic);
111
+ }
112
+
113
+ // --- Tool handlers (file-based) -------------------------------------------
114
+
115
+ function wikiQuery(args: Record<string, unknown>): CallToolResult {
116
+ const question = safeExt(args, ["question", "query", "q", "text"], "");
117
+ const query = question.toLowerCase();
118
+
119
+ const results: string[] = [];
120
+ const scored: { score: number; path: string; text: string }[] = [];
121
+
122
+ function scan(dir: string, label: string): void {
123
+ ensureDir(dir);
124
+ try {
125
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
126
+ if (entry.isDirectory()) {
127
+ const sub = join(dir, entry.name);
128
+ // Limit depth: only go one level deep for articles/skills
129
+ if (label === "articles" || label === "skills") {
130
+ try {
131
+ for (const f of readdirSync(sub, { withFileTypes: true })) {
132
+ if (!f.isFile() || !f.name.endsWith(".md")) continue;
133
+ const fp = join(sub, f.name);
134
+ try {
135
+ const text = readFileSync(fp, "utf-8");
136
+ scored.push({
137
+ score: scoreMatch(text),
138
+ path: `${label}/${entry.name}/${f.name}`,
139
+ text,
140
+ });
141
+ } catch {
142
+ /* skip unreadable */
143
+ }
144
+ }
145
+ } catch {
146
+ /* skip unreadable dir */
147
+ }
148
+ }
149
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
150
+ const fp = join(dir, entry.name);
151
+ try {
152
+ const text = readFileSync(fp, "utf-8");
153
+ scored.push({ score: scoreMatch(text), path: `${label}/${entry.name}`, text });
154
+ } catch {
155
+ /* skip unreadable */
156
+ }
157
+ }
158
+ }
159
+ } catch {
160
+ /* dir may not exist */
161
+ }
162
+ }
163
+
164
+ function scoreMatch(text: string): number {
165
+ const lower = text.toLowerCase();
166
+ let score = 0;
167
+ const words = query.split(/\s+/).filter(Boolean);
168
+ for (const w of words) {
169
+ if (lower.includes(w)) {
170
+ score += w.length >= 3 ? 3 : 1;
171
+ // Bonus for title match (first line starts with #)
172
+ const firstLine = lower.split("\n")[0];
173
+ if (firstLine.startsWith("#") && firstLine.includes(w)) score += 5;
174
+ }
175
+ }
176
+ return score;
177
+ }
178
+
179
+ scan(ARTICLES_DIR, "articles");
180
+ // Skills are node-local runtime knowledge, not canonical workspace memory.
181
+ // Keep the legacy all-in-one server compatible while ensuring the explicit
182
+ // wiki surface cannot read a node's skill library.
183
+ if (surface === "all") scan(SKILLS_DIR, "skills");
184
+ scan(TOPICS_DIR, "topic");
185
+ scan(SUMMARIES_DIR, "summaries");
186
+
187
+ scored.sort((a, b) => b.score - a.score);
188
+ const top = scored.slice(0, 8);
189
+
190
+ if (top.length === 0) {
191
+ results.push("No matching wiki articles found.");
192
+ } else {
193
+ results.push(`Found ${top.length} matching result(s):\n`);
194
+ for (const { path, text } of top) {
195
+ // Extract title from first heading
196
+ const titleLine = text.split("\n").find((l) => l.startsWith("#"));
197
+ const title = titleLine ? titleLine.replace(/^#+\s*/, "") : basename(path, ".md");
198
+ // Truncate body to 400 chars
199
+ const body = text
200
+ .replace(/^---[\s\S]*?---\n?/, "")
201
+ .replace(/^#.*$/m, "")
202
+ .trim()
203
+ .slice(0, 400);
204
+ results.push(
205
+ `## ${title}\nPath: ${path}\n\n${body}${text.length > 400 ? "\n...(truncated)" : ""}\n`,
206
+ );
207
+ }
208
+ }
209
+
210
+ return { content: [{ type: "text", text: results.join("\n") }] };
211
+ }
212
+
213
+ function wikiTopicBrief(args: Record<string, unknown>): CallToolResult {
214
+ const rawTopic = safeExt<string | undefined>(
215
+ args,
216
+ ["topic", "topicName", "topicId"],
217
+ topicId ?? undefined,
218
+ );
219
+ if (!rawTopic) {
220
+ return {
221
+ content: [{ type: "text", text: "No topic specified (provide --topic-id or topic arg)." }],
222
+ };
223
+ }
224
+
225
+ if (topicId && getTopicBrief) {
226
+ try {
227
+ const brief = getTopicBrief(rawTopic);
228
+ if (!brief) {
229
+ return { content: [{ type: "text", text: `No brief found for topic: ${rawTopic}` }] };
230
+ }
231
+ const lines = [
232
+ `# Topic Brief: ${topicNameFrom(rawTopic)}`,
233
+ "",
234
+ brief.briefMd || "(empty brief)",
235
+ ];
236
+ if (brief.latestSummaryMd) {
237
+ lines.push("", "## Latest Summary", "", brief.latestSummaryMd);
238
+ }
239
+ if (brief.summaryDate) {
240
+ lines.push("", `Summary date: ${brief.summaryDate}`);
241
+ }
242
+ return { content: [{ type: "text", text: lines.join("\n") }] };
243
+ } catch {
244
+ return { content: [{ type: "text", text: `Could not read topic brief for: ${rawTopic}` }] };
245
+ }
246
+ }
247
+
248
+ return { content: [{ type: "text", text: `No topic brief found for: ${rawTopic}` }] };
249
+ }
250
+
251
+ function wikiLastConversation(args: Record<string, unknown>): CallToolResult {
252
+ const rawTopic = safeExt<string | undefined>(
253
+ args,
254
+ ["topic", "topicName", "topicId"],
255
+ topicId ?? undefined,
256
+ );
257
+ const turns = safeExt(args, ["turns", "limit", "n"], 5);
258
+ const maxTurns = Math.min(Math.max(1, Number(turns) || 5), 10);
259
+
260
+ if (!rawTopic) {
261
+ return { content: [{ type: "text", text: "No topic specified." }] };
262
+ }
263
+
264
+ const name = slugify(topicNameFrom(rawTopic));
265
+
266
+ // Try per-topic archive dir first, then flat file
267
+ const archiveDir = resolve(ARCHIVE_DIR, name);
268
+ ensureDir(archiveDir);
269
+
270
+ try {
271
+ // List archive files sorted by name (which should be date-sorted)
272
+ let files = readdirSync(archiveDir)
273
+ .filter((f) => f.endsWith(".jsonl"))
274
+ .sort()
275
+ .reverse();
276
+ if (files.length === 0) {
277
+ // Try flat archive
278
+ ensureDir(ARCHIVE_DIR);
279
+ files = readdirSync(ARCHIVE_DIR)
280
+ .filter((f) => f.startsWith(name) && f.endsWith(".jsonl"))
281
+ .sort()
282
+ .reverse();
283
+ }
284
+ if (files.length === 0) {
285
+ return { content: [{ type: "text", text: `No archive found for topic: ${rawTopic}` }] };
286
+ }
287
+
288
+ // Read most recent archive file
289
+ const actualPath = files[0].includes("/")
290
+ ? files[0]
291
+ : resolve(readdirSync(archiveDir).includes(files[0]) ? archiveDir : ARCHIVE_DIR, files[0]);
292
+
293
+ try {
294
+ const raw = readFileSync(actualPath, "utf-8");
295
+ const lines = raw.trim().split("\n").filter(Boolean);
296
+
297
+ // Reconstruct turns from UnifiedEvent JSONL: pair user_message with following text/result
298
+ const turns: string[] = [];
299
+ let currentUser = "";
300
+ let currentAssistant = "";
301
+
302
+ const truncate = (s: string, max: number) =>
303
+ s.length > max ? `${s.slice(0, max)}\n...(truncated)` : s;
304
+
305
+ const flush = () => {
306
+ if (currentUser || currentAssistant) {
307
+ turns.push(
308
+ `**User**: ${truncate(currentUser || "(no prompt)", 500)}\n\n**Assistant**: ${truncate(currentAssistant || "(no response)", 1000)}`,
309
+ );
310
+ currentUser = "";
311
+ currentAssistant = "";
312
+ }
313
+ };
314
+
315
+ for (const line of lines) {
316
+ try {
317
+ // Archives are written by archiveTopicMessages → TopicArchiveTranscriptRecord:
318
+ // { type: "message", role: "user"|"assistant"|"system", text: "..." }
319
+ const record = JSON.parse(line) as { type: string; role?: string; text?: string };
320
+ if (record.type === "message" && record.role === "user") {
321
+ flush();
322
+ currentUser = record.text ?? "";
323
+ } else if (record.type === "message" && record.role === "assistant") {
324
+ currentAssistant += record.text ?? "";
325
+ }
326
+ } catch {
327
+ // skip malformed lines
328
+ }
329
+ }
330
+ flush();
331
+
332
+ if (turns.length === 0) {
333
+ return { content: [{ type: "text", text: `Archive file found but no valid entries.` }] };
334
+ }
335
+
336
+ const recent = turns.slice(-maxTurns);
337
+ return {
338
+ content: [
339
+ {
340
+ type: "text",
341
+ text: `## Last ${recent.length} turns from "${rawTopic}" (${files[0]})\n\n${recent.join("\n\n---\n\n")}`,
342
+ },
343
+ ],
344
+ };
345
+ } catch {
346
+ return { content: [{ type: "text", text: `Could not read archive for: ${rawTopic}` }] };
347
+ }
348
+ } catch {
349
+ return { content: [{ type: "text", text: `No archive found for: ${rawTopic}` }] };
350
+ }
351
+ }
352
+
353
+ function skillQuery(args: Record<string, unknown>): CallToolResult {
354
+ const question = safeExt(args, ["question", "query", "q"], "");
355
+ const query = question.toLowerCase();
356
+
357
+ ensureDir(SKILLS_DIR);
358
+
359
+ const matches: { name: string; path: string; desc: string }[] = [];
360
+
361
+ try {
362
+ for (const entry of readdirSync(SKILLS_DIR, { withFileTypes: true })) {
363
+ if (!entry.isDirectory()) continue;
364
+ const skillFile = resolve(SKILLS_DIR, entry.name, "skill.md");
365
+ try {
366
+ const text = readFileSync(skillFile, "utf-8");
367
+ const lower = text.toLowerCase();
368
+ let score = 0;
369
+ for (const w of query.split(/\s+/).filter(Boolean)) {
370
+ if (lower.includes(w)) score += w.length >= 3 ? 3 : 1;
371
+ }
372
+ if (score > 0) {
373
+ // Extract name/description from frontmatter
374
+ const fmMatch = text.match(/^---\n([\s\S]*?)\n---/);
375
+ let name = entry.name;
376
+ let desc = "";
377
+ if (fmMatch) {
378
+ const fm = fmMatch[1];
379
+ const nMatch = fm.match(/^name:\s*(.+)$/m);
380
+ const dMatch = fm.match(/^description:\s*(.+)$/m);
381
+ if (nMatch) name = nMatch[1].trim();
382
+ if (dMatch) desc = dMatch[1].trim();
383
+ }
384
+ matches.push({ name, path: `skills/${entry.name}/skill.md`, desc });
385
+ }
386
+ } catch {
387
+ /* skip */
388
+ }
389
+ }
390
+ } catch {
391
+ /* dir may not exist */
392
+ }
393
+
394
+ if (matches.length === 0) {
395
+ return { content: [{ type: "text", text: "No matching skills found." }] };
396
+ }
397
+
398
+ const lines = matches.map((m) => `- **${m.name}** (${m.path})${m.desc ? ` — ${m.desc}` : ""}`);
399
+ return { content: [{ type: "text", text: lines.join("\n") }] };
400
+ }
401
+
402
+ function skillSave(args: Record<string, unknown>): CallToolResult {
403
+ const name = safeExt(args, ["name", "skill_name"], "");
404
+ const content: string = safeExt(args, ["content", "text", "body"], "");
405
+
406
+ if (!name) return { content: [{ type: "text", text: "Missing skill name." }] };
407
+ if (!content) return { content: [{ type: "text", text: "Missing skill content." }] };
408
+
409
+ function extractGotchas(md: string): string[] {
410
+ // No `m` flag: `$` anchors to end-of-string so lazy [\s\S]*? isn't fooled
411
+ // by mid-section newlines the way it would be in multiline mode.
412
+ const m = md.match(/(?:^|\n)## Gotchas\s*\n([\s\S]*?)(?=\n## |$)/);
413
+ if (!m) return [];
414
+ return m[1].split("\n").filter((l) => l.trim().startsWith("-"));
415
+ }
416
+
417
+ function mergeGotchas(target: string, extra: string[]): string {
418
+ const existing = new Set(extractGotchas(target).map((l) => l.toLowerCase()));
419
+ const fresh = extra.filter((l) => !existing.has(l.toLowerCase()));
420
+ if (fresh.length === 0) return target;
421
+ const sectionMatch = target.match(/(?:^|\n)(## Gotchas\s*\n[\s\S]*?)(?=\n## |$)/);
422
+ if (sectionMatch) {
423
+ const block = sectionMatch[1];
424
+ const insert = `${block.trimEnd()}\n${fresh.join("\n")}`;
425
+ return target.replace(block, insert);
426
+ }
427
+ return `${target.trimEnd()}\n\n## Gotchas\n${fresh.join("\n")}\n`;
428
+ }
429
+
430
+ const skillDir = resolve(SKILLS_DIR, name);
431
+ ensureDir(skillDir);
432
+ const skillPath = resolve(skillDir, "skill.md");
433
+
434
+ let finalContent = content;
435
+ try {
436
+ const existing = readFileSync(skillPath, "utf-8");
437
+ finalContent = mergeGotchas(content, extractGotchas(existing));
438
+ } catch {
439
+ // new skill — no existing file to merge from
440
+ }
441
+
442
+ writeFileSync(skillPath, finalContent, "utf-8");
443
+
444
+ return { content: [{ type: "text", text: `Skill "${name}" saved at skills/${name}/skill.md` }] };
445
+ }
446
+
447
+ function saveWikiEntry(args: Record<string, unknown>): CallToolResult {
448
+ const rawTopic = safeExt(args, ["topic", "topicName"], "");
449
+ const content: string = safeExt(args, ["content", "text", "body"], "");
450
+
451
+ if (!rawTopic) return { content: [{ type: "text", text: "Missing topic." }] };
452
+ if (!content) return { content: [{ type: "text", text: "Missing content." }] };
453
+
454
+ const now = new Date();
455
+ const dateStr = now.toISOString().slice(0, 10);
456
+ const summaryName = wikiSummaryFilename(dateStr, rawTopic, topicId);
457
+ const storageKey = wikiBriefStorageKey(rawTopic, topicId);
458
+ const name = slugify(storageKey);
459
+
460
+ // Save to summaries directory
461
+ ensureDir(SUMMARIES_DIR);
462
+ const summaryFile = resolve(SUMMARIES_DIR, summaryName);
463
+ writeFileSync(summaryFile, content, "utf-8");
464
+
465
+ // Also update SQLite brief if in topicId mode
466
+ if (topicId && setTopicBrief) {
467
+ try {
468
+ // Extract a brief from the first paragraph (non-heading)
469
+ const bodyStart = content.indexOf("\n\n");
470
+ const briefParagraph =
471
+ bodyStart > 0
472
+ ? (content.slice(bodyStart).trim().split("\n\n")[0]?.slice(0, 600) ?? "")
473
+ : content.slice(0, 600);
474
+ setTopicBrief(storageKey, {
475
+ latestSummaryMd: content,
476
+ summaryDate: dateStr,
477
+ briefMd: briefParagraph,
478
+ });
479
+ } catch {
480
+ // DB update failed — file save succeeded, not critical.
481
+ }
482
+ }
483
+
484
+ // Also update topic brief file for backward-compat
485
+ ensureDir(TOPICS_DIR);
486
+ const briefPath = resolve(TOPICS_DIR, `${name}.md`);
487
+ try {
488
+ writeFileSync(briefPath, content, "utf-8");
489
+ } catch {
490
+ /* best effort */
491
+ }
492
+
493
+ return {
494
+ content: [
495
+ {
496
+ type: "text",
497
+ text:
498
+ `Saved summary: summaries/${summaryName}` +
499
+ (topicId ? "\nSQLite brief also updated." : ""),
500
+ },
501
+ ],
502
+ };
503
+ }
504
+
505
+ function indexUpsert(args: Record<string, unknown>): CallToolResult {
506
+ const slug = safeExt(args, ["slug", "id"], "");
507
+ const desc = safeExt(args, ["description", "desc", "text"], "");
508
+ const kind: string = safeExt(args, ["kind", "type"], "article");
509
+ const section = safeExt<string | undefined>(args, ["section", "category"], undefined);
510
+ const date = safeExt<string | undefined>(args, ["date", "created"], undefined);
511
+
512
+ if (!slug) return { content: [{ type: "text", text: "Missing slug." }] };
513
+
514
+ const today = new Date().toISOString().slice(0, 10);
515
+ const dateStr = date ?? today;
516
+
517
+ const indexPath = (() => {
518
+ if (kind === "topic") return resolve(WIKI_DIR, "topic-index.md");
519
+ if (kind === "skill") return resolve(WIKI_DIR, "skill-index.md");
520
+ return resolve(WIKI_DIR, "article-index.md");
521
+ })();
522
+
523
+ ensureDir(dirname(indexPath));
524
+
525
+ let index: string;
526
+ try {
527
+ index = readFileSync(indexPath, "utf-8");
528
+ } catch {
529
+ index = "";
530
+ }
531
+
532
+ const lines = index.split("\n");
533
+ const link = (() => {
534
+ switch (kind) {
535
+ case "summary":
536
+ return `- [[summaries/${slug}]] ${desc} (${dateStr})`;
537
+ case "topic":
538
+ return `- [[topic/${slug}]] ${desc} (${dateStr})`;
539
+ case "skill":
540
+ return `- [[skills/${slug}]] ${desc} (${dateStr})`;
541
+ default:
542
+ return `- [[articles/${slug}]] ${desc} (${dateStr})`;
543
+ }
544
+ })();
545
+
546
+ // Find existing entry for this slug and replace, or append
547
+ const slugPattern = `[[${slug}]]`; // loose match on wikilink target
548
+ let replaced = false;
549
+ for (let i = 0; i < lines.length; i++) {
550
+ if (lines[i].includes(slugPattern)) {
551
+ lines[i] = link;
552
+ replaced = true;
553
+ break;
554
+ }
555
+ }
556
+
557
+ if (!replaced) {
558
+ // Append to appropriate section
559
+ if (section) {
560
+ let sectionIdx = -1;
561
+ for (let i = 0; i < lines.length; i++) {
562
+ if (lines[i].startsWith("## ") && lines[i].includes(section)) {
563
+ sectionIdx = i;
564
+ break;
565
+ }
566
+ }
567
+ if (sectionIdx >= 0) {
568
+ // Insert after section heading, before next section
569
+ let insertAt = sectionIdx + 1;
570
+ while (
571
+ insertAt < lines.length &&
572
+ lines[insertAt].trim() !== "" &&
573
+ !lines[insertAt].startsWith("## ")
574
+ ) {
575
+ insertAt++;
576
+ }
577
+ lines.splice(insertAt, 0, link);
578
+ } else {
579
+ lines.push(`\n## ${section}`, link);
580
+ }
581
+ } else {
582
+ lines.push(link);
583
+ }
584
+ }
585
+
586
+ writeFileSync(indexPath, lines.join("\n"), "utf-8");
587
+
588
+ return { content: [{ type: "text", text: `Index updated: ${link}` }] };
589
+ }
590
+
591
+ // --- MCP Tool definitions -------------------------------------------------
592
+
593
+ const WIKI_TOOLS: Tool[] = [
594
+ {
595
+ name: "wiki_query",
596
+ description: "Search the wiki knowledge base and return relevant articles.",
597
+ inputSchema: {
598
+ type: "object",
599
+ properties: {
600
+ question: { type: "string", description: "The question or topic to search for" },
601
+ topic: { type: "string", description: "Optional topic name to narrow the search" },
602
+ },
603
+ required: ["question"],
604
+ },
605
+ },
606
+ {
607
+ name: "wiki_topic_brief",
608
+ description: "Get the lightweight topic brief for a specific topic.",
609
+ inputSchema: {
610
+ type: "object",
611
+ properties: {
612
+ topic: { type: "string", description: "Topic name (e.g. 'dev', 'trading', 'research')" },
613
+ },
614
+ required: ["topic"],
615
+ },
616
+ },
617
+ {
618
+ name: "wiki_last_conversation",
619
+ description: "Read the last N turns from the most recent archived session log for a topic.",
620
+ inputSchema: {
621
+ type: "object",
622
+ properties: {
623
+ topic: { type: "string", description: "Topic name (e.g. 'dev', 'trading')" },
624
+ turns: {
625
+ type: "number",
626
+ description: "Number of recent turns (max 10), default 5",
627
+ minimum: 1,
628
+ maximum: 10,
629
+ },
630
+ },
631
+ required: ["topic"],
632
+ },
633
+ },
634
+ {
635
+ name: "save_wiki_entry",
636
+ description: "Save a session summary directly to wiki/summaries/.",
637
+ inputSchema: {
638
+ type: "object",
639
+ properties: {
640
+ topic: { type: "string", description: "Topic name of the session" },
641
+ content: { type: "string", description: "Session summary in markdown" },
642
+ },
643
+ required: ["topic", "content"],
644
+ },
645
+ },
646
+ {
647
+ name: "index_upsert",
648
+ description: "Upsert an entry in wiki/article-index.md, topic-index.md, or skill-index.md.",
649
+ inputSchema: {
650
+ type: "object",
651
+ properties: {
652
+ slug: { type: "string", description: "Article/summary slug or topic name" },
653
+ description: { type: "string", description: "Single-line description for the index row" },
654
+ kind: {
655
+ type: "string",
656
+ enum: ["article", "summary", "topic", "skill"],
657
+ description: "Which index to update",
658
+ },
659
+ section: { type: "string", description: "For kind='article': H2 section title" },
660
+ date: { type: "string", description: "Override entry date YYYY-MM-DD (default: today)" },
661
+ },
662
+ required: ["slug", "description", "kind"],
663
+ },
664
+ },
665
+ ];
666
+
667
+ const SKILL_TOOLS: Tool[] = [
668
+ {
669
+ name: "skill_query",
670
+ description: "Search this node's local skill library and return matching definitions.",
671
+ inputSchema: {
672
+ type: "object",
673
+ properties: {
674
+ question: {
675
+ type: "string",
676
+ description: "The skill name or description of what you want to do",
677
+ },
678
+ },
679
+ required: ["question"],
680
+ },
681
+ },
682
+ {
683
+ name: "skill_save",
684
+ description: "Create or update a skill on this node.",
685
+ inputSchema: {
686
+ type: "object",
687
+ properties: {
688
+ name: { type: "string", description: "Skill folder name in kebab-case" },
689
+ content: {
690
+ type: "string",
691
+ description: "Skill definition in markdown (with frontmatter name+description)",
692
+ },
693
+ },
694
+ required: ["name", "content"],
695
+ },
696
+ },
697
+ ];
698
+
699
+ const TOOLS =
700
+ surface === "wiki"
701
+ ? WIKI_TOOLS
702
+ : surface === "skills"
703
+ ? SKILL_TOOLS
704
+ : [...WIKI_TOOLS, ...SKILL_TOOLS];
705
+
706
+ // --- Server ----------------------------------------------------------------
707
+
708
+ const server = new Server(
709
+ { name: "wiki-server", version: "2.0.0" },
710
+ { capabilities: { tools: {} } },
711
+ );
712
+
713
+ server.setRequestHandler(ListToolsRequestSchema, async (): Promise<ListToolsResult> => {
714
+ return { tools: TOOLS };
715
+ });
716
+
717
+ server.setRequestHandler(CallToolRequestSchema, async (request): Promise<CallToolResult> => {
718
+ const { name, arguments: args } = request.params;
719
+ const a = (args ?? {}) as Record<string, unknown>;
720
+
721
+ {
722
+ const fn: Record<string, (a: Record<string, unknown>) => CallToolResult> = {
723
+ wiki_query: wikiQuery,
724
+ wiki_topic_brief: wikiTopicBrief,
725
+ wiki_last_conversation: wikiLastConversation,
726
+ skill_query: skillQuery,
727
+ skill_save: skillSave,
728
+ save_wiki_entry: saveWikiEntry,
729
+ index_upsert: indexUpsert,
730
+ };
731
+ const handler = TOOLS.some((tool) => tool.name === name) ? fn[name] : undefined;
732
+ if (handler) return handler(a);
733
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
734
+ }
735
+ });
736
+
737
+ // --- Entrypoint ------------------------------------------------------------
738
+
739
+ async function main() {
740
+ // Initialize DB bridge before accepting connections (topicId mode only)
741
+ await ensureDbBridge();
742
+
743
+ // Ensure directories exist
744
+ for (const d of [SKILLS_DIR, TOPICS_DIR, SUMMARIES_DIR, ARTICLES_DIR, ARCHIVE_DIR]) {
745
+ ensureDir(d);
746
+ }
747
+
748
+ const transport = new StdioServerTransport();
749
+ await server.connect(transport);
750
+ }
751
+
752
+ main().catch((err) => {
753
+ console.error("wiki-server fatal:", err);
754
+ exit(1);
755
+ });