@amoree-code/ocean 0.0.0-stage → 1.2.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 (300) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +284 -2
  3. package/dist/application/auth/auth-orchestrator.js +77 -0
  4. package/dist/application/clients/clients.js +646 -0
  5. package/dist/application/clients/editors.js +398 -0
  6. package/dist/application/clients/hooks.js +335 -0
  7. package/dist/application/clients/native.js +410 -0
  8. package/dist/application/clients/opencode-plugin.js +158 -0
  9. package/dist/application/clients/registry.js +188 -0
  10. package/dist/application/gateway/webhook-gateway.js +137 -0
  11. package/dist/application/install/provider-installer.js +184 -0
  12. package/dist/application/integrations/charter-copies.js +76 -0
  13. package/dist/application/integrations/claude-vscode.js +106 -0
  14. package/dist/application/integrations/engine-entry-repair.js +105 -0
  15. package/dist/application/integrations/skill-filter.js +254 -0
  16. package/dist/application/layout/layout-apply.js +443 -0
  17. package/dist/application/layout/layout-plan.js +236 -0
  18. package/dist/application/layout/workspace-template.js +34 -0
  19. package/dist/application/onboarding/state.js +23 -0
  20. package/dist/application/onboarding/steps.js +249 -0
  21. package/dist/application/profiles/profile-distribution.js +53 -0
  22. package/dist/application/profiles/profile-suggestion.js +39 -0
  23. package/dist/application/profiles/profile-upgrade-service.js +208 -0
  24. package/dist/application/pull/history-index.js +476 -0
  25. package/dist/application/pull/project-match.js +132 -0
  26. package/dist/application/pull/pull.js +202 -0
  27. package/dist/application/pull/server-capture.js +307 -0
  28. package/dist/application/scheduler/local-scheduler.js +210 -0
  29. package/dist/composition/runtime.js +64 -0
  30. package/dist/engine-root.js +9 -0
  31. package/dist/infrastructure/filesystem/profile-loader.js +46 -0
  32. package/dist/infrastructure/filesystem/runtime-config-loader.js +29 -0
  33. package/dist/infrastructure/filesystem/skill-loader.js +84 -0
  34. package/dist/infrastructure/filesystem/workspace-capability.js +37 -0
  35. package/dist/infrastructure/observability/runtime-logger.js +16 -0
  36. package/dist/infrastructure/process/agent-tool-executor.js +99 -0
  37. package/dist/infrastructure/process/cli-process.js +49 -0
  38. package/dist/infrastructure/process/interactive-process.js +145 -0
  39. package/dist/infrastructure/process/process-supervisor.js +194 -0
  40. package/dist/infrastructure/process/service.js +252 -0
  41. package/dist/infrastructure/providers/browser-provider.js +2 -0
  42. package/dist/infrastructure/providers/claude-sdk.js +137 -0
  43. package/dist/infrastructure/providers/client-home.js +14 -0
  44. package/dist/infrastructure/providers/fake-browser-provider.js +146 -0
  45. package/dist/infrastructure/providers/ollama-embedder.js +74 -0
  46. package/dist/infrastructure/providers/openai-compatible.js +268 -0
  47. package/dist/infrastructure/providers/playwright-browser-provider.js +380 -0
  48. package/dist/infrastructure/providers/provider-capabilities.js +50 -0
  49. package/dist/infrastructure/providers/provider-invocation.js +184 -0
  50. package/dist/infrastructure/providers/provider-registry.js +77 -0
  51. package/dist/infrastructure/providers/providers.js +134 -0
  52. package/dist/infrastructure/wrappers/wrapper-manager.js +374 -0
  53. package/dist/interfaces/cli/auth-command.js +54 -0
  54. package/dist/interfaces/cli/browser-bridge-command.js +93 -0
  55. package/dist/interfaces/cli/browser-command.js +119 -0
  56. package/dist/interfaces/cli/capture-command.js +125 -0
  57. package/dist/interfaces/cli/client-test-command.js +133 -0
  58. package/dist/interfaces/cli/clients-command.js +129 -0
  59. package/dist/interfaces/cli/command-registry.js +1211 -0
  60. package/dist/interfaces/cli/context-command.js +172 -0
  61. package/dist/interfaces/cli/daily-command.js +131 -0
  62. package/dist/interfaces/cli/governance-command.js +55 -0
  63. package/dist/interfaces/cli/handoff-command.js +35 -0
  64. package/dist/interfaces/cli/help-command.js +207 -0
  65. package/dist/interfaces/cli/idea-command.js +47 -0
  66. package/dist/interfaces/cli/intercept-command.js +317 -0
  67. package/dist/interfaces/cli/layout-command.js +27 -0
  68. package/dist/interfaces/cli/market-command.js +52 -0
  69. package/dist/interfaces/cli/mcp-gateway-command.js +30 -0
  70. package/dist/interfaces/cli/memory-command.js +317 -0
  71. package/dist/interfaces/cli/migrate-command.js +26 -0
  72. package/dist/interfaces/cli/observe-command.js +78 -0
  73. package/dist/interfaces/cli/onboard-command.js +123 -0
  74. package/dist/interfaces/cli/operate-command.js +59 -0
  75. package/dist/interfaces/cli/profiles-command.js +70 -0
  76. package/dist/interfaces/cli/pull-command.js +54 -0
  77. package/dist/interfaces/cli/setup-command.js +143 -0
  78. package/dist/interfaces/cli/tasks-command.js +249 -0
  79. package/dist/main.js +49 -0
  80. package/dist/node-warnings.js +17 -0
  81. package/dist/version.js +8 -0
  82. package/market/mcp/chrome-devtools/ocean.capability.json +28 -0
  83. package/market/mcp/gateway/ocean.capability.json +21 -0
  84. package/market/mcp/obsidian/ocean.capability.json +21 -0
  85. package/market/mcp/ocean/ocean.capability.json +21 -0
  86. package/market/mcp/playwright/ocean.capability.json +23 -0
  87. package/node_modules/@ocean/brain/dist/brain/brain-eval.js +29 -0
  88. package/node_modules/@ocean/brain/dist/brain/brain-markdown.js +226 -0
  89. package/node_modules/@ocean/brain/dist/brain/brain-reindex.js +247 -0
  90. package/node_modules/@ocean/brain/dist/brain/brain-service.js +320 -0
  91. package/node_modules/@ocean/brain/dist/capture/inbox-sync.js +22 -0
  92. package/node_modules/@ocean/brain/dist/evidence/verification-report.js +13 -0
  93. package/node_modules/@ocean/brain/dist/infrastructure/brain/brain-index.js +285 -0
  94. package/node_modules/@ocean/brain/dist/memory/brain-dump.js +145 -0
  95. package/node_modules/@ocean/brain/dist/memory/daily-narrative.js +142 -0
  96. package/node_modules/@ocean/brain/dist/memory/index-sync.js +124 -0
  97. package/node_modules/@ocean/brain/dist/memory/lesson-activation.js +93 -0
  98. package/node_modules/@ocean/brain/dist/memory/lesson-learning.js +188 -0
  99. package/node_modules/@ocean/brain/dist/memory/lessons.js +321 -0
  100. package/node_modules/@ocean/brain/dist/memory/memory-audit.js +100 -0
  101. package/node_modules/@ocean/brain/dist/memory/memory-digest.js +49 -0
  102. package/node_modules/@ocean/brain/dist/memory/memory-harvest.js +345 -0
  103. package/node_modules/@ocean/brain/dist/memory/memory-paths.js +174 -0
  104. package/node_modules/@ocean/brain/dist/memory/memory-review.js +278 -0
  105. package/node_modules/@ocean/brain/dist/memory/memory-trash.js +152 -0
  106. package/node_modules/@ocean/brain/dist/memory/memory-write.js +216 -0
  107. package/node_modules/@ocean/brain/dist/memory/model-narrative.js +129 -0
  108. package/node_modules/@ocean/brain/dist/memory/profile-facts.js +103 -0
  109. package/node_modules/@ocean/brain/dist/memory/session-promotion.js +61 -0
  110. package/node_modules/@ocean/brain/dist/memory/session-summary.js +113 -0
  111. package/node_modules/@ocean/brain/package.json +11 -0
  112. package/node_modules/@ocean/context/dist/context/context-compression.js +66 -0
  113. package/node_modules/@ocean/context/dist/context/context-cost.js +587 -0
  114. package/node_modules/@ocean/context/dist/context/context-ladder.js +363 -0
  115. package/node_modules/@ocean/context/dist/context/context-packet.js +213 -0
  116. package/node_modules/@ocean/context/dist/context/context-references.js +255 -0
  117. package/node_modules/@ocean/context/dist/context/intent-router.js +259 -0
  118. package/node_modules/@ocean/context/dist/context/resource-injection.js +36 -0
  119. package/node_modules/@ocean/context/package.json +11 -0
  120. package/node_modules/@ocean/core/dist/domain/brain/brain-record.js +22 -0
  121. package/node_modules/@ocean/core/dist/domain/brain/record-scope.js +22 -0
  122. package/node_modules/@ocean/core/dist/domain/capabilities/browser-contract.js +64 -0
  123. package/node_modules/@ocean/core/dist/domain/capabilities/capability-contract.js +16 -0
  124. package/node_modules/@ocean/core/dist/domain/capabilities/capability-manifest.js +72 -0
  125. package/node_modules/@ocean/core/dist/domain/context/context-validator.js +5 -0
  126. package/node_modules/@ocean/core/dist/domain/context/context.js +34 -0
  127. package/node_modules/@ocean/core/dist/domain/context/project-id.js +30 -0
  128. package/node_modules/@ocean/core/dist/domain/evidence/evidence.js +16 -0
  129. package/node_modules/@ocean/core/dist/domain/mcp/mcp-contract.js +36 -0
  130. package/node_modules/@ocean/core/dist/domain/mcp/memory-contract.js +41 -0
  131. package/node_modules/@ocean/core/dist/domain/ports/brain-index-port.js +7 -0
  132. package/node_modules/@ocean/core/dist/domain/ports/browser-port.js +8 -0
  133. package/node_modules/@ocean/core/dist/domain/ports/embedder-port.js +2 -0
  134. package/node_modules/@ocean/core/dist/domain/ports/platform-ports.js +2 -0
  135. package/node_modules/@ocean/core/dist/domain/ports/runtime-ports.js +2 -0
  136. package/node_modules/@ocean/core/dist/domain/ports/session-store-port.js +2 -0
  137. package/node_modules/@ocean/core/dist/domain/process/process-events.js +5 -0
  138. package/node_modules/@ocean/core/dist/domain/profiles/profile-distribution.js +19 -0
  139. package/node_modules/@ocean/core/dist/domain/profiles/profile-policy.js +34 -0
  140. package/node_modules/@ocean/core/dist/domain/profiles/profile-selection.js +73 -0
  141. package/node_modules/@ocean/core/dist/domain/profiles/profile-upgrade.js +123 -0
  142. package/node_modules/@ocean/core/dist/domain/profiles/profile-validator.js +5 -0
  143. package/node_modules/@ocean/core/dist/domain/profiles/profile.js +218 -0
  144. package/node_modules/@ocean/core/dist/domain/profiles/runtime-config.js +30 -0
  145. package/node_modules/@ocean/core/dist/domain/providers/provider-capability.js +28 -0
  146. package/node_modules/@ocean/core/dist/domain/providers/provider.js +2 -0
  147. package/node_modules/@ocean/core/dist/domain/redaction/redaction.js +86 -0
  148. package/node_modules/@ocean/core/dist/domain/runs/agent-tools.js +92 -0
  149. package/node_modules/@ocean/core/dist/domain/runs/execution-plan.js +71 -0
  150. package/node_modules/@ocean/core/dist/domain/runs/run-contract.js +38 -0
  151. package/node_modules/@ocean/core/dist/domain/sessions/entry-contract.js +31 -0
  152. package/node_modules/@ocean/core/dist/domain/sessions/handoff.js +78 -0
  153. package/node_modules/@ocean/core/dist/domain/sessions/session.js +58 -0
  154. package/node_modules/@ocean/core/dist/domain/skills/default-skills.js +29 -0
  155. package/node_modules/@ocean/core/dist/domain/skills/skill-validator.js +5 -0
  156. package/node_modules/@ocean/core/dist/domain/skills/skill.js +11 -0
  157. package/node_modules/@ocean/core/dist/domain/tasks/task-id.js +85 -0
  158. package/node_modules/@ocean/core/dist/shared/fs-utils.js +212 -0
  159. package/node_modules/@ocean/core/dist/shared/local-date.js +16 -0
  160. package/node_modules/@ocean/core/dist/shared/paths.js +324 -0
  161. package/node_modules/@ocean/core/dist/shared/project-resolution.js +1065 -0
  162. package/node_modules/@ocean/core/dist/shared/task-reference.js +133 -0
  163. package/node_modules/@ocean/core/package.json +14 -0
  164. package/node_modules/@ocean/loop/dist/handoff/handoff-service.js +133 -0
  165. package/node_modules/@ocean/loop/dist/hooks/lifecycle-hooks.js +15 -0
  166. package/node_modules/@ocean/loop/dist/hooks/session-end-hook.js +183 -0
  167. package/node_modules/@ocean/loop/dist/hooks/session-start-hook.js +237 -0
  168. package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-store.js +690 -0
  169. package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-validator.js +5 -0
  170. package/node_modules/@ocean/loop/dist/loops/iteration-graph.js +147 -0
  171. package/node_modules/@ocean/loop/dist/loops/task-loop-record.js +2 -0
  172. package/node_modules/@ocean/loop/dist/loops/task-loop.js +281 -0
  173. package/node_modules/@ocean/loop/dist/operations/memory-knowledge-operations.js +263 -0
  174. package/node_modules/@ocean/loop/dist/operations/operation-contract.js +227 -0
  175. package/node_modules/@ocean/loop/dist/operations/operation-scope.js +28 -0
  176. package/node_modules/@ocean/loop/dist/operations/project-operations.js +93 -0
  177. package/node_modules/@ocean/loop/dist/operations/record-io.js +65 -0
  178. package/node_modules/@ocean/loop/dist/operations/record-operations.js +45 -0
  179. package/node_modules/@ocean/loop/dist/operations/task-linker.js +40 -0
  180. package/node_modules/@ocean/loop/dist/operations/task-operations.js +279 -0
  181. package/node_modules/@ocean/loop/dist/operations/write-guard.js +157 -0
  182. package/node_modules/@ocean/loop/dist/runs/prompt-assembly.js +112 -0
  183. package/node_modules/@ocean/loop/dist/runs/run-agent.js +425 -0
  184. package/node_modules/@ocean/loop/dist/runs/run-authorization.js +24 -0
  185. package/node_modules/@ocean/loop/dist/runs/tool-loop.js +173 -0
  186. package/node_modules/@ocean/loop/dist/sessions/session-closeout.js +150 -0
  187. package/node_modules/@ocean/loop/dist/sessions/session-pointer.js +133 -0
  188. package/node_modules/@ocean/loop/dist/sessions/session-projects.js +26 -0
  189. package/node_modules/@ocean/loop/dist/sessions/session-retention.js +132 -0
  190. package/node_modules/@ocean/loop/dist/tasks/archive-tasks.js +210 -0
  191. package/node_modules/@ocean/loop/dist/tasks/checkpoint-task.js +31 -0
  192. package/node_modules/@ocean/loop/dist/tasks/claim-task.js +126 -0
  193. package/node_modules/@ocean/loop/dist/tasks/list-tasks.js +113 -0
  194. package/node_modules/@ocean/loop/dist/tasks/new-task.js +42 -0
  195. package/node_modules/@ocean/loop/dist/tasks/renumber-tasks.js +134 -0
  196. package/node_modules/@ocean/loop/dist/tasks/verification-readiness.js +78 -0
  197. package/node_modules/@ocean/loop/dist/tasks/verify-task.js +234 -0
  198. package/node_modules/@ocean/loop/package.json +11 -0
  199. package/node_modules/@ocean/market/dist/adapters/mcp-adapter.js +64 -0
  200. package/node_modules/@ocean/market/dist/adapters/owned-directory.js +89 -0
  201. package/node_modules/@ocean/market/dist/adapters/plugin-adapter.js +39 -0
  202. package/node_modules/@ocean/market/dist/adapters/skill-adapter.js +32 -0
  203. package/node_modules/@ocean/market/dist/capability/adapter.js +2 -0
  204. package/node_modules/@ocean/market/dist/capability/catalog.js +63 -0
  205. package/node_modules/@ocean/market/dist/capability/content-digest.js +40 -0
  206. package/node_modules/@ocean/market/dist/capability/registry.js +81 -0
  207. package/node_modules/@ocean/market/dist/install/default-marketplace.js +47 -0
  208. package/node_modules/@ocean/market/dist/install/lock.js +34 -0
  209. package/node_modules/@ocean/market/dist/install/marketplace-service.js +165 -0
  210. package/node_modules/@ocean/market/dist/mcp/browser-bridge.js +263 -0
  211. package/node_modules/@ocean/market/dist/mcp/mcp-connection.js +67 -0
  212. package/node_modules/@ocean/market/dist/mcp/mcp-gateway.js +317 -0
  213. package/node_modules/@ocean/market/dist/mcp/mcp-launch.js +102 -0
  214. package/node_modules/@ocean/market/dist/mcp/mcp-runner.js +32 -0
  215. package/node_modules/@ocean/market/dist/security/description-scan.js +45 -0
  216. package/node_modules/@ocean/market/dist/security/permissions.js +57 -0
  217. package/node_modules/@ocean/market/dist/skills/core-skill-sync.js +113 -0
  218. package/node_modules/@ocean/market/dist/skills/signal-detection.js +84 -0
  219. package/node_modules/@ocean/market/dist/skills/skill-hub.js +220 -0
  220. package/node_modules/@ocean/market/package.json +11 -0
  221. package/node_modules/@ocean/services/dist/browser/browser-service.js +124 -0
  222. package/node_modules/@ocean/services/dist/browser/browser-session.js +287 -0
  223. package/node_modules/@ocean/services/dist/browser/browser-task-runner.js +70 -0
  224. package/node_modules/@ocean/services/dist/doctor/workspace-doctor.js +547 -0
  225. package/node_modules/@ocean/services/dist/infrastructure/mcp/call-ledger.js +114 -0
  226. package/node_modules/@ocean/services/dist/infrastructure/mcp/mcp-client.js +162 -0
  227. package/node_modules/@ocean/services/dist/infrastructure/mcp/obsidian-server.js +251 -0
  228. package/node_modules/@ocean/services/dist/infrastructure/mcp/ocean-server.js +880 -0
  229. package/node_modules/@ocean/services/dist/infrastructure/mcp/server-version.js +20 -0
  230. package/node_modules/@ocean/services/dist/infrastructure/mcp/tool-registry.js +29 -0
  231. package/node_modules/@ocean/services/dist/obsidian/conflict-log.js +68 -0
  232. package/node_modules/@ocean/services/dist/obsidian/inbox-promotion.js +80 -0
  233. package/node_modules/@ocean/services/dist/obsidian/vault-discovery.js +113 -0
  234. package/node_modules/@ocean/services/dist/obsidian/vault-ingestion.js +77 -0
  235. package/node_modules/@ocean/services/dist/obsidian/vault-sync.js +73 -0
  236. package/node_modules/@ocean/services/dist/obsidian/vault-writer.js +75 -0
  237. package/node_modules/@ocean/services/package.json +11 -0
  238. package/ocean-marketplace.json +415 -0
  239. package/package.json +67 -4
  240. package/skills/core/business-logic/SKILL.md +22 -0
  241. package/skills/core/catch-up/SKILL.md +17 -0
  242. package/skills/core/core-thinking/SKILL.md +11 -0
  243. package/skills/core/design-thinking/SKILL.md +19 -0
  244. package/skills/core/graft/SKILL.md +156 -0
  245. package/skills/core/loop/SKILL.md +20 -0
  246. package/skills/core/session-handoff/SKILL.md +15 -0
  247. package/skills/core/use-browser/SKILL.md +69 -0
  248. package/skills/core/verification/SKILL.md +11 -0
  249. package/skills/index.json +56 -0
  250. package/templates/README.md +4 -0
  251. package/templates/profiles/default.json +51 -0
  252. package/templates/profiles/developer.json +51 -0
  253. package/templates/profiles/reviewer.json +51 -0
  254. package/templates/profiles/strategist.json +51 -0
  255. package/templates/workspace/00-inbox/INBOX.md +7 -0
  256. package/templates/workspace/00-inbox/candidates/.gitkeep +0 -0
  257. package/templates/workspace/00-inbox/native/.gitkeep +0 -0
  258. package/templates/workspace/01-daily/.gitkeep +0 -0
  259. package/templates/workspace/02-personal/CONFLICTS.md +7 -0
  260. package/templates/workspace/02-personal/MEMORY.md +15 -0
  261. package/templates/workspace/02-personal/goals/.gitkeep +0 -0
  262. package/templates/workspace/02-personal/identity/.gitkeep +0 -0
  263. package/templates/workspace/02-personal/preferences/.gitkeep +0 -0
  264. package/templates/workspace/03-professional/.gitkeep +0 -0
  265. package/templates/workspace/04-projects/PROJECTS.md +5 -0
  266. package/templates/workspace/04-projects/_template/docs/.gitkeep +0 -0
  267. package/templates/workspace/04-projects/_template/memory/.gitkeep +0 -0
  268. package/templates/workspace/04-projects/_template/tasks/.gitkeep +0 -0
  269. package/templates/workspace/05-knowledge/KNOWLEDGE.md +8 -0
  270. package/templates/workspace/05-knowledge/decisions/.gitkeep +0 -0
  271. package/templates/workspace/05-knowledge/lessons/.gitkeep +0 -0
  272. package/templates/workspace/06-templates/TEMPLATES.md +25 -0
  273. package/templates/workspace/06-templates/decision.md +8 -0
  274. package/templates/workspace/06-templates/intelligence.md +32 -0
  275. package/templates/workspace/06-templates/project-claude.md +48 -0
  276. package/templates/workspace/06-templates/session.md +38 -0
  277. package/templates/workspace/06-templates/task.md +42 -0
  278. package/templates/workspace/MANIFEST.json +51 -0
  279. package/templates/workspace/bridge/config/CONFIG.md +21 -0
  280. package/templates/workspace/bridge/config/runtime.json +4 -0
  281. package/templates/workspace/charter/core.md +104 -0
  282. package/templates/workspace/charter/policies/README.md +31 -0
  283. package/templates/workspace/charter/policies/claude-code-context.md +90 -0
  284. package/templates/workspace/charter/policies/concise-output.md +29 -0
  285. package/templates/workspace/charter/policies/context.md +91 -0
  286. package/templates/workspace/charter/policies/contracts/task.md +189 -0
  287. package/templates/workspace/charter/policies/git.md +60 -0
  288. package/templates/workspace/charter/policies/graph.md +47 -0
  289. package/templates/workspace/charter/policies/knowledge.md +33 -0
  290. package/templates/workspace/charter/policies/lifecycle.md +119 -0
  291. package/templates/workspace/charter/policies/memory.md +67 -0
  292. package/templates/workspace/charter/policies/minimal-change.md +43 -0
  293. package/templates/workspace/charter/policies/models.md +57 -0
  294. package/templates/workspace/charter/policies/privacy-terms.txt +3 -0
  295. package/templates/workspace/charter/policies/response.md +39 -0
  296. package/templates/workspace/charter/policies/skill-observer.md +44 -0
  297. package/templates/workspace/charter/policies/stack.md +23 -0
  298. package/templates/workspace/charter/policies/task.md +139 -0
  299. package/templates/workspace/charter/policies/verification.md +29 -0
  300. package/templates/workspace/sessions/.gitkeep +0 -0
