@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,202 @@
1
+ import { execFile } from "node:child_process";
2
+ import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { memorySettings } from "@ocean/brain/memory/memory-audit.js";
7
+ import { harvestMemory, importClaudeExport, } from "@ocean/brain/memory/memory-harvest.js";
8
+ import { approveCandidate } from "@ocean/brain/memory/memory-review.js";
9
+ import { withFileLock } from "@ocean/core/shared/fs-utils.js";
10
+ import { oceanPath, SYSTEM_DIR } from "@ocean/core/shared/paths.js";
11
+ import { buildHistoryIndex } from "./history-index.js";
12
+ import { loadProjectIndex, placeMemory } from "./project-match.js";
13
+ import { captureServerHistory } from "./server-capture.js";
14
+ // `ocean pull` (T-305): one command brings everything every AI client on this machine holds into
15
+ // Ocean — each client's own memory and the instruction files the owner wrote (as candidates for
16
+ // review, never straight into memory), a claude.ai export found in ~/Downloads (its projects, as
17
+ // candidates too), and an index of every conversation (pointers only, transcripts stay put).
18
+ // Everything pulled is sorted by the owner's projects and carries where it came from (client,
19
+ // project, file, date). Nothing is pulled twice (content keys, also across spacing and case) and
20
+ // nothing is lost: every item read is listed in the run's record with what happened to it.
21
+ // A dry run until --yes. `--daily` returns at once when the last pull is under a day old, so the
22
+ // session-start hook can run it every time.
23
+ const DAY_MS = 24 * 60 * 60 * 1000;
24
+ const stampFile = () => oceanPath(SYSTEM_DIR, "state", "pull", "last.json");
25
+ const importsDir = () => oceanPath(SYSTEM_DIR, "state", "imports");
26
+ export const runsDir = () => oceanPath(SYSTEM_DIR, "state", "pull", "runs");
27
+ // With `pullAutoApprove: "on"` in bridge/config/memory.json, what this run proposed goes through
28
+ // the same validated write path as an owner's approval, but only as a new record: no target, an
29
+ // existing record to add to, a similar record or a refusal leaves the candidate for review.
30
+ async function autoApprove(items) {
31
+ const approvals = new Map();
32
+ for (const item of items) {
33
+ if (item.status !== "proposed" || !item.candidate)
34
+ continue;
35
+ try {
36
+ const written = await approveCandidate({
37
+ id: path.basename(item.candidate, ".md"),
38
+ createOnly: true,
39
+ source: "pull-auto-approve",
40
+ });
41
+ approvals.set(item.candidate, { approved: written.path });
42
+ }
43
+ catch (error) {
44
+ approvals.set(item.candidate, {
45
+ review: error instanceof Error ? error.message : String(error),
46
+ });
47
+ }
48
+ }
49
+ return approvals;
50
+ }
51
+ function tally(items) {
52
+ const counts = {};
53
+ for (const item of items) {
54
+ const bySource = counts[item.source] ?? {};
55
+ bySource[item.status] = (bySource[item.status] ?? 0) + 1;
56
+ counts[item.source] = bySource;
57
+ }
58
+ return counts;
59
+ }
60
+ const exists = (target) => stat(target).then(() => true, () => false);
61
+ // claude.ai exports in ~/Downloads: an unpacked folder holding projects.json, or the zip it came
62
+ // in (`data-….zip`), which --yes unpacks once into the workspace's state folder.
63
+ async function findExports(home) {
64
+ const downloads = path.join(home, "Downloads");
65
+ const entries = await readdir(downloads, { withFileTypes: true }).catch(() => []);
66
+ const found = [];
67
+ for (const entry of entries) {
68
+ const target = path.join(downloads, entry.name);
69
+ if (entry.isDirectory() &&
70
+ (await exists(path.join(target, "projects.json"))))
71
+ found.push({ path: target, kind: "folder" });
72
+ else if (entry.isFile() && /^data-.*\.zip$/i.test(entry.name))
73
+ found.push({ path: target, kind: "zip" });
74
+ }
75
+ return found;
76
+ }
77
+ async function unpack(zip) {
78
+ const target = path.join(importsDir(), path.basename(zip, ".zip"));
79
+ if (!(await exists(target))) {
80
+ await mkdir(target, { recursive: true, mode: 0o700 });
81
+ await promisify(execFile)("unzip", ["-o", "-q", zip, "-d", target]);
82
+ }
83
+ return target;
84
+ }
85
+ async function importExport(found, apply, classify) {
86
+ if (found.kind === "zip" && !apply)
87
+ return { found, items: [] };
88
+ const folder = found.kind === "zip" ? await unpack(found.path) : found.path;
89
+ const { items, problems } = await importClaudeExport(folder, {
90
+ apply,
91
+ classify,
92
+ });
93
+ const names = await readdir(folder).catch(() => []);
94
+ return {
95
+ items,
96
+ found: {
97
+ ...found,
98
+ items: items.length,
99
+ new: items.filter((item) => item.status === "new" || item.status === "proposed").length,
100
+ ...(problems.length ? { problems } : {}),
101
+ unread: names.filter((name) => name !== "projects.json" && !name.startsWith(".")),
102
+ },
103
+ };
104
+ }
105
+ // Clients starting at the same moment each start a pull: an applying run holds a lock, and the
106
+ // daily check is made inside it, so a second run waits and then finds the pull done instead of
107
+ // proposing the same items again.
108
+ export async function pull(options = {}) {
109
+ if (!options.apply)
110
+ return pullOnce(options);
111
+ await mkdir(path.dirname(stampFile()), { recursive: true, mode: 0o700 });
112
+ return withFileLock(stampFile(), () => pullOnce(options));
113
+ }
114
+ async function pullOnce(options) {
115
+ const apply = Boolean(options.apply);
116
+ const home = options.home ?? os.homedir();
117
+ if (options.daily) {
118
+ const last = JSON.parse(await readFile(stampFile(), "utf8").catch(() => "{}"));
119
+ const age = Date.now() - Date.parse(last.at ?? "");
120
+ if (age < DAY_MS)
121
+ return { applied: false, skipped: `last pull ${last.at}` };
122
+ }
123
+ const projects = await loadProjectIndex(home);
124
+ const classify = (origin) => placeMemory(projects, origin);
125
+ const items = await harvestMemory({ apply, home, classify });
126
+ const exports = [];
127
+ for (const found of await findExports(home)) {
128
+ const imported = await importExport(found, apply, classify);
129
+ exports.push(imported.found);
130
+ items.push(...imported.items);
131
+ }
132
+ const server = await captureServerHistory({ home, apply, classify });
133
+ items.push(...server.items);
134
+ const conversations = await buildHistoryIndex({ home, write: apply });
135
+ const history = {};
136
+ const byProject = {};
137
+ const slot = (id) => {
138
+ byProject[id] ??= { memory: {}, history: {} };
139
+ return byProject[id];
140
+ };
141
+ for (const item of items) {
142
+ const counts = slot(item.project ?? "unassigned").memory;
143
+ counts[item.status] = (counts[item.status] ?? 0) + 1;
144
+ }
145
+ const loose = new Map();
146
+ for (const entry of conversations) {
147
+ history[entry.client] = (history[entry.client] ?? 0) + 1;
148
+ const counts = slot(entry.project).history;
149
+ counts[entry.client] = (counts[entry.client] ?? 0) + 1;
150
+ if (entry.project === "unassigned" && entry.cwd)
151
+ loose.set(entry.cwd, (loose.get(entry.cwd) ?? 0) + 1);
152
+ }
153
+ const result = {
154
+ applied: apply,
155
+ memory: tally(items),
156
+ exports,
157
+ server: server.snapshots,
158
+ history,
159
+ projects: byProject,
160
+ unassignedFolders: [...loose]
161
+ .sort((a, b) => b[1] - a[1])
162
+ .slice(0, 15)
163
+ .map(([folder, count]) => ({ folder, conversations: count })),
164
+ };
165
+ const approvals = apply && (await memorySettings()).pullAutoApprove
166
+ ? await autoApprove(items)
167
+ : undefined;
168
+ if (approvals) {
169
+ const approved = [...approvals.values()].filter((item) => item.approved);
170
+ result.autoApproved = {
171
+ approved: approved.length,
172
+ review: approvals.size - approved.length,
173
+ };
174
+ }
175
+ if (apply) {
176
+ // The run's record: every item read and what became of it.
177
+ const at = new Date().toISOString();
178
+ const record = path.join(runsDir(), `${at.replace(/[:.]/g, "-")}.json`);
179
+ await mkdir(runsDir(), { recursive: true, mode: 0o700 });
180
+ await writeFile(record, `${JSON.stringify({
181
+ at,
182
+ ...result,
183
+ items: items.map((item) => ({
184
+ ...item,
185
+ ...approvals?.get(item.candidate ?? ""),
186
+ })),
187
+ }, null, 2)}\n`, { mode: 0o600 });
188
+ result.record = path.relative(oceanPath(), record);
189
+ await mkdir(path.dirname(stampFile()), { recursive: true, mode: 0o700 });
190
+ await writeFile(stampFile(), `${JSON.stringify({ at: new Date().toISOString() })}\n`, { mode: 0o600 });
191
+ }
192
+ return result;
193
+ }
194
+ // The newest run's record, for `ocean pull report`.
195
+ export async function lastPullRecord() {
196
+ const runs = (await readdir(runsDir()).catch(() => [])).filter((name) => name.endsWith(".json"));
197
+ const last = runs.sort().at(-1);
198
+ return last
199
+ ? JSON.parse(await readFile(path.join(runsDir(), last), "utf8"))
200
+ : null;
201
+ }
202
+ //# sourceMappingURL=pull.js.map
@@ -0,0 +1,307 @@
1
+ import { mkdir, open, readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import zlib from "node:zlib";
4
+ import { proposeImported, } from "@ocean/brain/memory/memory-harvest.js";
5
+ import { atomicWrite } from "@ocean/core/shared/fs-utils.js";
6
+ import { claudeProjectsFile } from "@ocean/core/shared/project-resolution.js";
7
+ import { serverHistoryDir } from "./history-index.js";
8
+ // claude.ai and ChatGPT keep their conversations, projects and memory on their servers (T-305).
9
+ // Claude Desktop and the browsers keep what they fetched of those pages in a Chromium HTTP cache;
10
+ // `ocean pull` reads it, read-only, and keeps ids, titles, dates and project names in the server
11
+ // snapshots the history index reads, and memory or instruction texts as review candidates. Only
12
+ // the account pages below are opened; a URL naming a token, OAuth or session is never read.
13
+ const WANTED = /https:\/\/(?:claude\.ai\/api\/organizations\/|chatgpt\.com\/backend-api\/(?:conversations|gizmos\/snorlax\/sidebar|user_system_messages|memories))[^\s]*/;
14
+ const SECRET_URL = /oauth|token|session|auth/i;
15
+ // The record that ends each stream of a Chromium "simple cache" entry file.
16
+ const EOF_MAGIC = Buffer.from("d8410d97456ffaf4", "hex");
17
+ const HEADER_BYTES = 24;
18
+ const UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
19
+ // Claude Desktop's cache, then every profile of the Chromium browsers.
20
+ async function cacheDirs(home) {
21
+ const dirs = [
22
+ path.join(home, "Library", "Application Support", "Claude", "Cache"),
23
+ ];
24
+ for (const browser of [
25
+ ["Google", "Chrome"],
26
+ ["Arc", "User Data"],
27
+ ["BraveSoftware", "Brave-Browser"],
28
+ ["Microsoft Edge"],
29
+ ]) {
30
+ const root = path.join(home, "Library", "Caches", ...browser);
31
+ for (const profile of await readdir(root).catch(() => []))
32
+ dirs.push(path.join(root, profile, "Cache"));
33
+ }
34
+ return dirs.map((dir) => path.join(dir, "Cache_Data"));
35
+ }
36
+ function decode(body, encoding) {
37
+ if (encoding === "br")
38
+ return zlib.brotliDecompressSync(body).toString();
39
+ if (encoding === "gzip")
40
+ return zlib.gunzipSync(body).toString();
41
+ if (encoding === "deflate")
42
+ return zlib.inflateSync(body).toString();
43
+ if (encoding === "zstd") {
44
+ const zstd = zlib
45
+ .zstdDecompressSync;
46
+ if (!zstd)
47
+ throw new Error("zstd needs Node 22.15");
48
+ return zstd(body).toString();
49
+ }
50
+ return body.toString();
51
+ }
52
+ // One entry file: the key (its URL) is read first; the body only when the URL is wanted.
53
+ async function cachedJson(file) {
54
+ const handle = await open(file, "r").catch(() => undefined);
55
+ if (!handle)
56
+ return undefined;
57
+ try {
58
+ const head = Buffer.alloc(4096);
59
+ const { bytesRead } = await handle.read(head, 0, head.length, 0);
60
+ if (bytesRead < HEADER_BYTES)
61
+ return undefined;
62
+ const keyLength = head.readUInt32LE(12);
63
+ const key = head
64
+ .subarray(HEADER_BYTES, Math.min(HEADER_BYTES + keyLength, bytesRead))
65
+ .toString("latin1");
66
+ const url = WANTED.exec(key)?.[0];
67
+ if (!url || SECRET_URL.test(url))
68
+ return undefined;
69
+ const data = await readFile(file);
70
+ const start = HEADER_BYTES + keyLength;
71
+ const end = data.indexOf(EOF_MAGIC, start);
72
+ if (end < 0)
73
+ return undefined;
74
+ const headers = data.subarray(end).toString("latin1").toLowerCase();
75
+ const encoding = /content-encoding:\s*([a-z0-9-]+)/.exec(headers)?.[1];
76
+ const date = /[\0\n]date:\s*([^\0\r\n]+)/.exec(headers)?.[1];
77
+ const json = JSON.parse(decode(data.subarray(start, end), encoding));
78
+ return { url, at: Date.parse(date ?? "") || 0, json };
79
+ }
80
+ catch {
81
+ // a partial entry, another format or a body that is not JSON
82
+ return undefined;
83
+ }
84
+ finally {
85
+ await handle.close();
86
+ }
87
+ }
88
+ // A cache holds tens of thousands of entries; their keys are read a batch at a time.
89
+ const BATCH = 128;
90
+ async function readCaches(home) {
91
+ const files = [];
92
+ for (const dir of await cacheDirs(home))
93
+ for (const name of await readdir(dir).catch(() => []))
94
+ if (name.endsWith("_0"))
95
+ files.push(path.join(dir, name));
96
+ const found = [];
97
+ for (let at = 0; at < files.length; at += BATCH)
98
+ for (const entry of await Promise.all(files.slice(at, at + BATCH).map(cachedJson)))
99
+ if (entry)
100
+ found.push(entry);
101
+ return found;
102
+ }
103
+ const object = (value) => value && typeof value === "object" ? value : {};
104
+ const list = (value) => {
105
+ const items = Array.isArray(value)
106
+ ? value
107
+ : (object(value).data ?? object(value).items);
108
+ return Array.isArray(items) ? items.map(object) : [];
109
+ };
110
+ const text = (value) => (typeof value === "string" ? value : "");
111
+ const iso = (value) => {
112
+ const time = typeof value === "number"
113
+ ? value < 1e12
114
+ ? value * 1000
115
+ : value
116
+ : Date.parse(text(value));
117
+ return Number.isFinite(time) ? new Date(time).toISOString() : undefined;
118
+ };
119
+ // Project uuid → name, from the claude.ai project responses Claude Desktop and the browsers
120
+ // cached.
121
+ function projectNames(entries) {
122
+ const names = new Map();
123
+ for (const { url, json } of entries) {
124
+ if (/\/projects(_v2)?(\?|$)/.test(url))
125
+ for (const project of list(json))
126
+ if (text(project.uuid))
127
+ names.set(text(project.uuid), text(project.name));
128
+ const one = new RegExp(`/projects/(${UUID})(\\?|$)`).exec(url)?.[1];
129
+ if (one && text(object(json).name))
130
+ names.set(one, text(object(json).name));
131
+ }
132
+ return names;
133
+ }
134
+ // The names of the owner's Claude projects that this machine has seen (T-307): what lets Ocean
135
+ // tell a real Claude project from a topic a client calls a project. Read-only, from the caches.
136
+ export async function claudeProjectNames(home) {
137
+ const entries = (await readCaches(home)).filter((entry) => entry.url.includes("claude.ai/"));
138
+ return [...new Set(projectNames(entries).values())].filter(Boolean);
139
+ }
140
+ // Writes those names (names only, owner-only file) where a tool call can read them at once.
141
+ export async function refreshClaudeProjects(home) {
142
+ const names = await claudeProjectNames(home);
143
+ const file = claudeProjectsFile();
144
+ await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
145
+ await atomicWrite(file, `${JSON.stringify({ at: new Date().toISOString(), names }, null, 2)}\n`, 0o600);
146
+ return names.length;
147
+ }
148
+ function claudeAi(entries) {
149
+ const names = projectNames(entries);
150
+ const conversations = new Map();
151
+ const texts = new Map();
152
+ for (const { url, json, at } of entries) {
153
+ const inProject = new RegExp(`/projects/(${UUID})/conversations_v2`).exec(url)?.[1];
154
+ if (inProject || /\/chat_conversations(_v2)?(\?|$)/.test(url))
155
+ for (const row of list(json)) {
156
+ const id = text(row.uuid);
157
+ if (!id)
158
+ continue;
159
+ const project = text(row.project_uuid) || text(object(row.project).uuid) || inProject;
160
+ conversations.set(id, {
161
+ id,
162
+ title: text(row.name),
163
+ url: `https://claude.ai/chat/${id}`,
164
+ updated: iso(row.updated_at),
165
+ projectName: (project && names.get(project)) ||
166
+ text(object(row.project).name) ||
167
+ undefined,
168
+ });
169
+ }
170
+ const org = /organizations\/([0-9a-f-]{36})\/memory(\?|$)/.exec(url)?.[1];
171
+ const memory = text(object(json).memory).trim();
172
+ if (org && memory) {
173
+ const label = `claude.ai memory (org ${org.slice(0, 8)})`;
174
+ const updated = Date.parse(text(object(json).updated_at)) || at;
175
+ if (updated >= (texts.get(label)?.at ?? -1))
176
+ texts.set(label, {
177
+ text: `# claude.ai memory\n\n${memory}`,
178
+ at: updated,
179
+ });
180
+ }
181
+ }
182
+ return { conversations, texts };
183
+ }
184
+ function chatGpt(entries) {
185
+ const conversations = new Map();
186
+ const texts = new Map();
187
+ const keep = (label, body, at) => {
188
+ if (body.trim() && at >= (texts.get(label)?.at ?? -1))
189
+ texts.set(label, { text: body, at });
190
+ };
191
+ for (const { url, json, at } of entries) {
192
+ const add = (row, projectName) => {
193
+ const id = text(row.id);
194
+ if (id)
195
+ conversations.set(id, {
196
+ id,
197
+ title: text(row.title),
198
+ url: `https://chatgpt.com/c/${id}`,
199
+ updated: iso(row.update_time),
200
+ projectName: projectName || undefined,
201
+ });
202
+ };
203
+ if (/\/backend-api\/conversations(\?|$)/.test(url))
204
+ for (const row of list(json))
205
+ add(row);
206
+ if (url.includes("/gizmos/snorlax/sidebar"))
207
+ for (const item of list(json)) {
208
+ const gizmo = object(object(item.gizmo).gizmo);
209
+ const name = text(object(gizmo.display).name);
210
+ for (const row of list(object(item).conversations))
211
+ add(row, name);
212
+ }
213
+ if (url.includes("/user_system_messages")) {
214
+ const about = text(object(json).about_user_message);
215
+ const respond = text(object(json).about_model_message);
216
+ keep("ChatGPT custom instructions", `# ChatGPT custom instructions\n\n${about ? `## About me\n\n${about}\n\n` : ""}${respond ? `## How to respond\n\n${respond}\n` : ""}`, about || respond ? at : -1);
217
+ }
218
+ if (/\/backend-api\/memories(\?|$)/.test(url)) {
219
+ const lines = list(object(json).memories)
220
+ .map((memory) => text(memory.content).trim())
221
+ .filter(Boolean);
222
+ if (lines.length)
223
+ keep("ChatGPT memory", `# ChatGPT memory\n\n${lines.map((line) => `- ${line}`).join("\n")}\n`, at);
224
+ }
225
+ }
226
+ return { conversations, texts };
227
+ }
228
+ const newer = (a, b) => (Date.parse(a ?? "") || 0) >= (Date.parse(b ?? "") || 0) ? a : b;
229
+ // A snapshot only grows: what the cache no longer holds stays, and a project set earlier (by hand
230
+ // or by a capture) is kept; a newer title or date replaces the old one.
231
+ async function mergeSnapshot(client, rows, write) {
232
+ const file = path.join(serverHistoryDir(), `${client}.jsonl`);
233
+ const known = new Map();
234
+ for (const line of (await readFile(file, "utf8").catch(() => "")).split("\n"))
235
+ try {
236
+ const row = JSON.parse(line);
237
+ if (row?.id)
238
+ known.set(row.id, row);
239
+ }
240
+ catch {
241
+ // an empty or foreign line
242
+ }
243
+ let added = 0;
244
+ for (const row of rows) {
245
+ const before = known.get(row.id);
246
+ if (!before)
247
+ added += 1;
248
+ known.set(row.id, before
249
+ ? {
250
+ ...before,
251
+ title: row.title || before.title,
252
+ updated: newer(row.updated, before.updated),
253
+ }
254
+ : row);
255
+ }
256
+ if (write && rows.length) {
257
+ await mkdir(serverHistoryDir(), { recursive: true, mode: 0o700 });
258
+ await atomicWrite(file, [...known.values()].map((row) => JSON.stringify(row)).join("\n") +
259
+ (known.size ? "\n" : ""), 0o600);
260
+ }
261
+ return { conversations: known.size, new: added };
262
+ }
263
+ export async function captureServerHistory(options) {
264
+ const entries = await readCaches(options.home);
265
+ const snapshots = {};
266
+ const imports = [];
267
+ for (const [client, tag, source, found] of [
268
+ [
269
+ "claude-ai",
270
+ "claude.ai",
271
+ "claude.ai",
272
+ claudeAi(entries.filter((entry) => entry.url.includes("claude.ai/"))),
273
+ ],
274
+ [
275
+ "chatgpt",
276
+ "chatgpt",
277
+ "chatgpt",
278
+ chatGpt(entries.filter((entry) => entry.url.includes("chatgpt.com/"))),
279
+ ],
280
+ ]) {
281
+ const rows = [...found.conversations.values()].map(({ projectName, ...row }) => {
282
+ // A conversation in a provider project goes where that project's name points.
283
+ const project = projectName
284
+ ? options.classify({
285
+ client: source,
286
+ file: row.url,
287
+ text: "",
288
+ name: projectName,
289
+ }).project
290
+ : undefined;
291
+ return {
292
+ ...row,
293
+ project: project ?? "unassigned",
294
+ tags: projectName ? [tag, projectName] : [tag],
295
+ };
296
+ });
297
+ snapshots[client] = await mergeSnapshot(client, rows, options.apply);
298
+ for (const [label, { text: body }] of found.texts)
299
+ imports.push({ label, text: body, source });
300
+ }
301
+ const items = await proposeImported(imports, {
302
+ apply: options.apply,
303
+ classify: options.classify,
304
+ });
305
+ return { snapshots, items };
306
+ }
307
+ //# sourceMappingURL=server-capture.js.map