@amoree-code/ocean 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) 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/dist/memory/signal-detection.js +66 -0
  112. package/node_modules/@ocean/brain/package.json +11 -0
  113. package/node_modules/@ocean/context/dist/context/context-compression.js +66 -0
  114. package/node_modules/@ocean/context/dist/context/context-cost.js +587 -0
  115. package/node_modules/@ocean/context/dist/context/context-ladder.js +363 -0
  116. package/node_modules/@ocean/context/dist/context/context-packet.js +213 -0
  117. package/node_modules/@ocean/context/dist/context/context-references.js +255 -0
  118. package/node_modules/@ocean/context/dist/context/intent-router.js +259 -0
  119. package/node_modules/@ocean/context/dist/context/resource-injection.js +36 -0
  120. package/node_modules/@ocean/context/package.json +11 -0
  121. package/node_modules/@ocean/core/dist/domain/brain/brain-record.js +22 -0
  122. package/node_modules/@ocean/core/dist/domain/brain/record-scope.js +22 -0
  123. package/node_modules/@ocean/core/dist/domain/capabilities/browser-contract.js +64 -0
  124. package/node_modules/@ocean/core/dist/domain/capabilities/capability-contract.js +16 -0
  125. package/node_modules/@ocean/core/dist/domain/capabilities/capability-manifest.js +72 -0
  126. package/node_modules/@ocean/core/dist/domain/context/context-validator.js +5 -0
  127. package/node_modules/@ocean/core/dist/domain/context/context.js +34 -0
  128. package/node_modules/@ocean/core/dist/domain/context/project-id.js +30 -0
  129. package/node_modules/@ocean/core/dist/domain/evidence/evidence.js +16 -0
  130. package/node_modules/@ocean/core/dist/domain/mcp/mcp-contract.js +36 -0
  131. package/node_modules/@ocean/core/dist/domain/mcp/memory-contract.js +41 -0
  132. package/node_modules/@ocean/core/dist/domain/ports/brain-index-port.js +7 -0
  133. package/node_modules/@ocean/core/dist/domain/ports/browser-port.js +8 -0
  134. package/node_modules/@ocean/core/dist/domain/ports/embedder-port.js +2 -0
  135. package/node_modules/@ocean/core/dist/domain/ports/platform-ports.js +2 -0
  136. package/node_modules/@ocean/core/dist/domain/ports/runtime-ports.js +2 -0
  137. package/node_modules/@ocean/core/dist/domain/ports/session-store-port.js +2 -0
  138. package/node_modules/@ocean/core/dist/domain/process/process-events.js +5 -0
  139. package/node_modules/@ocean/core/dist/domain/profiles/profile-distribution.js +19 -0
  140. package/node_modules/@ocean/core/dist/domain/profiles/profile-policy.js +34 -0
  141. package/node_modules/@ocean/core/dist/domain/profiles/profile-selection.js +73 -0
  142. package/node_modules/@ocean/core/dist/domain/profiles/profile-upgrade.js +123 -0
  143. package/node_modules/@ocean/core/dist/domain/profiles/profile-validator.js +5 -0
  144. package/node_modules/@ocean/core/dist/domain/profiles/profile.js +218 -0
  145. package/node_modules/@ocean/core/dist/domain/profiles/runtime-config.js +30 -0
  146. package/node_modules/@ocean/core/dist/domain/providers/provider-capability.js +28 -0
  147. package/node_modules/@ocean/core/dist/domain/providers/provider.js +2 -0
  148. package/node_modules/@ocean/core/dist/domain/redaction/redaction.js +86 -0
  149. package/node_modules/@ocean/core/dist/domain/runs/agent-tools.js +92 -0
  150. package/node_modules/@ocean/core/dist/domain/runs/execution-plan.js +71 -0
  151. package/node_modules/@ocean/core/dist/domain/runs/run-contract.js +38 -0
  152. package/node_modules/@ocean/core/dist/domain/sessions/entry-contract.js +31 -0
  153. package/node_modules/@ocean/core/dist/domain/sessions/handoff.js +78 -0
  154. package/node_modules/@ocean/core/dist/domain/sessions/session.js +58 -0
  155. package/node_modules/@ocean/core/dist/domain/skills/default-skills.js +29 -0
  156. package/node_modules/@ocean/core/dist/domain/skills/skill-validator.js +5 -0
  157. package/node_modules/@ocean/core/dist/domain/skills/skill.js +11 -0
  158. package/node_modules/@ocean/core/dist/domain/tasks/task-id.js +85 -0
  159. package/node_modules/@ocean/core/dist/shared/fs-utils.js +212 -0
  160. package/node_modules/@ocean/core/dist/shared/local-date.js +16 -0
  161. package/node_modules/@ocean/core/dist/shared/paths.js +324 -0
  162. package/node_modules/@ocean/core/dist/shared/project-resolution.js +1065 -0
  163. package/node_modules/@ocean/core/dist/shared/task-reference.js +133 -0
  164. package/node_modules/@ocean/core/package.json +14 -0
  165. package/node_modules/@ocean/loop/dist/handoff/handoff-service.js +133 -0
  166. package/node_modules/@ocean/loop/dist/hooks/lifecycle-hooks.js +15 -0
  167. package/node_modules/@ocean/loop/dist/hooks/session-end-hook.js +183 -0
  168. package/node_modules/@ocean/loop/dist/hooks/session-start-hook.js +237 -0
  169. package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-store.js +690 -0
  170. package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-validator.js +5 -0
  171. package/node_modules/@ocean/loop/dist/loops/iteration-graph.js +147 -0
  172. package/node_modules/@ocean/loop/dist/loops/task-loop-record.js +2 -0
  173. package/node_modules/@ocean/loop/dist/loops/task-loop.js +281 -0
  174. package/node_modules/@ocean/loop/dist/operations/memory-knowledge-operations.js +263 -0
  175. package/node_modules/@ocean/loop/dist/operations/operation-contract.js +227 -0
  176. package/node_modules/@ocean/loop/dist/operations/operation-scope.js +28 -0
  177. package/node_modules/@ocean/loop/dist/operations/project-operations.js +93 -0
  178. package/node_modules/@ocean/loop/dist/operations/record-io.js +65 -0
  179. package/node_modules/@ocean/loop/dist/operations/record-operations.js +45 -0
  180. package/node_modules/@ocean/loop/dist/operations/task-linker.js +40 -0
  181. package/node_modules/@ocean/loop/dist/operations/task-operations.js +279 -0
  182. package/node_modules/@ocean/loop/dist/operations/write-guard.js +157 -0
  183. package/node_modules/@ocean/loop/dist/runs/prompt-assembly.js +112 -0
  184. package/node_modules/@ocean/loop/dist/runs/run-agent.js +425 -0
  185. package/node_modules/@ocean/loop/dist/runs/run-authorization.js +24 -0
  186. package/node_modules/@ocean/loop/dist/runs/tool-loop.js +173 -0
  187. package/node_modules/@ocean/loop/dist/sessions/session-closeout.js +150 -0
  188. package/node_modules/@ocean/loop/dist/sessions/session-pointer.js +133 -0
  189. package/node_modules/@ocean/loop/dist/sessions/session-projects.js +26 -0
  190. package/node_modules/@ocean/loop/dist/sessions/session-retention.js +132 -0
  191. package/node_modules/@ocean/loop/dist/tasks/archive-tasks.js +210 -0
  192. package/node_modules/@ocean/loop/dist/tasks/checkpoint-task.js +31 -0
  193. package/node_modules/@ocean/loop/dist/tasks/claim-task.js +126 -0
  194. package/node_modules/@ocean/loop/dist/tasks/list-tasks.js +113 -0
  195. package/node_modules/@ocean/loop/dist/tasks/new-task.js +42 -0
  196. package/node_modules/@ocean/loop/dist/tasks/renumber-tasks.js +134 -0
  197. package/node_modules/@ocean/loop/dist/tasks/verification-readiness.js +78 -0
  198. package/node_modules/@ocean/loop/dist/tasks/verify-task.js +234 -0
  199. package/node_modules/@ocean/loop/package.json +11 -0
  200. package/node_modules/@ocean/market/dist/adapters/mcp-adapter.js +64 -0
  201. package/node_modules/@ocean/market/dist/adapters/owned-directory.js +89 -0
  202. package/node_modules/@ocean/market/dist/adapters/plugin-adapter.js +39 -0
  203. package/node_modules/@ocean/market/dist/adapters/skill-adapter.js +32 -0
  204. package/node_modules/@ocean/market/dist/capability/adapter.js +2 -0
  205. package/node_modules/@ocean/market/dist/capability/catalog.js +63 -0
  206. package/node_modules/@ocean/market/dist/capability/content-digest.js +40 -0
  207. package/node_modules/@ocean/market/dist/capability/registry.js +81 -0
  208. package/node_modules/@ocean/market/dist/install/default-marketplace.js +47 -0
  209. package/node_modules/@ocean/market/dist/install/lock.js +34 -0
  210. package/node_modules/@ocean/market/dist/install/marketplace-service.js +165 -0
  211. package/node_modules/@ocean/market/dist/mcp/browser-bridge.js +263 -0
  212. package/node_modules/@ocean/market/dist/mcp/mcp-connection.js +67 -0
  213. package/node_modules/@ocean/market/dist/mcp/mcp-gateway.js +317 -0
  214. package/node_modules/@ocean/market/dist/mcp/mcp-launch.js +102 -0
  215. package/node_modules/@ocean/market/dist/mcp/mcp-runner.js +32 -0
  216. package/node_modules/@ocean/market/dist/security/description-scan.js +45 -0
  217. package/node_modules/@ocean/market/dist/security/permissions.js +57 -0
  218. package/node_modules/@ocean/market/dist/security/sandbox.js +74 -0
  219. package/node_modules/@ocean/market/dist/skills/core-skill-sync.js +113 -0
  220. package/node_modules/@ocean/market/dist/skills/signal-detection.js +84 -0
  221. package/node_modules/@ocean/market/dist/skills/skill-curation.js +123 -0
  222. package/node_modules/@ocean/market/dist/skills/skill-hub.js +220 -0
  223. package/node_modules/@ocean/market/dist/skills/task-observer.js +165 -0
  224. package/node_modules/@ocean/market/package.json +11 -0
  225. package/node_modules/@ocean/services/dist/browser/browser-service.js +124 -0
  226. package/node_modules/@ocean/services/dist/browser/browser-session.js +287 -0
  227. package/node_modules/@ocean/services/dist/browser/browser-task-runner.js +70 -0
  228. package/node_modules/@ocean/services/dist/doctor/workspace-doctor.js +547 -0
  229. package/node_modules/@ocean/services/dist/infrastructure/mcp/call-ledger.js +114 -0
  230. package/node_modules/@ocean/services/dist/infrastructure/mcp/mcp-client.js +162 -0
  231. package/node_modules/@ocean/services/dist/infrastructure/mcp/obsidian-server.js +251 -0
  232. package/node_modules/@ocean/services/dist/infrastructure/mcp/ocean-server.js +880 -0
  233. package/node_modules/@ocean/services/dist/infrastructure/mcp/server-version.js +20 -0
  234. package/node_modules/@ocean/services/dist/infrastructure/mcp/tool-registry.js +29 -0
  235. package/node_modules/@ocean/services/dist/obsidian/conflict-log.js +68 -0
  236. package/node_modules/@ocean/services/dist/obsidian/inbox-promotion.js +80 -0
  237. package/node_modules/@ocean/services/dist/obsidian/vault-discovery.js +113 -0
  238. package/node_modules/@ocean/services/dist/obsidian/vault-ingestion.js +77 -0
  239. package/node_modules/@ocean/services/dist/obsidian/vault-sync.js +73 -0
  240. package/node_modules/@ocean/services/dist/obsidian/vault-writer.js +75 -0
  241. package/node_modules/@ocean/services/package.json +11 -0
  242. package/ocean-marketplace.json +415 -0
  243. package/package.json +67 -4
  244. package/skills/core/business-logic/SKILL.md +22 -0
  245. package/skills/core/catch-up/SKILL.md +17 -0
  246. package/skills/core/core-thinking/SKILL.md +11 -0
  247. package/skills/core/design-thinking/SKILL.md +19 -0
  248. package/skills/core/graft/SKILL.md +156 -0
  249. package/skills/core/loop/SKILL.md +20 -0
  250. package/skills/core/session-handoff/SKILL.md +15 -0
  251. package/skills/core/use-browser/SKILL.md +69 -0
  252. package/skills/core/verification/SKILL.md +11 -0
  253. package/skills/index.json +56 -0
  254. package/templates/README.md +4 -0
  255. package/templates/profiles/default.json +51 -0
  256. package/templates/profiles/developer.json +51 -0
  257. package/templates/profiles/reviewer.json +51 -0
  258. package/templates/profiles/strategist.json +51 -0
  259. package/templates/workspace/00-inbox/INBOX.md +7 -0
  260. package/templates/workspace/00-inbox/candidates/.gitkeep +0 -0
  261. package/templates/workspace/00-inbox/native/.gitkeep +0 -0
  262. package/templates/workspace/01-daily/.gitkeep +0 -0
  263. package/templates/workspace/02-personal/CONFLICTS.md +7 -0
  264. package/templates/workspace/02-personal/MEMORY.md +15 -0
  265. package/templates/workspace/02-personal/goals/.gitkeep +0 -0
  266. package/templates/workspace/02-personal/identity/.gitkeep +0 -0
  267. package/templates/workspace/02-personal/preferences/.gitkeep +0 -0
  268. package/templates/workspace/03-professional/.gitkeep +0 -0
  269. package/templates/workspace/04-projects/PROJECTS.md +5 -0
  270. package/templates/workspace/04-projects/_template/docs/.gitkeep +0 -0
  271. package/templates/workspace/04-projects/_template/memory/.gitkeep +0 -0
  272. package/templates/workspace/04-projects/_template/tasks/.gitkeep +0 -0
  273. package/templates/workspace/05-knowledge/KNOWLEDGE.md +8 -0
  274. package/templates/workspace/05-knowledge/decisions/.gitkeep +0 -0
  275. package/templates/workspace/05-knowledge/lessons/.gitkeep +0 -0
  276. package/templates/workspace/06-templates/TEMPLATES.md +25 -0
  277. package/templates/workspace/06-templates/decision.md +8 -0
  278. package/templates/workspace/06-templates/intelligence.md +32 -0
  279. package/templates/workspace/06-templates/project-claude.md +48 -0
  280. package/templates/workspace/06-templates/session.md +38 -0
  281. package/templates/workspace/06-templates/task.md +42 -0
  282. package/templates/workspace/MANIFEST.json +51 -0
  283. package/templates/workspace/bridge/config/CONFIG.md +21 -0
  284. package/templates/workspace/bridge/config/runtime.json +4 -0
  285. package/templates/workspace/charter/core.md +104 -0
  286. package/templates/workspace/charter/policies/README.md +31 -0
  287. package/templates/workspace/charter/policies/claude-code-context.md +90 -0
  288. package/templates/workspace/charter/policies/concise-output.md +29 -0
  289. package/templates/workspace/charter/policies/context.md +91 -0
  290. package/templates/workspace/charter/policies/contracts/task.md +189 -0
  291. package/templates/workspace/charter/policies/git.md +60 -0
  292. package/templates/workspace/charter/policies/graph.md +47 -0
  293. package/templates/workspace/charter/policies/knowledge.md +33 -0
  294. package/templates/workspace/charter/policies/lifecycle.md +119 -0
  295. package/templates/workspace/charter/policies/memory.md +67 -0
  296. package/templates/workspace/charter/policies/minimal-change.md +43 -0
  297. package/templates/workspace/charter/policies/models.md +57 -0
  298. package/templates/workspace/charter/policies/privacy-terms.txt +3 -0
  299. package/templates/workspace/charter/policies/response.md +39 -0
  300. package/templates/workspace/charter/policies/skill-observer.md +44 -0
  301. package/templates/workspace/charter/policies/stack.md +23 -0
  302. package/templates/workspace/charter/policies/task.md +139 -0
  303. package/templates/workspace/charter/policies/verification.md +29 -0
  304. package/templates/workspace/sessions/.gitkeep +0 -0