@@ -0,0 +1,20 @@
1
+ import { readFile } from "node:fs/promises";
2
+ // The version the MCP servers report, read from this package's manifest (T-307: it was
3
+ // hard-coded and drifted from the release).
4
+ let cached;
5
+ export async function serverVersion() {
6
+ if (cached)
7
+ return cached;
8
+ try {
9
+ const manifest = JSON.parse(await readFile(new URL("../../../package.json", import.meta.url), "utf8"));
10
+ cached =
11
+ typeof manifest.version === "string" && manifest.version.trim()
12
+ ? manifest.version
13
+ : "unknown";
14
+ }
15
+ catch {
16
+ cached = "unknown";
17
+ }
18
+ return cached;
19
+ }
20
+ //# sourceMappingURL=server-version.js.map
@@ -0,0 +1,29 @@
1
+ import { mcpToolSchema, } from "@ocean/core/domain/mcp/mcp-contract.js";
2
+ // The tools an MCP server exposes, validated once at registration. `tools/list` and the
3
+ // `tools/call` name check both read from here, so a tool is added by registering it — there is
4
+ // no second list to keep in step.
5
+ export class McpToolRegistry {
6
+ #tools = new Map();
7
+ constructor(tools = []) {
8
+ for (const tool of tools)
9
+ this.register(tool);
10
+ }
11
+ register(tool) {
12
+ const parsed = mcpToolSchema.safeParse(tool);
13
+ if (!parsed.success)
14
+ throw new Error(`Invalid MCP tool ${String(tool.name)}: ${parsed.error.issues
15
+ .map((issue) => `${issue.path.map(String).join(".")}: ${issue.message}`)
16
+ .join("; ")}`);
17
+ if (this.#tools.has(tool.name))
18
+ throw new Error(`MCP tool ${tool.name} is already registered`);
19
+ this.#tools.set(tool.name, tool);
20
+ }
21
+ has(name) {
22
+ return this.#tools.has(name);
23
+ }
24
+ // Registration order, which is the order clients see.
25
+ list() {
26
+ return [...this.#tools.values()];
27
+ }
28
+ }
29
+ //# sourceMappingURL=tool-registry.js.map
@@ -0,0 +1,68 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { oceanPath, SYSTEM_DIR } from "@ocean/core/shared/paths.js";
5
+ export function parseConflictSide(value) {
6
+ if (value === "vault")
7
+ return "vault";
8
+ if (value === "ocean")
9
+ return "ocean";
10
+ return null;
11
+ }
12
+ function conflictsDirectory(root = oceanPath(SYSTEM_DIR, "integrations", "obsidian", "conflicts")) {
13
+ return root;
14
+ }
15
+ function digest(content) {
16
+ return content === undefined || content === null
17
+ ? null
18
+ : createHash("sha256").update(content).digest("hex");
19
+ }
20
+ export async function appendConflict(input, directory = conflictsDirectory()) {
21
+ await mkdir(directory, { recursive: true });
22
+ const id = `${new Date().toISOString().replaceAll(/[:.]/g, "-")}-${randomUUID()}`;
23
+ const record = {
24
+ version: 1,
25
+ id,
26
+ ...input,
27
+ createdAt: new Date().toISOString(),
28
+ };
29
+ const file = path.join(directory, `${id}.json`);
30
+ await writeFile(file, `${JSON.stringify(record, null, 2)}\n`, {
31
+ encoding: "utf8",
32
+ mode: 0o600,
33
+ });
34
+ return file;
35
+ }
36
+ export function readConflictRecord(raw) {
37
+ const record = raw;
38
+ return { ...record, oceanSha256: record.oceanSha256 ?? null };
39
+ }
40
+ function changedFromBaseline(record, side) {
41
+ const hash = side === "vault" ? record.vaultSha256 : record.oceanSha256;
42
+ return hash !== record.baselineSha256;
43
+ }
44
+ export async function resolveConflict(id, requestedKeep, options = {}) {
45
+ const keep = parseConflictSide(requestedKeep);
46
+ if (!keep)
47
+ throw new Error("Conflict side must be vault or ocean");
48
+ if (!id || path.basename(id) !== id)
49
+ throw new Error("Conflict id must be a conflict filename");
50
+ const directory = options.directory ?? conflictsDirectory();
51
+ const filename = id.endsWith(".json") ? id : `${id}.json`;
52
+ const source = path.join(directory, filename);
53
+ const record = readConflictRecord(JSON.parse(await readFile(source, "utf8")));
54
+ const vaultChanged = changedFromBaseline(record, "vault");
55
+ const oceanChanged = changedFromBaseline(record, "ocean");
56
+ const status = vaultChanged !== oceanChanged ? "auto-resolved" : "manual-required";
57
+ if (status === "auto-resolved" && options.apply)
58
+ await options.apply(record, keep);
59
+ const archiveDirectory = path.join(directory, "resolved");
60
+ await mkdir(archiveDirectory, { recursive: true });
61
+ const archived = path.join(archiveDirectory, id);
62
+ await rename(source, archived);
63
+ return { id: filename, keep, status, archived };
64
+ }
65
+ export function contentHash(content) {
66
+ return digest(content);
67
+ }
68
+ //# sourceMappingURL=conflict-log.js.map
@@ -0,0 +1,80 @@
1
+ import { access, mkdir, rename } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { discoverObsidianVault, } from "./vault-discovery.js";
4
+ const roots = ["01-Projects", "02-Areas", "03-Resources", "05-Goals"];
5
+ function suggestion(properties) {
6
+ if (properties.type === "goal" || properties.domain === "goals")
7
+ return "05-Goals";
8
+ if (properties.type === "resource")
9
+ return "03-Resources";
10
+ if (properties.type === "project")
11
+ return "01-Projects";
12
+ if (properties.domain === "personal")
13
+ return "02-Areas/Personal";
14
+ if (properties.domain === "business")
15
+ return "02-Areas/Business";
16
+ if (properties.domain === "education")
17
+ return "02-Areas/Education";
18
+ return "03-Resources";
19
+ }
20
+ export async function listInboxCandidates(connection) {
21
+ const discovery = await discoverObsidianVault(connection);
22
+ const byHash = new Map();
23
+ for (const note of discovery.notes)
24
+ if (!byHash.has(note.sha256))
25
+ byHash.set(note.sha256, note.path);
26
+ return discovery.notes
27
+ .filter((note) => note.path === "00-Inbox" || note.path.startsWith("00-Inbox/"))
28
+ .map((note) => ({
29
+ path: note.path,
30
+ bytes: note.bytes,
31
+ sha256: note.sha256,
32
+ properties: note.properties,
33
+ suggestedRoot: suggestion(note.properties),
34
+ duplicateOf: discovery.notes.find((other) => other.sha256 === note.sha256 && other.path !== note.path)?.path ?? null,
35
+ }));
36
+ }
37
+ function safePath(root, relative) {
38
+ if (!relative ||
39
+ path.isAbsolute(relative) ||
40
+ relative.split(/[\\/]/).includes(".."))
41
+ throw new Error("Vault path must be relative and stay inside the configured vault");
42
+ const resolved = path.resolve(root, relative);
43
+ if (!resolved.startsWith(`${path.resolve(root)}${path.sep}`))
44
+ throw new Error("Vault path escapes the configured vault");
45
+ return resolved;
46
+ }
47
+ export async function promoteInboxNote(connection, source, targetDirectory, apply = false) {
48
+ if (!source.startsWith("00-Inbox/"))
49
+ throw new Error("Only notes inside 00-Inbox can be promoted");
50
+ if (!roots.some((root) => targetDirectory === root || targetDirectory.startsWith(`${root}/`)))
51
+ throw new Error(`Promotion target must stay under: ${roots.join(", ")}`);
52
+ const sourceFile = safePath(connection.vaultPath, source);
53
+ const destinationDirectory = safePath(connection.vaultPath, targetDirectory);
54
+ const destination = path.join(destinationDirectory, path.basename(sourceFile));
55
+ await access(sourceFile);
56
+ if (!apply)
57
+ return {
58
+ source,
59
+ destination: path.relative(connection.vaultPath, destination),
60
+ applied: false,
61
+ };
62
+ if (connection.mode !== "read-write")
63
+ throw new Error("Obsidian connection is read-only; use an explicit read-write connection before applying promotion");
64
+ await mkdir(destinationDirectory, { recursive: true });
65
+ try {
66
+ await access(destination);
67
+ throw new Error(`Promotion destination already exists: ${path.relative(connection.vaultPath, destination)}`);
68
+ }
69
+ catch (error) {
70
+ if (error.code !== "ENOENT")
71
+ throw error;
72
+ }
73
+ await rename(sourceFile, destination);
74
+ return {
75
+ source,
76
+ destination: path.relative(connection.vaultPath, destination),
77
+ applied: true,
78
+ };
79
+ }
80
+ //# sourceMappingURL=inbox-promotion.js.map
@@ -0,0 +1,113 @@
1
+ import { createHash } from "node:crypto";
2
+ import { access, mkdir, readdir, readFile, stat, writeFile, } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { oceanPath, SYSTEM_DIR } from "@ocean/core/shared/paths.js";
5
+ const MAX_FILES = 10_000;
6
+ const MAX_BYTES = 50 * 1024 * 1024;
7
+ const connectionPath = () => oceanPath(SYSTEM_DIR, "integrations", "obsidian", "connection.json");
8
+ export async function connectObsidianVault(vaultPath, mode = "read-only", file = connectionPath()) {
9
+ if (mode !== "read-only" && mode !== "read-write")
10
+ throw new Error("Obsidian mode must be read-only or read-write");
11
+ const resolvedVault = path.resolve(vaultPath);
12
+ const vault = await stat(resolvedVault);
13
+ if (!vault.isDirectory())
14
+ throw new Error("Obsidian vaultPath must be a directory");
15
+ const metadata = await stat(path.join(resolvedVault, ".obsidian"));
16
+ if (!metadata.isDirectory())
17
+ throw new Error("Path is not an Obsidian vault: missing .obsidian directory");
18
+ const connection = {
19
+ enabled: true,
20
+ mode,
21
+ vaultPath: resolvedVault,
22
+ };
23
+ const discovery = await discoverObsidianVault(connection);
24
+ await mkdir(path.dirname(file), { recursive: true });
25
+ await writeFile(file, `${JSON.stringify(connection, null, 2)}\n`, {
26
+ mode: 0o600,
27
+ });
28
+ return discovery;
29
+ }
30
+ export async function loadObsidianConnection(file = connectionPath()) {
31
+ const source = JSON.parse(await readFile(file, "utf8"));
32
+ if (source.enabled !== true)
33
+ throw new Error("Obsidian integration is disabled");
34
+ if (source.mode !== "read-only" && source.mode !== "read-write")
35
+ throw new Error("Obsidian mode must be read-only or read-write");
36
+ if (!source.vaultPath || !path.isAbsolute(source.vaultPath))
37
+ throw new Error("Obsidian vaultPath must be absolute");
38
+ const connection = {
39
+ enabled: true,
40
+ mode: source.mode,
41
+ vaultPath: source.vaultPath,
42
+ };
43
+ await validateObsidianVault(connection);
44
+ return connection;
45
+ }
46
+ export async function validateObsidianVault(connection) {
47
+ const vault = await stat(connection.vaultPath);
48
+ if (!vault.isDirectory())
49
+ throw new Error("Obsidian vaultPath must be a directory");
50
+ const metadata = await stat(path.join(connection.vaultPath, ".obsidian"));
51
+ if (!metadata.isDirectory())
52
+ throw new Error("Path is not an Obsidian vault: missing .obsidian directory");
53
+ }
54
+ function properties(content) {
55
+ if (!content.startsWith("---\n"))
56
+ return {};
57
+ const end = content.indexOf("\n---", 4);
58
+ if (end < 0)
59
+ return {};
60
+ return Object.fromEntries(content
61
+ .slice(4, end)
62
+ .split("\n")
63
+ .map((line) => line.match(/^([A-Za-z][\w-]*):\s*(.*)$/))
64
+ .filter((match) => Boolean(match))
65
+ .map((match) => [match[1], match[2].trim()]));
66
+ }
67
+ async function collect(directory, root, notes, total) {
68
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
69
+ if (entry.name.startsWith(".") || entry.name === "node_modules")
70
+ continue;
71
+ const file = path.join(directory, entry.name);
72
+ if (entry.isDirectory()) {
73
+ await collect(file, root, notes, total);
74
+ continue;
75
+ }
76
+ if (!entry.name.endsWith(".md"))
77
+ continue;
78
+ if (++total.files > MAX_FILES)
79
+ throw new Error(`Obsidian vault exceeds ${MAX_FILES} Markdown files`);
80
+ const content = await readFile(file);
81
+ total.bytes += content.byteLength;
82
+ if (total.bytes > MAX_BYTES)
83
+ throw new Error(`Obsidian vault exceeds ${MAX_BYTES} bytes`);
84
+ const parsed = properties(content.toString("utf8"));
85
+ const issues = [];
86
+ if (!Object.keys(parsed).length)
87
+ issues.push("missing YAML properties");
88
+ if (parsed.type === undefined)
89
+ issues.push("missing property: type");
90
+ notes.push({
91
+ path: path.relative(root, file).split(path.sep).join("/"),
92
+ bytes: content.byteLength,
93
+ sha256: createHash("sha256").update(content).digest("hex"),
94
+ properties: parsed,
95
+ issues,
96
+ });
97
+ }
98
+ }
99
+ export async function discoverObsidianVault(connection) {
100
+ const resolvedConnection = connection ?? (await loadObsidianConnection());
101
+ await access(resolvedConnection.vaultPath);
102
+ const notes = [];
103
+ const total = { files: 0, bytes: 0 };
104
+ await collect(resolvedConnection.vaultPath, resolvedConnection.vaultPath, notes, total);
105
+ return {
106
+ vaultPath: resolvedConnection.vaultPath,
107
+ noteCount: notes.length,
108
+ totalBytes: total.bytes,
109
+ notes,
110
+ issues: notes.flatMap((note) => note.issues.map((issue) => `${note.path}: ${issue}`)),
111
+ };
112
+ }
113
+ //# sourceMappingURL=vault-discovery.js.map
@@ -0,0 +1,77 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { oceanPath, projectDir, WORKSPACE_PROJECT_ID, } from "@ocean/core/shared/paths.js";
4
+ import { appendConflict, contentHash } from "./conflict-log.js";
5
+ function isInbox(file) {
6
+ return file === "00-Inbox" || file.startsWith("00-Inbox/");
7
+ }
8
+ function isPromotionArea(file) {
9
+ return ["02-Areas/", "03-Resources/", "05-Goals/"].some((root) => file.startsWith(root));
10
+ }
11
+ // The workspace project's mirror in the vault: 01-Projects/Ocean(.md|/).
12
+ const PROJECT_MIRRORS = ["Ocean"];
13
+ function projectMirror(file) {
14
+ for (const name of PROJECT_MIRRORS) {
15
+ if (file === `01-Projects/${name}.md`)
16
+ return { relative: "README.md" };
17
+ const prefix = `01-Projects/${name}/`;
18
+ if (file.startsWith(prefix))
19
+ return { relative: file.slice(prefix.length) };
20
+ }
21
+ return null;
22
+ }
23
+ async function readOptional(file) {
24
+ try {
25
+ return await readFile(file, "utf8");
26
+ }
27
+ catch (error) {
28
+ if (error.code === "ENOENT")
29
+ return undefined;
30
+ throw error;
31
+ }
32
+ }
33
+ export async function ingestVaultChanges(connection, result, stateFile, baselineState) {
34
+ const state = stateFile
35
+ ? JSON.parse(await readFile(stateFile, "utf8"))
36
+ : { notes: baselineState ?? {} };
37
+ const baseline = state.notes ?? {};
38
+ const changed = [...result.added, ...result.changed];
39
+ const output = {
40
+ inbox: [],
41
+ promotionCandidates: [],
42
+ conflicts: [],
43
+ removed: [...result.removed],
44
+ };
45
+ const seenMirrors = new Set();
46
+ for (const relative of changed) {
47
+ if (isInbox(relative)) {
48
+ output.inbox.push(relative);
49
+ continue;
50
+ }
51
+ if (isPromotionArea(relative)) {
52
+ output.promotionCandidates.push(relative);
53
+ continue;
54
+ }
55
+ const mirror = projectMirror(relative);
56
+ if (!mirror)
57
+ continue;
58
+ if (seenMirrors.has(mirror.relative))
59
+ continue;
60
+ seenMirrors.add(mirror.relative);
61
+ const vaultContent = await readOptional(path.join(connection.vaultPath, relative));
62
+ const oceanFile = oceanPath(projectDir(WORKSPACE_PROJECT_ID), mirror.relative);
63
+ const oceanContent = await readOptional(oceanFile);
64
+ const record = await appendConflict({
65
+ path: relative,
66
+ baselineSha256: baseline[relative]?.sha256 ?? null,
67
+ vaultSha256: contentHash(vaultContent),
68
+ oceanSha256: contentHash(oceanContent),
69
+ vaultContent,
70
+ oceanContent,
71
+ source: "vault-ingestion",
72
+ });
73
+ output.conflicts.push(record);
74
+ }
75
+ return output;
76
+ }
77
+ //# sourceMappingURL=vault-ingestion.js.map
@@ -0,0 +1,73 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { renameSync, writeFileSync } from "node:fs";
3
+ import { mkdir, readFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { oceanPath, SYSTEM_DIR } from "@ocean/core/shared/paths.js";
6
+ import { discoverObsidianVault, } from "./vault-discovery.js";
7
+ import { ingestVaultChanges, } from "./vault-ingestion.js";
8
+ const defaultStatePath = () => oceanPath(SYSTEM_DIR, "integrations", "obsidian", "sync-state.json");
9
+ async function readState(file) {
10
+ try {
11
+ const state = JSON.parse(await readFile(file, "utf8"));
12
+ return state.notes && typeof state.notes === "object"
13
+ ? state.notes
14
+ : state;
15
+ }
16
+ catch (error) {
17
+ if (error.code === "ENOENT")
18
+ return {};
19
+ throw error;
20
+ }
21
+ }
22
+ export async function syncObsidianVault(connection, stateFile = defaultStatePath()) {
23
+ const discovery = await discoverObsidianVault(connection);
24
+ const previous = await readState(stateFile);
25
+ const current = Object.fromEntries(discovery.notes.map((note) => [
26
+ note.path,
27
+ { sha256: note.sha256, bytes: note.bytes },
28
+ ]));
29
+ const added = Object.keys(current).filter((file) => !previous[file]);
30
+ const changed = Object.keys(current).filter((file) => previous[file] && previous[file]?.sha256 !== current[file]?.sha256);
31
+ const removed = Object.keys(previous).filter((file) => !current[file]);
32
+ await mkdir(path.dirname(stateFile), { recursive: true });
33
+ const temporary = `${stateFile}.tmp-${randomUUID()}`;
34
+ writeFileSync(temporary, `${JSON.stringify({ version: 1, vaultPath: connection.vaultPath, notes: current }, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
35
+ renameSync(temporary, stateFile);
36
+ const result = { ...discovery, added, changed, removed };
37
+ return {
38
+ ...result,
39
+ ingestion: await ingestVaultChanges(connection, result, undefined, previous),
40
+ };
41
+ }
42
+ export async function watchObsidianVault(connection, stateFile = defaultStatePath(), signal, debounceMs = 250) {
43
+ await syncObsidianVault(connection, stateFile);
44
+ const watcher = (await import("node:fs")).watch(connection.vaultPath, {
45
+ recursive: true,
46
+ });
47
+ let timer;
48
+ let running = Promise.resolve();
49
+ const run = () => {
50
+ if (timer)
51
+ clearTimeout(timer);
52
+ timer = setTimeout(() => {
53
+ running = running
54
+ .then(() => syncObsidianVault(connection, stateFile))
55
+ .then(() => undefined);
56
+ }, debounceMs);
57
+ };
58
+ watcher.on("change", run);
59
+ watcher.on("rename", run);
60
+ await new Promise((resolve) => {
61
+ const stop = () => {
62
+ if (timer)
63
+ clearTimeout(timer);
64
+ watcher.close();
65
+ void running.finally(resolve);
66
+ };
67
+ if (signal?.aborted)
68
+ stop();
69
+ else
70
+ signal?.addEventListener("abort", stop, { once: true });
71
+ });
72
+ }
73
+ //# sourceMappingURL=vault-sync.js.map
@@ -0,0 +1,75 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { oceanPath, SYSTEM_DIR } from "@ocean/core/shared/paths.js";
5
+ const MAX_CONTENT_BYTES = 1_000_000;
6
+ export function resolveObsidianNotePath(vaultPath, relative) {
7
+ if (!relative ||
8
+ path.isAbsolute(relative) ||
9
+ relative.split(/[\\/]/).includes("..") ||
10
+ !relative.endsWith(".md")) {
11
+ throw new Error("Obsidian write path must be a relative Markdown file inside the vault");
12
+ }
13
+ const resolved = path.resolve(vaultPath, relative);
14
+ if (!resolved.startsWith(`${path.resolve(vaultPath)}${path.sep}`) ||
15
+ relative.split(/[\\/]/).some((part) => part.startsWith("."))) {
16
+ throw new Error("Obsidian write path cannot target hidden metadata or escape the vault");
17
+ }
18
+ return resolved;
19
+ }
20
+ function hash(content) {
21
+ return createHash("sha256").update(content).digest("hex");
22
+ }
23
+ async function recordConflict(relative, expectedSha256, actualSha256, content, conflictsDirectory = oceanPath(SYSTEM_DIR, "integrations", "obsidian", "conflicts")) {
24
+ const directory = conflictsDirectory;
25
+ await mkdir(directory, { recursive: true });
26
+ const record = path.join(directory, `${new Date().toISOString().replaceAll(/[:.]/g, "-")}-${randomUUID()}.json`);
27
+ await writeFile(record, `${JSON.stringify({ version: 1, path: relative, expectedSha256, actualSha256, proposedContent: content, createdAt: new Date().toISOString() }, null, 2)}\n`);
28
+ return record;
29
+ }
30
+ export async function writeObsidianNote(connection, relative, content, expectedSha256 = null, apply = false, conflictsDirectory) {
31
+ const file = resolveObsidianNotePath(connection.vaultPath, relative);
32
+ if (Buffer.byteLength(content) > MAX_CONTENT_BYTES)
33
+ throw new Error(`Obsidian note exceeds ${MAX_CONTENT_BYTES} bytes`);
34
+ let current = null;
35
+ try {
36
+ current = await readFile(file);
37
+ }
38
+ catch (error) {
39
+ if (error.code !== "ENOENT")
40
+ throw error;
41
+ }
42
+ const actualSha256 = current ? hash(current) : null;
43
+ if (current && actualSha256 !== expectedSha256) {
44
+ const record = await recordConflict(relative, expectedSha256, actualSha256, content, conflictsDirectory);
45
+ return {
46
+ applied: false,
47
+ path: relative,
48
+ conflict: { record, expectedSha256, actualSha256 },
49
+ };
50
+ }
51
+ if (!current && expectedSha256 !== null) {
52
+ const record = await recordConflict(relative, expectedSha256, null, content, conflictsDirectory);
53
+ return {
54
+ applied: false,
55
+ path: relative,
56
+ conflict: { record, expectedSha256, actualSha256: null },
57
+ };
58
+ }
59
+ if (!apply)
60
+ return { applied: false, path: relative, sha256: hash(content) };
61
+ if (connection.mode !== "read-write")
62
+ throw new Error("Obsidian connection is read-only; use an explicit read-write connection before applying a write");
63
+ await mkdir(path.dirname(file), { recursive: true });
64
+ const temporary = `${file}.ocean-tmp-${randomUUID()}`;
65
+ try {
66
+ await writeFile(temporary, content, { encoding: "utf8", mode: 0o600 });
67
+ await rename(temporary, file);
68
+ }
69
+ catch (error) {
70
+ await unlink(temporary).catch(() => undefined);
71
+ throw error;
72
+ }
73
+ return { applied: true, path: relative, sha256: hash(content) };
74
+ }
75
+ //# sourceMappingURL=vault-writer.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "@ocean/services",
3
+ "version": "1.2.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "exports": {
7
+ "./*.js": {
8
+ "import": "./dist/*.js"
9
+ }
10
+ }
11
+ }