@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,410 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { isDeepStrictEqual } from "node:util";
4
+ import { stagingDir } from "@ocean/brain/memory/memory-paths.js";
5
+ import { defaultClientEnv, edit, newBatch, planClients, readJournal, } from "./clients.js";
6
+ import { canonicalJson, ManualEdit } from "./editors.js";
7
+ export const NATIVE_ADAPTERS = [
8
+ {
9
+ client: "claude-code",
10
+ kind: "json-set",
11
+ file: ".claude/settings.json",
12
+ key: ["autoMemoryDirectory"],
13
+ value: () => stagingDir("claude-code"),
14
+ },
15
+ {
16
+ client: "codex",
17
+ kind: "toml",
18
+ file: ".codex/config.toml",
19
+ section: "memories",
20
+ values: { generate_memories: false, use_memories: false },
21
+ },
22
+ {
23
+ client: "gemini",
24
+ kind: "json-add",
25
+ file: ".gemini/settings.json",
26
+ key: ["tools", "exclude"],
27
+ value: () => "save_memory",
28
+ },
29
+ {
30
+ client: "qwen",
31
+ kind: "json-add",
32
+ file: ".qwen/settings.json",
33
+ key: ["tools", "exclude"],
34
+ value: () => "save_memory",
35
+ },
36
+ {
37
+ client: "hermes",
38
+ kind: "yaml",
39
+ file: ".hermes/config.yaml",
40
+ section: "memory",
41
+ values: { memory_enabled: false, user_profile_enabled: false },
42
+ },
43
+ {
44
+ client: "claude-desktop",
45
+ kind: "capture",
46
+ text: "left on: the daily `ocean pull` proposes its memory and project instructions (also claude.ai) from Claude Desktop's and the browsers' caches for review",
47
+ },
48
+ {
49
+ client: "cursor",
50
+ kind: "uncaptured",
51
+ text: "left on: Cursor keeps its Memories in the app, where Ocean cannot read them; its chats are indexed by `ocean pull`",
52
+ },
53
+ {
54
+ client: "chatgpt",
55
+ kind: "capture",
56
+ text: "left on: the daily `ocean pull` proposes its memory and custom instructions from the browsers' chatgpt.com caches for review",
57
+ },
58
+ {
59
+ client: "antigravity",
60
+ kind: "harvest",
61
+ text: "no switch: `ocean memory harvest --source antigravity` turns its brain notes into candidates",
62
+ },
63
+ ];
64
+ const hasSetting = (adapter) => "file" in adapter && adapter.kind !== "yaml";
65
+ async function readText(file) {
66
+ try {
67
+ return await readFile(file, "utf8");
68
+ }
69
+ catch (error) {
70
+ if (error.code === "ENOENT")
71
+ return "";
72
+ throw error;
73
+ }
74
+ }
75
+ const settingLabel = (adapter, env) => adapter.kind === "toml"
76
+ ? `[${adapter.section}] ${Object.entries(adapter.values)
77
+ .map(([key, value]) => `${key} = ${value}`)
78
+ .join(", ")}`
79
+ : `${adapter.key.join(".")} ${adapter.kind === "json-add" ? "+=" : "="} ${JSON.stringify(adapter.value(env))}`;
80
+ function nodeAt(root, key) {
81
+ let node = root;
82
+ for (const part of key)
83
+ node =
84
+ node && typeof node === "object" && !Array.isArray(node)
85
+ ? node[part]
86
+ : undefined;
87
+ return node;
88
+ }
89
+ function desiredJson(adapter, current, env) {
90
+ const value = adapter.value(env);
91
+ if (adapter.kind === "json-set")
92
+ return value;
93
+ const list = Array.isArray(current) ? current : [];
94
+ return list.includes(value) ? list : [...list, value];
95
+ }
96
+ // TOML: the managed keys inside one `[section]`, read and set line by line.
97
+ const tomlLine = (key) => new RegExp(`^\\s*${key}\\s*=\\s*(\\S+)`);
98
+ function tomlSection(lines, section) {
99
+ const start = lines.findIndex((line) => line.trim() === `[${section}]`);
100
+ if (start < 0)
101
+ return null;
102
+ let end = lines.length;
103
+ for (let index = start + 1; index < lines.length; index++)
104
+ if (/^\s*\[/.test(lines[index])) {
105
+ end = index;
106
+ break;
107
+ }
108
+ // The section ends at its last non-blank line: new keys go there, before any blank
109
+ // separator (or the file's final newline).
110
+ while (end - 1 > start && !lines[end - 1].trim())
111
+ end -= 1;
112
+ return { start, end };
113
+ }
114
+ function tomlValues(text, adapter) {
115
+ const lines = text.split("\n");
116
+ const range = tomlSection(lines, adapter.section);
117
+ const values = {};
118
+ for (const key of Object.keys(adapter.values)) {
119
+ const line = range
120
+ ? lines
121
+ .slice(range.start + 1, range.end)
122
+ .find((item) => tomlLine(key).test(item))
123
+ : undefined;
124
+ values[key] = line ? (tomlLine(key).exec(line)?.[1] ?? null) : null;
125
+ }
126
+ return { values, hasSection: Boolean(range) };
127
+ }
128
+ function setToml(text, section, values, dropEmptySection) {
129
+ const lines = text.split("\n");
130
+ let range = tomlSection(lines, section);
131
+ if (!range) {
132
+ const tail = lines.at(-1) === "" ? lines.length - 1 : lines.length;
133
+ const block = [
134
+ ...(tail && lines[tail - 1].trim() ? [""] : []),
135
+ `[${section}]`,
136
+ ];
137
+ lines.splice(tail, 0, ...block);
138
+ range = tomlSection(lines, section);
139
+ }
140
+ for (const [key, value] of Object.entries(values)) {
141
+ const at = lines.findIndex((line, index) => index > range.start && index < range.end && tomlLine(key).test(line));
142
+ if (value === null) {
143
+ if (at >= 0) {
144
+ lines.splice(at, 1);
145
+ range.end -= 1;
146
+ }
147
+ }
148
+ else if (at >= 0)
149
+ lines[at] = `${key} = ${value}`;
150
+ else {
151
+ lines.splice(range.end, 0, `${key} = ${value}`);
152
+ range.end += 1;
153
+ }
154
+ }
155
+ if (dropEmptySection) {
156
+ const body = lines.slice(range.start + 1, range.end);
157
+ if (body.every((line) => !line.trim())) {
158
+ const before = range.start > 0 && !lines[range.start - 1].trim() ? 1 : 0;
159
+ lines.splice(range.start - before, range.end - range.start + before);
160
+ }
161
+ }
162
+ return lines.join("\n");
163
+ }
164
+ function planOne(text, adapter, env) {
165
+ const plan = {
166
+ client: adapter.client,
167
+ status: "change",
168
+ file: `~/${adapter.file}`,
169
+ setting: settingLabel(adapter, env),
170
+ };
171
+ if (adapter.kind === "toml") {
172
+ const { values } = tomlValues(text, adapter);
173
+ const done = Object.entries(adapter.values).every(([key, value]) => values[key] === String(value));
174
+ return done ? { ...plan, status: "current" } : plan;
175
+ }
176
+ let root;
177
+ try {
178
+ root = JSON.parse(text.trim() ? text : "{}");
179
+ }
180
+ catch {
181
+ return { ...plan, status: "manual", reason: "the file is not valid JSON" };
182
+ }
183
+ const current = nodeAt(root, adapter.key);
184
+ if (isDeepStrictEqual(current, desiredJson(adapter, current, env)))
185
+ return { ...plan, status: "current" };
186
+ try {
187
+ canonicalJson(text);
188
+ }
189
+ catch (error) {
190
+ if (!(error instanceof ManualEdit))
191
+ throw error;
192
+ return { ...plan, status: "manual", reason: error.message };
193
+ }
194
+ return plan;
195
+ }
196
+ function yamlValues(text, section) {
197
+ const lines = text.split("\n");
198
+ const start = lines.findIndex((line) => line.trimEnd() === `${section}:`);
199
+ const values = {};
200
+ if (start < 0)
201
+ return values;
202
+ for (const line of lines.slice(start + 1)) {
203
+ if (!line.trim() || line.trimStart().startsWith("#"))
204
+ continue;
205
+ if (!/^\s/.test(line))
206
+ break;
207
+ const match = /^\s+([A-Za-z0-9_-]+):\s*([^\s#]+)/.exec(line);
208
+ if (match && !(match[1] in values))
209
+ values[match[1]] = match[2];
210
+ }
211
+ return values;
212
+ }
213
+ function planYaml(text, adapter) {
214
+ const values = yamlValues(text, adapter.section);
215
+ const done = Object.entries(adapter.values).every(([key, value]) => values[key] === String(value));
216
+ return {
217
+ client: adapter.client,
218
+ status: done ? "current" : "manual",
219
+ file: `~/${adapter.file}`,
220
+ setting: Object.entries(adapter.values)
221
+ .map(([key, value]) => `${adapter.section}.${key}: ${value}`)
222
+ .join(", "),
223
+ ...(done ? {} : { reason: "Ocean does not rewrite YAML; set it by hand" }),
224
+ };
225
+ }
226
+ export async function planNative(env = defaultClientEnv()) {
227
+ const installed = new Set((await planClients(env))
228
+ .filter((plan) => plan.installed)
229
+ .map((plan) => plan.client));
230
+ const plans = [];
231
+ for (const adapter of NATIVE_ADAPTERS) {
232
+ if (!installed.has(adapter.client))
233
+ continue;
234
+ if (adapter.kind === "yaml")
235
+ plans.push(planYaml(await readText(path.join(env.home, adapter.file)), adapter));
236
+ else if (!hasSetting(adapter))
237
+ plans.push({
238
+ client: adapter.client,
239
+ status: adapter.kind,
240
+ reason: adapter.text,
241
+ });
242
+ else
243
+ plans.push(planOne(await readText(path.join(env.home, adapter.file)), adapter, env));
244
+ }
245
+ return plans;
246
+ }
247
+ export async function applyNative(options = {}) {
248
+ const env = options.env ?? defaultClientEnv();
249
+ const batch = options.batch ?? newBatch();
250
+ const results = [];
251
+ for (const plan of await planNative(env)) {
252
+ if (plan.status !== "change")
253
+ continue;
254
+ if (options.clients && !options.clients.includes(plan.client))
255
+ continue;
256
+ const adapter = NATIVE_ADAPTERS.find((item) => item.client === plan.client);
257
+ if (!adapter || !hasSetting(adapter))
258
+ continue;
259
+ const result = await edit(path.join(env.home, adapter.file), (text) => {
260
+ if (planOne(text, adapter, env).status !== "change")
261
+ return null;
262
+ const entry = {
263
+ client: adapter.client,
264
+ file: adapter.file,
265
+ op: "native-set",
266
+ key: adapter.kind === "toml" ? adapter.section : adapter.key.join("."),
267
+ };
268
+ if (adapter.kind === "toml") {
269
+ const { values, hasSection } = tomlValues(text, adapter);
270
+ const after = Object.fromEntries(Object.entries(adapter.values).map(([key, value]) => [
271
+ key,
272
+ String(value),
273
+ ]));
274
+ return {
275
+ next: setToml(text, adapter.section, after, false),
276
+ entries: [
277
+ {
278
+ ...entry,
279
+ before: values,
280
+ after,
281
+ created: hasSection ? [] : [adapter.section],
282
+ },
283
+ ],
284
+ };
285
+ }
286
+ const { parsed, render } = canonicalJson(text);
287
+ const before = nodeAt(parsed, adapter.key);
288
+ const after = desiredJson(adapter, before, env);
289
+ const created = [];
290
+ let node = parsed;
291
+ for (const part of adapter.key.slice(0, -1)) {
292
+ if (node[part] === undefined) {
293
+ node[part] = {};
294
+ created.push(part);
295
+ }
296
+ node = node[part];
297
+ }
298
+ node[adapter.key.at(-1)] = after;
299
+ return {
300
+ next: render(parsed),
301
+ entries: [
302
+ {
303
+ ...entry,
304
+ before: before === undefined ? null : { value: before },
305
+ after: { value: after },
306
+ created,
307
+ },
308
+ ],
309
+ };
310
+ }, batch);
311
+ results.push({ client: plan.client, result });
312
+ }
313
+ return { batch, results };
314
+ }
315
+ // Puts each setting one apply batch changed back to its value before, when it still holds what
316
+ // Ocean wrote; otherwise it is a conflict and left alone.
317
+ export async function restoreNative(options) {
318
+ const env = options.env ?? defaultClientEnv();
319
+ const entries = (await readJournal())
320
+ .filter((entry) => entry.batch === options.batch &&
321
+ entry.op === "native-set" &&
322
+ !entry.restoreOf)
323
+ .reverse();
324
+ const restored = [];
325
+ const conflicts = [];
326
+ const undo = newBatch();
327
+ for (const entry of entries) {
328
+ const adapter = NATIVE_ADAPTERS.find((item) => item.client === entry.client);
329
+ if (!adapter || !hasSetting(adapter))
330
+ continue;
331
+ const outcome = { restored: false };
332
+ await edit(path.join(env.home, entry.file), (text) => {
333
+ outcome.restored = false;
334
+ let next;
335
+ if (adapter.kind === "toml") {
336
+ const { values } = tomlValues(text, adapter);
337
+ if (!isDeepStrictEqual(values, entry.after))
338
+ return null;
339
+ next = setToml(text, adapter.section, entry.before, Boolean(entry.created?.length));
340
+ }
341
+ else {
342
+ let document;
343
+ try {
344
+ document = canonicalJson(text);
345
+ }
346
+ catch {
347
+ return null;
348
+ }
349
+ const { parsed, render } = document;
350
+ if (!isDeepStrictEqual(nodeAt(parsed, adapter.key), entry.after?.value))
351
+ return null;
352
+ const parents = [parsed];
353
+ for (const part of adapter.key.slice(0, -1))
354
+ parents.push(parents.at(-1)?.[part]);
355
+ const leaf = parents.at(-1);
356
+ if (entry.before)
357
+ leaf[adapter.key.at(-1)] = entry.before.value;
358
+ else
359
+ delete leaf[adapter.key.at(-1)];
360
+ for (let depth = adapter.key.length - 2; depth >= 0; depth--) {
361
+ const part = adapter.key[depth];
362
+ if (entry.created?.includes(part) &&
363
+ !Object.keys(parents[depth + 1]).length)
364
+ delete parents[depth][part];
365
+ }
366
+ next = render(parsed);
367
+ }
368
+ outcome.restored = true;
369
+ return { next, entries: [{ ...entry, restoreOf: options.batch }] };
370
+ }, undo);
371
+ (outcome.restored ? restored : conflicts).push(`${entry.client}:native`);
372
+ }
373
+ return { restored, conflicts };
374
+ }
375
+ // Doctor view (§3.8): a switch Ocean can set but has not is a competing source (FAIL); a server
376
+ // store `ocean pull` captures is governed (OK); one it cannot read, or a store with no switch
377
+ // that is harvested by hand, is a WARN.
378
+ export async function nativeFindings(env = defaultClientEnv()) {
379
+ return (await planNative(env)).map((plan) => {
380
+ const name = `${plan.client} native memory`;
381
+ if (plan.status === "current")
382
+ return {
383
+ code: "NATIVE_POLICY_SET",
384
+ severity: "OK",
385
+ message: `${name}: ${plan.setting} (confirm in the app that nothing new lands in its own store)`,
386
+ fixable: false,
387
+ };
388
+ if (plan.status === "change" || plan.status === "manual")
389
+ return {
390
+ code: "NATIVE_COMPETING",
391
+ severity: "FAIL",
392
+ message: `${name}: competing source — ${plan.status === "change" ? `run \`ocean clients apply --yes\` (${plan.setting} in ${plan.file})` : `hand edit: ${plan.setting} in ${plan.file} (${plan.reason})`}`,
393
+ fixable: plan.status === "change",
394
+ };
395
+ if (plan.status === "capture")
396
+ return {
397
+ code: "NATIVE_CAPTURED",
398
+ severity: "OK",
399
+ message: `${name}: ${plan.reason}`,
400
+ fixable: false,
401
+ };
402
+ return {
403
+ code: plan.status === "uncaptured" ? "NATIVE_UNCAPTURED" : "NATIVE_HARVEST",
404
+ severity: "WARN",
405
+ message: `${name}: ${plan.reason}`,
406
+ fixable: false,
407
+ };
408
+ });
409
+ }
410
+ //# sourceMappingURL=native.js.map
@@ -0,0 +1,158 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile, unlink } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { appendJournal, defaultClientEnv, edit, newBatch, planClients, readJournal, temporaryEntryProblem, } from "./clients.js";
5
+ // OpenCode has no shell hooks; its plugins do (T-307). Ocean owns one plugin file that, once per
6
+ // OpenCode session, runs `ocean hook session-start --client opencode` and adds the bounded start
7
+ // line to that session's system prompt (`experimental.chat.system.transform`, OpenCode 1.18).
8
+ // OpenCode fires no event when a session ends (`session.deleted` is a deletion), so there is no
9
+ // session-end hook. The file is Ocean's alone: written whole, journaled, removed on restore.
10
+ export const OPENCODE_PLUGIN_FILE = ".config/opencode/plugins/ocean.js";
11
+ const MARKER = "// Ocean session context for OpenCode";
12
+ const sha = (text) => createHash("sha256").update(text).digest("hex");
13
+ // The engine invocation the plugin spawns (no shell: argv words only).
14
+ function invocation(env) {
15
+ const program = Array.isArray(env.entry.args)
16
+ ? String(env.entry.args[0])
17
+ : "";
18
+ return program === "mcp"
19
+ ? [String(env.entry.command)]
20
+ : [String(env.entry.command), program];
21
+ }
22
+ export function opencodePluginSource(env) {
23
+ return `${MARKER} (written by \`ocean clients apply\`; \`ocean clients restore\` removes it).
24
+ import { spawn } from "node:child_process";
25
+
26
+ const OCEAN = ${JSON.stringify(invocation(env))};
27
+ const contexts = new Map();
28
+
29
+ function startLine(sessionID, directory) {
30
+ return new Promise((resolve) => {
31
+ let out = "";
32
+ const child = spawn(OCEAN[0], [...OCEAN.slice(1), "hook", "session-start", "--client", "opencode"], {
33
+ stdio: ["pipe", "pipe", "ignore"],
34
+ });
35
+ const timer = setTimeout(() => child.kill(), 10000);
36
+ child.stdout.on("data", (chunk) => (out += chunk));
37
+ child.on("error", () => resolve(""));
38
+ child.on("close", () => {
39
+ clearTimeout(timer);
40
+ try {
41
+ resolve(JSON.parse(out).hookSpecificOutput?.additionalContext ?? "");
42
+ } catch {
43
+ resolve("");
44
+ }
45
+ });
46
+ child.stdin.end(JSON.stringify({ session_id: sessionID, cwd: directory ?? "" }));
47
+ });
48
+ }
49
+
50
+ export const OceanContext = async ({ directory }) => ({
51
+ "experimental.chat.system.transform": async (input, output) => {
52
+ const id = input?.sessionID;
53
+ if (!id || !Array.isArray(output?.system)) return;
54
+ if (!contexts.has(id)) contexts.set(id, startLine(id, directory));
55
+ const line = await contexts.get(id);
56
+ if (line) output.system.push(line);
57
+ },
58
+ });
59
+ `;
60
+ }
61
+ async function readIfPresent(file) {
62
+ try {
63
+ return await readFile(file, "utf8");
64
+ }
65
+ catch (error) {
66
+ if (error.code === "ENOENT")
67
+ return null;
68
+ throw error;
69
+ }
70
+ }
71
+ function planText(text, want) {
72
+ const plan = {
73
+ client: "opencode",
74
+ file: `~/${OPENCODE_PLUGIN_FILE}`,
75
+ };
76
+ if (text === want)
77
+ return { ...plan, status: "current" };
78
+ if (text === null || text.startsWith(MARKER))
79
+ return { ...plan, status: "change" };
80
+ return {
81
+ ...plan,
82
+ status: "manual",
83
+ reason: "a plugin Ocean did not write already has this name",
84
+ };
85
+ }
86
+ export async function planOpencodePlugin(env = defaultClientEnv()) {
87
+ const installed = (await planClients(env)).some((plan) => plan.client === "opencode" && plan.installed);
88
+ if (!installed)
89
+ return [];
90
+ const text = await readIfPresent(path.join(env.home, OPENCODE_PLUGIN_FILE));
91
+ return [planText(text, opencodePluginSource(env))];
92
+ }
93
+ export async function applyOpencodePlugin(options = {}) {
94
+ const env = options.env ?? defaultClientEnv();
95
+ const batch = options.batch ?? newBatch();
96
+ if (options.clients && !options.clients.includes("opencode"))
97
+ return { batch, results: [] };
98
+ if (options.durable ?? !options.env) {
99
+ const problem = temporaryEntryProblem(env.entry);
100
+ if (problem)
101
+ throw new Error(`Refusing to add the OpenCode plugin: ${problem}`);
102
+ }
103
+ const plans = await planOpencodePlugin(env);
104
+ if (plans[0]?.status !== "change")
105
+ return { batch, results: [] };
106
+ const want = opencodePluginSource(env);
107
+ const result = await edit(path.join(env.home, OPENCODE_PLUGIN_FILE), (text) => {
108
+ const current = text === "" ? null : text;
109
+ if (planText(current, want).status !== "change")
110
+ return null;
111
+ return {
112
+ next: want,
113
+ entries: [
114
+ {
115
+ client: "opencode",
116
+ file: OPENCODE_PLUGIN_FILE,
117
+ op: "hook-add",
118
+ key: "plugin",
119
+ before: current === null ? null : { sha256: sha(current) },
120
+ after: { sha256: sha(want) },
121
+ },
122
+ ],
123
+ };
124
+ }, batch);
125
+ return { batch, results: [{ client: "opencode", result }] };
126
+ }
127
+ // Removes the plugin one apply batch wrote, unless it changed since (a conflict, left alone).
128
+ // A plugin that replaced an older Ocean plugin is removed too; the next apply writes it again.
129
+ export async function restoreOpencodePlugin(options) {
130
+ const env = options.env ?? defaultClientEnv();
131
+ const entries = (await readJournal()).filter((entry) => entry.batch === options.batch &&
132
+ entry.client === "opencode" &&
133
+ entry.key === "plugin" &&
134
+ !entry.restoreOf);
135
+ const restored = [];
136
+ const conflicts = [];
137
+ for (const entry of entries) {
138
+ const file = path.join(env.home, entry.file);
139
+ const text = await readIfPresent(file);
140
+ const after = entry.after?.sha256;
141
+ if (text === null || sha(text) !== after) {
142
+ conflicts.push("opencode:plugin");
143
+ continue;
144
+ }
145
+ await unlink(file);
146
+ await appendJournal([
147
+ {
148
+ ...entry,
149
+ batch: newBatch(),
150
+ ts: new Date().toISOString(),
151
+ restoreOf: options.batch,
152
+ },
153
+ ]);
154
+ restored.push("opencode:plugin");
155
+ }
156
+ return { restored, conflicts };
157
+ }
158
+ //# sourceMappingURL=opencode-plugin.js.map