@@ -0,0 +1,646 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createHash, randomBytes } from "node:crypto";
3
+ import { accessSync, constants, existsSync, realpathSync } from "node:fs";
4
+ import { appendFile, mkdir, readFile } from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { clientJournalFile } from "@ocean/brain/memory/memory-paths.js";
8
+ import { containsKnownSecret } from "@ocean/core/domain/redaction/redaction.js";
9
+ import { atomicWrite, withFileLock } from "@ocean/core/shared/fs-utils.js";
10
+ import { liveEngineEntry, oceanPath, SYSTEM_DIR, } from "@ocean/core/shared/paths.js";
11
+ import { ENTRY_KEYS, ManualEdit, } from "./editors.js";
12
+ import { CLIENT_ADAPTERS, mcpClientOf, } from "./registry.js";
13
+ // A node inside a version manager's tree disappears when that version is removed (T-307).
14
+ const VERSIONED_NODE = /[\\/](?:node-versions|\.nvm[\\/]versions[\\/]node|\.volta[\\/]tools[\\/]image[\\/]node|\.asdf[\\/]installs[\\/]nodejs|mise[\\/]installs[\\/]node|n[\\/]versions[\\/]node)[\\/]/;
15
+ export function isVersionedNode(execPath) {
16
+ return VERSIONED_NODE.test(execPath);
17
+ }
18
+ // The `ocean` MCP entry. With a version-managed node it launches through the `ocean` shim, which
19
+ // falls back to another node when that version is gone; a temporary engine keeps the direct form
20
+ // so `temporaryEntryProblem` still refuses it.
21
+ export function oceanServerEntry(execPath = process.execPath, engine = liveEngineEntry(), platform = process.platform) {
22
+ const direct = { command: execPath, args: [engine, "mcp", "serve"] };
23
+ if (platform === "win32" ||
24
+ !isVersionedNode(execPath) ||
25
+ temporaryEntryProblem(direct))
26
+ return direct;
27
+ return {
28
+ command: oceanPath(SYSTEM_DIR, "runtime", "shims", "ocean"),
29
+ args: ["mcp", "serve"],
30
+ };
31
+ }
32
+ export function defaultClientEnv() {
33
+ const home = os.homedir();
34
+ return {
35
+ home,
36
+ pathDirs: (process.env.PATH ?? "").split(path.delimiter).filter(Boolean),
37
+ appDirs: process.platform === "darwin"
38
+ ? ["/Applications", path.join(home, "Applications")]
39
+ : [],
40
+ entry: oceanServerEntry(),
41
+ };
42
+ }
43
+ const sha = (value) => createHash("sha256").update(value).digest("hex");
44
+ function executable(file) {
45
+ try {
46
+ accessSync(file, constants.X_OK);
47
+ return true;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ function detect(adapter, env) {
54
+ const evidence = [];
55
+ for (const name of adapter.detect.binaries ?? [])
56
+ for (const dir of env.pathDirs)
57
+ if (executable(path.join(dir, name))) {
58
+ evidence.push(`binary ${name}`);
59
+ break;
60
+ }
61
+ for (const app of adapter.detect.apps ?? [])
62
+ if (env.appDirs.some((dir) => existsSync(path.join(dir, app))))
63
+ evidence.push(`app ${app}`);
64
+ for (const relative of adapter.detect.paths ?? [])
65
+ if (existsSync(path.join(env.home, relative)))
66
+ evidence.push(`~/${relative}`);
67
+ return evidence;
68
+ }
69
+ // A TOML entry with sub-tables carries a `__subtables` marker, which fails the key check too.
70
+ const isOceanShaped = (value) => Boolean(value) &&
71
+ Object.keys(value ?? {}).every((key) => ENTRY_KEYS.includes(key));
72
+ const sameEntry = (value, entry) => Boolean(value) &&
73
+ value?.command === entry.command &&
74
+ JSON.stringify(value?.args ?? []) === JSON.stringify(entry.args ?? []);
75
+ const runsObsidian = (value) => Array.isArray(value.args) && value.args.includes("obsidian");
76
+ // The retired atlas gateway: an entry that names it and whose program no longer exists.
77
+ function staleAtlas(key, value) {
78
+ const command = typeof value.command === "string" ? value.command : "";
79
+ return ((/atlas/i.test(key) || /atlas/i.test(command)) &&
80
+ path.isAbsolute(command) &&
81
+ !existsSync(command));
82
+ }
83
+ // Windows' tmpdir can be an 8.3 short path (C:\\Users\\RUNNER~1\\...) while an engine's path is the
84
+ // long form of the same directory.
85
+ function realTemporaryDir() {
86
+ try {
87
+ return [realpathSync.native(os.tmpdir())];
88
+ }
89
+ catch {
90
+ return [];
91
+ }
92
+ }
93
+ // A build in a temporary directory (a scratch worktree) disappears with it, and every client
94
+ // wired to it would then fail to start Ocean.
95
+ const TEMPORARY_ROOTS = [
96
+ os.tmpdir(),
97
+ ...realTemporaryDir(),
98
+ "/tmp",
99
+ "/private/tmp",
100
+ "/var/folders",
101
+ "/private/var/folders",
102
+ ];
103
+ // `paths` is a parameter so the Windows rules (separator, case, drives) are testable anywhere.
104
+ export function isWithinDir(dir, file, paths = path) {
105
+ const relative = paths.relative(dir, file);
106
+ return (relative !== ".." &&
107
+ !relative.startsWith(`..${paths.sep}`) &&
108
+ !paths.isAbsolute(relative));
109
+ }
110
+ export function temporaryEntryProblem(entry) {
111
+ const program = Array.isArray(entry.args) ? entry.args[0] : undefined;
112
+ if (typeof program !== "string")
113
+ return null;
114
+ const resolved = path.resolve(program);
115
+ const root = TEMPORARY_ROOTS.find((dir) => isWithinDir(dir, resolved));
116
+ return root
117
+ ? `the Ocean engine ${resolved} is in a temporary directory (${root}); run this from the workspace kernel checkout`
118
+ : null;
119
+ }
120
+ async function readText(file) {
121
+ try {
122
+ return await readFile(file, "utf8");
123
+ }
124
+ catch (error) {
125
+ if (error.code === "ENOENT")
126
+ return null;
127
+ throw error;
128
+ }
129
+ }
130
+ // The ops that bring one config text to the desired state, or why it needs a hand edit.
131
+ function opsFor(adapter, text, entry) {
132
+ const editor = adapter.config?.editor;
133
+ const servers = editor?.servers(text);
134
+ if (!editor || !servers)
135
+ return {
136
+ ops: [],
137
+ manual: adapter.reason ?? "the server table cannot be read",
138
+ };
139
+ const ops = [];
140
+ const ocean = servers.get("ocean");
141
+ const legacy = ocean ? runsObsidian(ocean) : false;
142
+ if (ocean && legacy) {
143
+ if (!isOceanShaped(ocean))
144
+ return {
145
+ ops: [],
146
+ manual: "the legacy `ocean` entry has keys Ocean does not manage",
147
+ };
148
+ // With an `ocean-obsidian` already there, the legacy entry is simply replaced in place below.
149
+ if (!servers.has("ocean-obsidian"))
150
+ ops.push({ kind: "rename", key: "ocean", to: "ocean-obsidian" });
151
+ }
152
+ for (const [key, value] of servers)
153
+ if (key !== "ocean" && staleAtlas(key, value) && isOceanShaped(value))
154
+ ops.push({ kind: "remove", key });
155
+ if (!ocean || legacy)
156
+ ops.push({ kind: "set", key: "ocean", value: entry });
157
+ else if (!isOceanShaped(ocean))
158
+ return {
159
+ ops: [],
160
+ manual: "the `ocean` entry has keys Ocean does not manage",
161
+ };
162
+ else if (!sameEntry(ocean, entry))
163
+ ops.push({ kind: "set", key: "ocean", value: entry });
164
+ return { ops };
165
+ }
166
+ export async function planClients(env = defaultClientEnv()) {
167
+ const plans = [];
168
+ for (const adapter of CLIENT_ADAPTERS) {
169
+ const evidence = detect(adapter, env);
170
+ const plan = {
171
+ client: adapter.id,
172
+ label: adapter.label,
173
+ tier: adapter.tier,
174
+ installed: evidence.length > 0,
175
+ evidence,
176
+ file: adapter.config ? `~/${adapter.config.file}` : undefined,
177
+ status: "current",
178
+ ops: [],
179
+ nativeMemory: adapter.nativeMemory,
180
+ reason: adapter.reason,
181
+ fallback: adapter.fallback,
182
+ };
183
+ if (!plan.installed)
184
+ plan.status = "not-installed";
185
+ else if (adapter.tier !== "supported" || !adapter.config)
186
+ plan.status = adapter.tier === "supported" ? "unknown" : adapter.tier;
187
+ else {
188
+ const text = (await readText(path.join(env.home, adapter.config.file))) ?? "";
189
+ const { ops, manual } = opsFor(adapter, text, env.entry);
190
+ plan.ops = ops;
191
+ plan.status = ops.length ? "change" : "current";
192
+ if (!manual && ops.length)
193
+ try {
194
+ adapter.config.editor.apply(text, ops);
195
+ }
196
+ catch (error) {
197
+ if (!(error instanceof ManualEdit))
198
+ throw error;
199
+ plan.reason = error.message;
200
+ plan.status = "manual";
201
+ }
202
+ if (manual) {
203
+ plan.reason = manual;
204
+ plan.status = "manual";
205
+ }
206
+ if (plan.status === "manual")
207
+ plan.snippet = JSON.stringify({ ocean: env.entry });
208
+ }
209
+ plans.push(plan);
210
+ }
211
+ return plans;
212
+ }
213
+ function stateFile() {
214
+ return oceanPath(SYSTEM_DIR, "state", "clients", "clients.json");
215
+ }
216
+ // How far a client's use of Ocean is proven (T-307). Only `in-app` — the client's model searched
217
+ // Ocean during normal use and cited the record — counts as done; `blocked` names why that check
218
+ // cannot run yet. A confirmation recorded before labels existed has none and proves nothing.
219
+ export const EVIDENCE_LABELS = [
220
+ "in-app",
221
+ "headless",
222
+ "connected",
223
+ "blocked",
224
+ ];
225
+ export async function readClientState() {
226
+ const text = await readText(stateFile());
227
+ const parsed = text ? JSON.parse(text) : {};
228
+ return {
229
+ approved: parsed.approved ?? {},
230
+ verified: parsed.verified ?? {},
231
+ confirmed: parsed.confirmed ?? {},
232
+ };
233
+ }
234
+ async function approvedClients() {
235
+ return (await readClientState()).approved;
236
+ }
237
+ async function updateClientState(change) {
238
+ const file = stateFile();
239
+ await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
240
+ await withFileLock(file, async () => {
241
+ const state = await readClientState();
242
+ change(state);
243
+ await atomicWrite(file, `${JSON.stringify(state, null, 2)}\n`, 0o600);
244
+ });
245
+ }
246
+ async function approve(ids) {
247
+ if (!ids.length)
248
+ return;
249
+ await updateClientState((state) => {
250
+ for (const id of ids)
251
+ state.approved[id] = { approvedAt: new Date().toISOString() };
252
+ });
253
+ }
254
+ export async function recordVerification(client, ok) {
255
+ await updateClientState((state) => {
256
+ state.verified[client] = { verifiedAt: new Date().toISOString(), ok };
257
+ });
258
+ }
259
+ const EVIDENCE_TEXT_MAX = 200;
260
+ function evidenceText(value) {
261
+ const text = value?.replace(/\s+/g, " ").trim();
262
+ return text ? text.slice(0, EVIDENCE_TEXT_MAX) : undefined;
263
+ }
264
+ // The call ledger keeps the raw MCP client name. Each row also names the Ocean client it
265
+ // belongs to and, where that client's modes send different names, the mode (T-307).
266
+ export function attributeLedger(rows) {
267
+ return rows.map((row) => {
268
+ const known = mcpClientOf(row.client);
269
+ if (!known)
270
+ return row;
271
+ return {
272
+ ...row,
273
+ oceanClient: known.id,
274
+ ...(known.mode ? { mode: known.mode } : {}),
275
+ };
276
+ });
277
+ }
278
+ // Records what was proven about a client's use of Ocean, and how.
279
+ export async function confirmClient(client, evidence) {
280
+ if (!CLIENT_ADAPTERS.some((adapter) => adapter.id === client))
281
+ throw new Error(`unknown client '${client}'`);
282
+ if (!EVIDENCE_LABELS.includes(evidence.label))
283
+ throw new Error(`unknown evidence label '${evidence.label}' (use ${EVIDENCE_LABELS.join(", ")})`);
284
+ const via = evidenceText(evidence.via);
285
+ const reason = evidenceText(evidence.reason);
286
+ if (evidence.label === "in-app" && !via)
287
+ throw new Error("an in-app confirmation needs --via <evidence>");
288
+ if (evidence.label === "blocked" && !reason)
289
+ throw new Error("a blocked client needs --reason <why>");
290
+ await updateClientState((state) => {
291
+ state.confirmed[client] = {
292
+ confirmedAt: new Date().toISOString(),
293
+ label: evidence.label,
294
+ ...(via ? { via } : {}),
295
+ ...(reason ? { reason } : {}),
296
+ };
297
+ });
298
+ }
299
+ export async function appendJournal(entries) {
300
+ if (!entries.length)
301
+ return;
302
+ const file = clientJournalFile();
303
+ await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
304
+ await withFileLock(file, () => appendFile(file, entries.map((entry) => `${JSON.stringify(entry)}\n`).join(""), { mode: 0o600 }));
305
+ }
306
+ export async function readJournal() {
307
+ const text = await readText(clientJournalFile());
308
+ return (text ?? "")
309
+ .split("\n")
310
+ .filter(Boolean)
311
+ .map((line) => JSON.parse(line));
312
+ }
313
+ // A value goes into the journal only when it is Ocean-shaped and holds no secret; otherwise
314
+ // only its hash, and restoring it can only remove Ocean's entry.
315
+ function journalValue(value) {
316
+ if (!value)
317
+ return { before: null };
318
+ const text = JSON.stringify(value);
319
+ if (isOceanShaped(value) && !containsKnownSecret(text))
320
+ return { before: value };
321
+ return { before: null, beforeSha: sha(text) };
322
+ }
323
+ function simulate(state, op) {
324
+ if (op.kind === "set")
325
+ state.set(op.key, op.value);
326
+ else if (op.kind === "remove")
327
+ state.delete(op.key);
328
+ else {
329
+ const value = state.get(op.key);
330
+ state.delete(op.key);
331
+ if (value)
332
+ state.set(op.to, value);
333
+ }
334
+ }
335
+ export const newBatch = () => `${new Date()
336
+ .toISOString()
337
+ .replace(/[-:TZ]/g, "")
338
+ .slice(0, 14)}-${randomBytes(4).toString("hex")}`;
339
+ // Writes one client file: re-reads it right before the rename and recomputes when it changed
340
+ // meanwhile, so an edit the user (or the client) made in between is never overwritten.
341
+ // Exported for its test only.
342
+ export async function edit(file, change, batch) {
343
+ for (let attempt = 0; attempt < 3; attempt++) {
344
+ const text = await readText(file);
345
+ const result = change(text ?? "");
346
+ if (!result)
347
+ return "unchanged";
348
+ await mkdir(path.dirname(file), { recursive: true });
349
+ const outcome = await withFileLock(file, async () => {
350
+ if ((await readText(file)) !== text)
351
+ return "retry";
352
+ await atomicWrite(file, result.next, text === null ? 0o600 : undefined);
353
+ return "written";
354
+ });
355
+ if (outcome === "retry")
356
+ continue;
357
+ const ts = new Date().toISOString();
358
+ await appendJournal(result.entries.map((entry) => ({ ...entry, batch, ts })));
359
+ return "written";
360
+ }
361
+ throw new Error(`${file} kept changing while Ocean tried to edit it; try again`);
362
+ }
363
+ // `durable` (default: on for this machine's real env) refuses an engine in a temporary directory.
364
+ export async function applyClients(options = {}) {
365
+ const env = options.env ?? defaultClientEnv();
366
+ if (options.durable ?? !options.env) {
367
+ const problem = temporaryEntryProblem(env.entry);
368
+ if (problem)
369
+ throw new Error(`Refusing to wire clients: ${problem}`);
370
+ }
371
+ const batch = options.batch ?? newBatch();
372
+ const results = [];
373
+ for (const plan of await planClients(env)) {
374
+ if (options.clients && !options.clients.includes(plan.client))
375
+ continue;
376
+ if (plan.status !== "change") {
377
+ if (options.clients)
378
+ results.push({ client: plan.client, result: plan.status });
379
+ continue;
380
+ }
381
+ const adapter = CLIENT_ADAPTERS.find((item) => item.id === plan.client);
382
+ if (!adapter?.config)
383
+ continue;
384
+ const config = adapter.config;
385
+ const file = path.join(env.home, config.file);
386
+ const result = await edit(file, (text) => {
387
+ const { ops, manual } = opsFor(adapter, text, env.entry);
388
+ if (manual || !ops.length)
389
+ return null;
390
+ // Each op's "before" is the table as the earlier ops of this batch left it.
391
+ const state = new Map(config.editor.servers(text) ?? []);
392
+ const entries = ops.map((op) => {
393
+ const entry = {
394
+ client: adapter.id,
395
+ file: config.file,
396
+ op: op.kind,
397
+ key: op.key,
398
+ to: op.kind === "rename" ? op.to : undefined,
399
+ ...(op.kind === "rename"
400
+ ? { before: null }
401
+ : journalValue(state.get(op.key))),
402
+ after: op.kind === "set" ? op.value : null,
403
+ };
404
+ simulate(state, op);
405
+ return entry;
406
+ });
407
+ return { next: config.editor.apply(text, ops), entries };
408
+ }, batch);
409
+ results.push({ client: plan.client, result });
410
+ }
411
+ await approve(results
412
+ .filter((item) => item.result === "written")
413
+ .map((item) => item.client));
414
+ return { batch, results };
415
+ }
416
+ // Re-applies the desired entry for clients the owner already approved; never a new client.
417
+ export async function repairClients(env) {
418
+ const approved = Object.keys(await approvedClients());
419
+ if (!approved.length)
420
+ return { batch: null, results: [] };
421
+ return applyClients({ clients: approved, env });
422
+ }
423
+ // Undoes one apply batch (default: the latest), entry by entry in reverse. An entry whose
424
+ // node no longer holds what Ocean wrote is a conflict and is left alone.
425
+ export async function restoreClients(options = {}) {
426
+ const env = options.env ?? defaultClientEnv();
427
+ const journal = await readJournal();
428
+ const batch = options.batch ??
429
+ [...journal].reverse().find((entry) => !entry.restoreOf)?.batch ??
430
+ null;
431
+ const restored = [];
432
+ const conflicts = [];
433
+ if (!batch)
434
+ return { batch, restored, conflicts };
435
+ const entries = journal
436
+ .filter((entry) => entry.batch === batch &&
437
+ entry.op !== "hook-add" &&
438
+ entry.op !== "native-set")
439
+ .reverse();
440
+ const undo = newBatch();
441
+ for (const file of [...new Set(entries.map((entry) => entry.file))]) {
442
+ const adapter = CLIENT_ADAPTERS.find((item) => item.config?.file === file);
443
+ if (!adapter?.config)
444
+ continue;
445
+ const config = adapter.config;
446
+ // Collected per attempt: edit() may run the change again when the file moved under it.
447
+ let fileRestored = [];
448
+ let fileConflicts = [];
449
+ await edit(path.join(env.home, file), (text) => {
450
+ fileRestored = [];
451
+ fileConflicts = [];
452
+ const restored = fileRestored;
453
+ const conflicts = fileConflicts;
454
+ const read = config.editor.servers(text);
455
+ if (!read) {
456
+ conflicts.push(`${adapter.id}: config no longer readable`);
457
+ return null;
458
+ }
459
+ const servers = new Map(read);
460
+ const ops = [];
461
+ const queue = (op) => {
462
+ ops.push(op);
463
+ simulate(servers, op);
464
+ };
465
+ const notes = [];
466
+ for (const entry of entries.filter((item) => item.file === file)) {
467
+ const label = `${adapter.id}:${entry.key}`;
468
+ if (entry.op === "rename" && entry.to) {
469
+ if (servers.has(entry.to) && !servers.has(entry.key)) {
470
+ queue({ kind: "rename", key: entry.to, to: entry.key });
471
+ restored.push(label);
472
+ }
473
+ else
474
+ conflicts.push(label);
475
+ }
476
+ else if (entry.op === "set") {
477
+ if (!sameEntry(servers.get(entry.key), entry.after ?? {})) {
478
+ conflicts.push(label);
479
+ continue;
480
+ }
481
+ if (entry.before)
482
+ queue({ kind: "set", key: entry.key, value: entry.before });
483
+ else
484
+ queue({ kind: "remove", key: entry.key });
485
+ restored.push(entry.beforeSha
486
+ ? `${label} (removed; original not stored)`
487
+ : label);
488
+ }
489
+ else if (entry.op === "remove") {
490
+ if (servers.has(entry.key) || !entry.before) {
491
+ conflicts.push(label);
492
+ continue;
493
+ }
494
+ queue({ kind: "set", key: entry.key, value: entry.before });
495
+ restored.push(label);
496
+ }
497
+ notes.push({ ...entry, restoreOf: batch });
498
+ }
499
+ if (!ops.length)
500
+ return null;
501
+ return { next: config.editor.apply(text, ops), entries: notes };
502
+ }, undo);
503
+ restored.push(...fileRestored);
504
+ conflicts.push(...fileConflicts);
505
+ }
506
+ return { batch, restored, conflicts };
507
+ }
508
+ // Starts the client's configured `ocean` command and checks it answers as the brain server.
509
+ // This proves the entry launches; it does not prove the client app connects to it.
510
+ export async function verifyClient(client, env = defaultClientEnv()) {
511
+ const adapter = CLIENT_ADAPTERS.find((item) => item.id === client);
512
+ if (!adapter?.config)
513
+ return { client, ok: false, detail: "no MCP config" };
514
+ const text = await readText(path.join(env.home, adapter.config.file));
515
+ const entry = text
516
+ ? adapter.config.editor.servers(text)?.get("ocean")
517
+ : undefined;
518
+ if (!entry || typeof entry.command !== "string")
519
+ return { client, ok: false, detail: "no `ocean` entry" };
520
+ return new Promise((resolve) => {
521
+ const child = spawn(entry.command, entry.args ?? [], {
522
+ stdio: ["pipe", "pipe", "ignore"],
523
+ });
524
+ let output = "";
525
+ let done = false;
526
+ const finish = (ok, detail) => {
527
+ if (done)
528
+ return;
529
+ done = true;
530
+ clearTimeout(timer);
531
+ child.kill();
532
+ resolve({ client, ok, detail });
533
+ };
534
+ const timer = setTimeout(() => finish(false, "no answer within 15 s"), 15_000);
535
+ child.on("error", (error) => finish(false, error.message));
536
+ child.on("exit", (code) => finish(false, `exited with code ${code} before answering`));
537
+ child.stdout.on("data", (chunk) => {
538
+ output += chunk.toString("utf8");
539
+ // Only complete lines: a large reply (tools/list) arrives in several chunks.
540
+ const replies = output
541
+ .split("\n")
542
+ .slice(0, -1)
543
+ .filter((line) => line.startsWith("{"))
544
+ .map((line) => JSON.parse(line));
545
+ const init = replies.find((reply) => reply.id === 1);
546
+ const list = replies.find((reply) => reply.id === 2);
547
+ if (!init || !list)
548
+ return;
549
+ const name = init.result?.serverInfo
550
+ ?.name;
551
+ const tools = (list.result?.tools ?? []).map((tool) => tool.name);
552
+ const ok = name === "ocean" &&
553
+ tools.includes("brain_search") &&
554
+ tools.includes("memory_write");
555
+ finish(ok, ok
556
+ ? "answers as the ocean brain server with memory tools"
557
+ : `serverInfo ${name}, ${tools.length} tools`);
558
+ });
559
+ child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { clientInfo: { name: "ocean-verify" } } })}\n${JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list" })}\n`);
560
+ });
561
+ }
562
+ // Doctor view: a supported client that is installed but not wired fails; unsupported clients
563
+ // are reported with their fallback; an unknown capability is a warning, never a pass. On this
564
+ // machine's real env, an engine in a temporary directory is named first: every plan below then
565
+ // points at a build that will disappear (T-300).
566
+ // A wired client's status says only what its evidence proves: "checked in the app" is reserved
567
+ // for an in-app confirmation.
568
+ function evidenceFinding(name, client, record) {
569
+ const confirm = `\`ocean clients confirm ${client} --label in-app --via <evidence>\``;
570
+ const date = record?.confirmedAt.slice(0, 10);
571
+ if (record?.label === "in-app")
572
+ return {
573
+ code: "CLIENT_WIRED",
574
+ severity: "OK",
575
+ message: `${name}: \`ocean\` entry current, checked in the app ${date} (${record.via})`,
576
+ fixable: false,
577
+ };
578
+ if (record?.label === "blocked")
579
+ return {
580
+ code: "CLIENT_BLOCKED",
581
+ severity: "WARN",
582
+ message: `${name}: \`ocean\` entry current; in-app check blocked since ${date}: ${record.reason}`,
583
+ fixable: false,
584
+ };
585
+ const proven = record?.label
586
+ ? `${record.label} only (${date}${record.via ? `: ${record.via}` : ""})`
587
+ : record
588
+ ? `confirmed ${date} without an evidence label`
589
+ : "in-app check not recorded";
590
+ return {
591
+ code: "CLIENT_CHECK_PENDING",
592
+ severity: "WARN",
593
+ message: `${name}: \`ocean\` entry current; ${proven} — ask it a question that needs Ocean, then ${confirm}`,
594
+ fixable: false,
595
+ };
596
+ }
597
+ export async function clientFindings(env) {
598
+ const findings = [];
599
+ const problem = env ? null : temporaryEntryProblem(defaultClientEnv().entry);
600
+ if (problem)
601
+ findings.push({
602
+ code: "CLIENT_ENGINE_TEMPORARY",
603
+ severity: "WARN",
604
+ message: `the client plans below are for a temporary engine — ${problem}`,
605
+ fixable: false,
606
+ });
607
+ env ??= defaultClientEnv();
608
+ const { confirmed } = await readClientState();
609
+ for (const plan of await planClients(env)) {
610
+ if (!plan.installed)
611
+ continue;
612
+ const name = `${plan.label} (${plan.client})`;
613
+ if (plan.status === "current")
614
+ findings.push(evidenceFinding(name, plan.client, confirmed[plan.client]));
615
+ else if (plan.status === "change")
616
+ findings.push({
617
+ code: "CLIENT_NOT_WIRED",
618
+ severity: "FAIL",
619
+ message: `${name}: ${plan.ops.map((op) => `${op.kind} ${op.key}`).join(", ")} pending in ${plan.file} — run \`ocean clients apply --yes\``,
620
+ fixable: true,
621
+ });
622
+ else if (plan.status === "manual")
623
+ findings.push({
624
+ code: "CLIENT_MANUAL",
625
+ severity: "FAIL",
626
+ message: `${name}: needs a hand edit (${plan.reason}); add ${plan.snippet}`,
627
+ fixable: false,
628
+ });
629
+ else if (plan.status === "unknown")
630
+ findings.push({
631
+ code: "CLIENT_UNKNOWN",
632
+ severity: "WARN",
633
+ message: `${name}: capability unknown — ${plan.reason}`,
634
+ fixable: false,
635
+ });
636
+ else
637
+ findings.push({
638
+ code: "CLIENT_UNSUPPORTED",
639
+ severity: "OK",
640
+ message: `${name}: ${plan.status} — ${plan.reason}; fallback: ${plan.fallback}`,
641
+ fixable: false,
642
+ });
643
+ }
644
+ return findings;
645
+ }
646
+ //# sourceMappingURL=clients.js.map