synabun 1.3.0 → 2.0.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 (1163) hide show
  1. package/CHANGELOG.md +205 -662
  2. package/LICENSE-COMMERCIAL.md +33 -0
  3. package/NOTICE +17 -0
  4. package/README.md +175 -853
  5. package/THIRD-PARTY-LICENSES.md +136 -0
  6. package/benchmarks/browser-live-results.json +79 -0
  7. package/benchmarks/browser-v2-results.json +258 -0
  8. package/benchmarks/fixtures/browser/README.md +57 -0
  9. package/benchmarks/fixtures/browser/page-state.json +132 -0
  10. package/benchmarks/fixtures/browser/social.json +92 -0
  11. package/benchmarks/fixtures/browser/target.json +319 -0
  12. package/benchmarks/fixtures/desktop/README.md +58 -0
  13. package/benchmarks/fixtures/desktop/abstention.json +59 -0
  14. package/benchmarks/fixtures/desktop/adversarial.json +22 -0
  15. package/benchmarks/fixtures/desktop/forbidden.json +93 -0
  16. package/benchmarks/fixtures/desktop/generate.mjs +1023 -0
  17. package/benchmarks/fixtures/desktop/target.json +136 -0
  18. package/bin/synabun.js +30 -0
  19. package/docs/whatsapp.md +325 -0
  20. package/hooks/claude-code/post-plan.mjs +613 -391
  21. package/hooks/claude-code/post-remember.mjs +246 -122
  22. package/hooks/claude-code/pre-compact.mjs +171 -110
  23. package/hooks/claude-code/pre-task.mjs +133 -0
  24. package/hooks/claude-code/pre-websearch.mjs +15 -4
  25. package/hooks/claude-code/prompt-origin.mjs +94 -0
  26. package/hooks/claude-code/prompt-submit.mjs +1244 -861
  27. package/hooks/claude-code/session-start.mjs +114 -156
  28. package/hooks/claude-code/shared.mjs +1228 -368
  29. package/hooks/claude-code/state.mjs +167 -0
  30. package/hooks/claude-code/stop.mjs +515 -140
  31. package/hooks/claude-code/subagent-start.mjs +154 -0
  32. package/hooks/claude-code/subagent-stop.mjs +102 -0
  33. package/hooks/claude-code/task-gate.mjs +150 -0
  34. package/launcher.mjs +40 -0
  35. package/lib/claude-hooks.js +137 -0
  36. package/lib/claude-usage-audit.js +141 -0
  37. package/lib/client-config-repair.js +145 -0
  38. package/lib/data-home-migration.js +502 -0
  39. package/lib/desktop-pwa.js +206 -0
  40. package/lib/external-tools.js +221 -0
  41. package/lib/first-launch-protection.js +245 -0
  42. package/lib/packaged-runtime.js +110 -0
  43. package/lib/paths.d.ts +21 -0
  44. package/lib/paths.js +103 -0
  45. package/lib/start-launcher.js +1309 -0
  46. package/mcp-server/dist/config.d.ts +22 -0
  47. package/mcp-server/dist/config.d.ts.map +1 -0
  48. package/mcp-server/dist/config.js +109 -0
  49. package/mcp-server/dist/config.js.map +1 -0
  50. package/mcp-server/dist/http.d.ts +30 -0
  51. package/mcp-server/dist/http.d.ts.map +1 -0
  52. package/mcp-server/dist/http.js +276 -0
  53. package/mcp-server/dist/http.js.map +1 -0
  54. package/mcp-server/dist/index.d.ts +48 -0
  55. package/mcp-server/dist/index.d.ts.map +1 -0
  56. package/mcp-server/dist/index.js +538 -0
  57. package/mcp-server/dist/index.js.map +1 -0
  58. package/mcp-server/dist/preload-http.d.ts +3 -0
  59. package/mcp-server/dist/preload-http.d.ts.map +1 -0
  60. package/mcp-server/dist/preload-http.js +28 -0
  61. package/mcp-server/dist/preload-http.js.map +1 -0
  62. package/mcp-server/dist/preload.d.ts +3 -0
  63. package/mcp-server/dist/preload.d.ts.map +1 -0
  64. package/mcp-server/dist/preload.js +32 -0
  65. package/mcp-server/dist/preload.js.map +1 -0
  66. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts +19 -0
  67. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts.map +1 -0
  68. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js +403 -0
  69. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js.map +1 -0
  70. package/mcp-server/dist/scripts/setup-embeddings.d.ts +3 -0
  71. package/mcp-server/dist/scripts/setup-embeddings.d.ts.map +1 -0
  72. package/mcp-server/dist/scripts/setup-embeddings.js +15 -0
  73. package/mcp-server/dist/scripts/setup-embeddings.js.map +1 -0
  74. package/mcp-server/dist/services/assistant-judgments.d.ts +79 -0
  75. package/mcp-server/dist/services/assistant-judgments.d.ts.map +1 -0
  76. package/mcp-server/dist/services/assistant-judgments.js +128 -0
  77. package/mcp-server/dist/services/assistant-judgments.js.map +1 -0
  78. package/mcp-server/dist/services/browser-assist-gate.d.ts +314 -0
  79. package/mcp-server/dist/services/browser-assist-gate.d.ts.map +1 -0
  80. package/mcp-server/dist/services/browser-assist-gate.js +344 -0
  81. package/mcp-server/dist/services/browser-assist-gate.js.map +1 -0
  82. package/mcp-server/dist/services/browser-assist.d.ts +104 -0
  83. package/mcp-server/dist/services/browser-assist.d.ts.map +1 -0
  84. package/mcp-server/dist/services/browser-assist.js +282 -0
  85. package/mcp-server/dist/services/browser-assist.js.map +1 -0
  86. package/mcp-server/dist/services/browser-bench.d.ts +772 -0
  87. package/mcp-server/dist/services/browser-bench.d.ts.map +1 -0
  88. package/mcp-server/dist/services/browser-bench.js +424 -0
  89. package/mcp-server/dist/services/browser-bench.js.map +1 -0
  90. package/mcp-server/dist/services/browser-judgments.d.ts +196 -0
  91. package/mcp-server/dist/services/browser-judgments.d.ts.map +1 -0
  92. package/mcp-server/dist/services/browser-judgments.js +363 -0
  93. package/mcp-server/dist/services/browser-judgments.js.map +1 -0
  94. package/mcp-server/dist/services/browser-risk.d.ts +166 -0
  95. package/mcp-server/dist/services/browser-risk.d.ts.map +1 -0
  96. package/mcp-server/dist/services/browser-risk.js +458 -0
  97. package/mcp-server/dist/services/browser-risk.js.map +1 -0
  98. package/mcp-server/dist/services/categories.d.ts +61 -0
  99. package/mcp-server/dist/services/categories.d.ts.map +1 -0
  100. package/mcp-server/dist/services/categories.js +377 -0
  101. package/mcp-server/dist/services/categories.js.map +1 -0
  102. package/mcp-server/dist/services/codex-config-heal.d.ts +49 -0
  103. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -0
  104. package/mcp-server/dist/services/codex-config-heal.js +346 -0
  105. package/mcp-server/dist/services/codex-config-heal.js.map +1 -0
  106. package/mcp-server/dist/services/desktop-assist-gate.d.ts +198 -0
  107. package/mcp-server/dist/services/desktop-assist-gate.d.ts.map +1 -0
  108. package/mcp-server/dist/services/desktop-assist-gate.js +202 -0
  109. package/mcp-server/dist/services/desktop-assist-gate.js.map +1 -0
  110. package/mcp-server/dist/services/desktop-assist.d.ts +104 -0
  111. package/mcp-server/dist/services/desktop-assist.d.ts.map +1 -0
  112. package/mcp-server/dist/services/desktop-assist.js +266 -0
  113. package/mcp-server/dist/services/desktop-assist.js.map +1 -0
  114. package/mcp-server/dist/services/desktop-bench.d.ts +553 -0
  115. package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -0
  116. package/mcp-server/dist/services/desktop-bench.js +311 -0
  117. package/mcp-server/dist/services/desktop-bench.js.map +1 -0
  118. package/mcp-server/dist/services/desktop-client.d.ts +23 -0
  119. package/mcp-server/dist/services/desktop-client.d.ts.map +1 -0
  120. package/mcp-server/dist/services/desktop-client.js +79 -0
  121. package/mcp-server/dist/services/desktop-client.js.map +1 -0
  122. package/mcp-server/dist/services/desktop-judgments.d.ts +125 -0
  123. package/mcp-server/dist/services/desktop-judgments.d.ts.map +1 -0
  124. package/mcp-server/dist/services/desktop-judgments.js +177 -0
  125. package/mcp-server/dist/services/desktop-judgments.js.map +1 -0
  126. package/mcp-server/dist/services/desktop-risk.d.ts +283 -0
  127. package/mcp-server/dist/services/desktop-risk.d.ts.map +1 -0
  128. package/mcp-server/dist/services/desktop-risk.js +605 -0
  129. package/mcp-server/dist/services/desktop-risk.js.map +1 -0
  130. package/mcp-server/dist/services/discord.d.ts +112 -0
  131. package/mcp-server/dist/services/discord.d.ts.map +1 -0
  132. package/mcp-server/dist/services/discord.js +477 -0
  133. package/mcp-server/dist/services/discord.js.map +1 -0
  134. package/mcp-server/dist/services/edit-stale.d.ts +81 -0
  135. package/mcp-server/dist/services/edit-stale.d.ts.map +1 -0
  136. package/mcp-server/dist/services/edit-stale.js +358 -0
  137. package/mcp-server/dist/services/edit-stale.js.map +1 -0
  138. package/mcp-server/dist/services/embedding-service.d.ts +16 -0
  139. package/mcp-server/dist/services/embedding-service.d.ts.map +1 -0
  140. package/mcp-server/dist/services/embedding-service.js +126 -0
  141. package/mcp-server/dist/services/embedding-service.js.map +1 -0
  142. package/mcp-server/dist/services/embedding-worker.d.ts +2 -0
  143. package/mcp-server/dist/services/embedding-worker.d.ts.map +1 -0
  144. package/mcp-server/dist/services/embedding-worker.js +52 -0
  145. package/mcp-server/dist/services/embedding-worker.js.map +1 -0
  146. package/mcp-server/dist/services/fb-regions.d.ts +71 -0
  147. package/mcp-server/dist/services/fb-regions.d.ts.map +1 -0
  148. package/mcp-server/dist/services/fb-regions.js +211 -0
  149. package/mcp-server/dist/services/fb-regions.js.map +1 -0
  150. package/mcp-server/dist/services/file-checksums.d.ts +22 -0
  151. package/mcp-server/dist/services/file-checksums.d.ts.map +1 -0
  152. package/mcp-server/dist/services/file-checksums.js +55 -0
  153. package/mcp-server/dist/services/file-checksums.js.map +1 -0
  154. package/mcp-server/dist/services/file-excerpt.d.ts +24 -0
  155. package/mcp-server/dist/services/file-excerpt.d.ts.map +1 -0
  156. package/mcp-server/dist/services/file-excerpt.js +65 -0
  157. package/mcp-server/dist/services/file-excerpt.js.map +1 -0
  158. package/mcp-server/dist/services/identity.d.ts +124 -0
  159. package/mcp-server/dist/services/identity.d.ts.map +1 -0
  160. package/mcp-server/dist/services/identity.js +241 -0
  161. package/mcp-server/dist/services/identity.js.map +1 -0
  162. package/mcp-server/dist/services/local-embeddings.d.ts +2 -0
  163. package/mcp-server/dist/services/local-embeddings.d.ts.map +1 -0
  164. package/mcp-server/dist/services/local-embeddings.js +3 -0
  165. package/mcp-server/dist/services/local-embeddings.js.map +1 -0
  166. package/mcp-server/dist/services/memory-client.d.ts +4 -0
  167. package/mcp-server/dist/services/memory-client.d.ts.map +1 -0
  168. package/mcp-server/dist/services/memory-client.js +27 -0
  169. package/mcp-server/dist/services/memory-client.js.map +1 -0
  170. package/mcp-server/dist/services/memory-judge-backfill.d.ts +135 -0
  171. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -0
  172. package/mcp-server/dist/services/memory-judge-backfill.js +510 -0
  173. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -0
  174. package/mcp-server/dist/services/memory-judgments.d.ts +362 -0
  175. package/mcp-server/dist/services/memory-judgments.d.ts.map +1 -0
  176. package/mcp-server/dist/services/memory-judgments.js +674 -0
  177. package/mcp-server/dist/services/memory-judgments.js.map +1 -0
  178. package/mcp-server/dist/services/memory-maintenance.d.ts +108 -0
  179. package/mcp-server/dist/services/memory-maintenance.d.ts.map +1 -0
  180. package/mcp-server/dist/services/memory-maintenance.js +403 -0
  181. package/mcp-server/dist/services/memory-maintenance.js.map +1 -0
  182. package/mcp-server/dist/services/memory-retrieval.d.ts +66 -0
  183. package/mcp-server/dist/services/memory-retrieval.d.ts.map +1 -0
  184. package/mcp-server/dist/services/memory-retrieval.js +412 -0
  185. package/mcp-server/dist/services/memory-retrieval.js.map +1 -0
  186. package/mcp-server/dist/services/memory-schema.d.ts +16 -0
  187. package/mcp-server/dist/services/memory-schema.d.ts.map +1 -0
  188. package/mcp-server/dist/services/memory-schema.js +282 -0
  189. package/mcp-server/dist/services/memory-schema.js.map +1 -0
  190. package/mcp-server/dist/services/memory-sessions.d.ts +24 -0
  191. package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -0
  192. package/mcp-server/dist/services/memory-sessions.js +110 -0
  193. package/mcp-server/dist/services/memory-sessions.js.map +1 -0
  194. package/mcp-server/dist/services/memory-triage.d.ts +60 -0
  195. package/mcp-server/dist/services/memory-triage.d.ts.map +1 -0
  196. package/mcp-server/dist/services/memory-triage.js +189 -0
  197. package/mcp-server/dist/services/memory-triage.js.map +1 -0
  198. package/mcp-server/dist/services/memory-verification.d.ts +24 -0
  199. package/mcp-server/dist/services/memory-verification.d.ts.map +1 -0
  200. package/mcp-server/dist/services/memory-verification.js +92 -0
  201. package/mcp-server/dist/services/memory-verification.js.map +1 -0
  202. package/mcp-server/dist/services/memory-writer.d.ts +26 -0
  203. package/mcp-server/dist/services/memory-writer.d.ts.map +1 -0
  204. package/mcp-server/dist/services/memory-writer.js +126 -0
  205. package/mcp-server/dist/services/memory-writer.js.map +1 -0
  206. package/mcp-server/dist/services/neural-interface.d.ts +439 -0
  207. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -0
  208. package/mcp-server/dist/services/neural-interface.js +1135 -0
  209. package/mcp-server/dist/services/neural-interface.js.map +1 -0
  210. package/mcp-server/dist/services/profiles.d.ts +120 -0
  211. package/mcp-server/dist/services/profiles.d.ts.map +1 -0
  212. package/mcp-server/dist/services/profiles.js +387 -0
  213. package/mcp-server/dist/services/profiles.js.map +1 -0
  214. package/mcp-server/dist/services/secret-gate.d.ts +29 -0
  215. package/mcp-server/dist/services/secret-gate.d.ts.map +1 -0
  216. package/mcp-server/dist/services/secret-gate.js +43 -0
  217. package/mcp-server/dist/services/secret-gate.js.map +1 -0
  218. package/mcp-server/dist/services/server-instructions.d.ts +10 -0
  219. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -0
  220. package/mcp-server/dist/services/server-instructions.js +101 -0
  221. package/mcp-server/dist/services/server-instructions.js.map +1 -0
  222. package/mcp-server/dist/services/sqlite.d.ts +235 -0
  223. package/mcp-server/dist/services/sqlite.d.ts.map +1 -0
  224. package/mcp-server/dist/services/sqlite.js +1185 -0
  225. package/mcp-server/dist/services/sqlite.js.map +1 -0
  226. package/mcp-server/dist/services/typesafe-bench.d.ts +99 -0
  227. package/mcp-server/dist/services/typesafe-bench.d.ts.map +1 -0
  228. package/mcp-server/dist/services/typesafe-bench.js +218 -0
  229. package/mcp-server/dist/services/typesafe-bench.js.map +1 -0
  230. package/mcp-server/dist/services/typesafe-config.d.ts +342 -0
  231. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -0
  232. package/mcp-server/dist/services/typesafe-config.js +746 -0
  233. package/mcp-server/dist/services/typesafe-config.js.map +1 -0
  234. package/mcp-server/dist/services/typesafe.d.ts +213 -0
  235. package/mcp-server/dist/services/typesafe.d.ts.map +1 -0
  236. package/mcp-server/dist/services/typesafe.js +330 -0
  237. package/mcp-server/dist/services/typesafe.js.map +1 -0
  238. package/mcp-server/dist/services/vector-cache.d.ts +68 -0
  239. package/mcp-server/dist/services/vector-cache.d.ts.map +1 -0
  240. package/mcp-server/dist/services/vector-cache.js +271 -0
  241. package/mcp-server/dist/services/vector-cache.js.map +1 -0
  242. package/mcp-server/dist/services/whiteboard-geometry.d.ts +305 -0
  243. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -0
  244. package/mcp-server/dist/services/whiteboard-geometry.js +666 -0
  245. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -0
  246. package/mcp-server/dist/tools/agents.d.ts +212 -0
  247. package/mcp-server/dist/tools/agents.d.ts.map +1 -0
  248. package/mcp-server/dist/tools/agents.js +407 -0
  249. package/mcp-server/dist/tools/agents.js.map +1 -0
  250. package/mcp-server/dist/tools/bluesky.d.ts +39 -0
  251. package/mcp-server/dist/tools/bluesky.d.ts.map +1 -0
  252. package/mcp-server/dist/tools/bluesky.js +1144 -0
  253. package/mcp-server/dist/tools/bluesky.js.map +1 -0
  254. package/mcp-server/dist/tools/browser-advanced.d.ts +58 -0
  255. package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -0
  256. package/mcp-server/dist/tools/browser-advanced.js +106 -0
  257. package/mcp-server/dist/tools/browser-advanced.js.map +1 -0
  258. package/mcp-server/dist/tools/browser-batch.d.ts +99 -0
  259. package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -0
  260. package/mcp-server/dist/tools/browser-batch.js +187 -0
  261. package/mcp-server/dist/tools/browser-batch.js.map +1 -0
  262. package/mcp-server/dist/tools/browser-cheatsheet.d.ts +21 -0
  263. package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -0
  264. package/mcp-server/dist/tools/browser-cheatsheet.js +144 -0
  265. package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -0
  266. package/mcp-server/dist/tools/browser-interact.d.ts +239 -0
  267. package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -0
  268. package/mcp-server/dist/tools/browser-interact.js +300 -0
  269. package/mcp-server/dist/tools/browser-interact.js.map +1 -0
  270. package/mcp-server/dist/tools/browser-navigate.d.ts +90 -0
  271. package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -0
  272. package/mcp-server/dist/tools/browser-navigate.js +142 -0
  273. package/mcp-server/dist/tools/browser-navigate.js.map +1 -0
  274. package/mcp-server/dist/tools/browser-observe.d.ts +561 -0
  275. package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -0
  276. package/mcp-server/dist/tools/browser-observe.js +2009 -0
  277. package/mcp-server/dist/tools/browser-observe.js.map +1 -0
  278. package/mcp-server/dist/tools/browser.d.ts +34 -0
  279. package/mcp-server/dist/tools/browser.d.ts.map +1 -0
  280. package/mcp-server/dist/tools/browser.js +112 -0
  281. package/mcp-server/dist/tools/browser.js.map +1 -0
  282. package/mcp-server/dist/tools/card-tools.d.ts +82 -0
  283. package/mcp-server/dist/tools/card-tools.d.ts.map +1 -0
  284. package/mcp-server/dist/tools/card-tools.js +137 -0
  285. package/mcp-server/dist/tools/card-tools.js.map +1 -0
  286. package/mcp-server/dist/tools/card.d.ts +8 -0
  287. package/mcp-server/dist/tools/card.d.ts.map +1 -0
  288. package/mcp-server/dist/tools/card.js +16 -0
  289. package/mcp-server/dist/tools/card.js.map +1 -0
  290. package/mcp-server/dist/tools/category.d.ts +32 -0
  291. package/mcp-server/dist/tools/category.d.ts.map +1 -0
  292. package/mcp-server/dist/tools/category.js +290 -0
  293. package/mcp-server/dist/tools/category.js.map +1 -0
  294. package/mcp-server/dist/tools/choice.d.ts +160 -0
  295. package/mcp-server/dist/tools/choice.d.ts.map +1 -0
  296. package/mcp-server/dist/tools/choice.js +80 -0
  297. package/mcp-server/dist/tools/choice.js.map +1 -0
  298. package/mcp-server/dist/tools/computer.d.ts +71 -0
  299. package/mcp-server/dist/tools/computer.d.ts.map +1 -0
  300. package/mcp-server/dist/tools/computer.js +174 -0
  301. package/mcp-server/dist/tools/computer.js.map +1 -0
  302. package/mcp-server/dist/tools/discord-channel.d.ts +44 -0
  303. package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -0
  304. package/mcp-server/dist/tools/discord-channel.js +236 -0
  305. package/mcp-server/dist/tools/discord-channel.js.map +1 -0
  306. package/mcp-server/dist/tools/discord-guild.d.ts +20 -0
  307. package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -0
  308. package/mcp-server/dist/tools/discord-guild.js +138 -0
  309. package/mcp-server/dist/tools/discord-guild.js.map +1 -0
  310. package/mcp-server/dist/tools/discord-member.d.ts +26 -0
  311. package/mcp-server/dist/tools/discord-member.d.ts.map +1 -0
  312. package/mcp-server/dist/tools/discord-member.js +122 -0
  313. package/mcp-server/dist/tools/discord-member.js.map +1 -0
  314. package/mcp-server/dist/tools/discord-message.d.ts +36 -0
  315. package/mcp-server/dist/tools/discord-message.d.ts.map +1 -0
  316. package/mcp-server/dist/tools/discord-message.js +184 -0
  317. package/mcp-server/dist/tools/discord-message.js.map +1 -0
  318. package/mcp-server/dist/tools/discord-onboarding.d.ts +32 -0
  319. package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -0
  320. package/mcp-server/dist/tools/discord-onboarding.js +179 -0
  321. package/mcp-server/dist/tools/discord-onboarding.js.map +1 -0
  322. package/mcp-server/dist/tools/discord-role.d.ts +34 -0
  323. package/mcp-server/dist/tools/discord-role.d.ts.map +1 -0
  324. package/mcp-server/dist/tools/discord-role.js +178 -0
  325. package/mcp-server/dist/tools/discord-role.js.map +1 -0
  326. package/mcp-server/dist/tools/discord-thread.d.ts +28 -0
  327. package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -0
  328. package/mcp-server/dist/tools/discord-thread.js +88 -0
  329. package/mcp-server/dist/tools/discord-thread.js.map +1 -0
  330. package/mcp-server/dist/tools/discord-webhook.d.ts +32 -0
  331. package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -0
  332. package/mcp-server/dist/tools/discord-webhook.js +154 -0
  333. package/mcp-server/dist/tools/discord-webhook.js.map +1 -0
  334. package/mcp-server/dist/tools/discord.d.ts +7 -0
  335. package/mcp-server/dist/tools/discord.d.ts.map +1 -0
  336. package/mcp-server/dist/tools/discord.js +25 -0
  337. package/mcp-server/dist/tools/discord.js.map +1 -0
  338. package/mcp-server/dist/tools/fb-groups.d.ts +98 -0
  339. package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -0
  340. package/mcp-server/dist/tools/fb-groups.js +369 -0
  341. package/mcp-server/dist/tools/fb-groups.js.map +1 -0
  342. package/mcp-server/dist/tools/forget.d.ts +14 -0
  343. package/mcp-server/dist/tools/forget.d.ts.map +1 -0
  344. package/mcp-server/dist/tools/forget.js +26 -0
  345. package/mcp-server/dist/tools/forget.js.map +1 -0
  346. package/mcp-server/dist/tools/git-tools.d.ts +24 -0
  347. package/mcp-server/dist/tools/git-tools.d.ts.map +1 -0
  348. package/mcp-server/dist/tools/git-tools.js +107 -0
  349. package/mcp-server/dist/tools/git-tools.js.map +1 -0
  350. package/mcp-server/dist/tools/git.d.ts +7 -0
  351. package/mcp-server/dist/tools/git.d.ts.map +1 -0
  352. package/mcp-server/dist/tools/git.js +11 -0
  353. package/mcp-server/dist/tools/git.js.map +1 -0
  354. package/mcp-server/dist/tools/gsc/experience.d.ts +135 -0
  355. package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -0
  356. package/mcp-server/dist/tools/gsc/experience.js +351 -0
  357. package/mcp-server/dist/tools/gsc/experience.js.map +1 -0
  358. package/mcp-server/dist/tools/gsc/helpers.d.ts +78 -0
  359. package/mcp-server/dist/tools/gsc/helpers.d.ts.map +1 -0
  360. package/mcp-server/dist/tools/gsc/helpers.js +258 -0
  361. package/mcp-server/dist/tools/gsc/helpers.js.map +1 -0
  362. package/mcp-server/dist/tools/gsc/indexing.d.ts +121 -0
  363. package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -0
  364. package/mcp-server/dist/tools/gsc/indexing.js +357 -0
  365. package/mcp-server/dist/tools/gsc/indexing.js.map +1 -0
  366. package/mcp-server/dist/tools/gsc/inspect.d.ts +78 -0
  367. package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -0
  368. package/mcp-server/dist/tools/gsc/inspect.js +321 -0
  369. package/mcp-server/dist/tools/gsc/inspect.js.map +1 -0
  370. package/mcp-server/dist/tools/gsc/navigate.d.ts +44 -0
  371. package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -0
  372. package/mcp-server/dist/tools/gsc/navigate.js +197 -0
  373. package/mcp-server/dist/tools/gsc/navigate.js.map +1 -0
  374. package/mcp-server/dist/tools/gsc/performance.d.ts +84 -0
  375. package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -0
  376. package/mcp-server/dist/tools/gsc/performance.js +274 -0
  377. package/mcp-server/dist/tools/gsc/performance.js.map +1 -0
  378. package/mcp-server/dist/tools/gsc/settings.d.ts +153 -0
  379. package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -0
  380. package/mcp-server/dist/tools/gsc/settings.js +425 -0
  381. package/mcp-server/dist/tools/gsc/settings.js.map +1 -0
  382. package/mcp-server/dist/tools/gsc.d.ts +7 -0
  383. package/mcp-server/dist/tools/gsc.d.ts.map +1 -0
  384. package/mcp-server/dist/tools/gsc.js +51 -0
  385. package/mcp-server/dist/tools/gsc.js.map +1 -0
  386. package/mcp-server/dist/tools/image-tools.d.ts +24 -0
  387. package/mcp-server/dist/tools/image-tools.d.ts.map +1 -0
  388. package/mcp-server/dist/tools/image-tools.js +80 -0
  389. package/mcp-server/dist/tools/image-tools.js.map +1 -0
  390. package/mcp-server/dist/tools/image.d.ts +10 -0
  391. package/mcp-server/dist/tools/image.d.ts.map +1 -0
  392. package/mcp-server/dist/tools/image.js +14 -0
  393. package/mcp-server/dist/tools/image.js.map +1 -0
  394. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +97 -0
  395. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -0
  396. package/mcp-server/dist/tools/leonardo-browser-tools.js +228 -0
  397. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -0
  398. package/mcp-server/dist/tools/leonardo.d.ts +13 -0
  399. package/mcp-server/dist/tools/leonardo.d.ts.map +1 -0
  400. package/mcp-server/dist/tools/leonardo.js +21 -0
  401. package/mcp-server/dist/tools/leonardo.js.map +1 -0
  402. package/mcp-server/dist/tools/loop.d.ts +30 -0
  403. package/mcp-server/dist/tools/loop.d.ts.map +1 -0
  404. package/mcp-server/dist/tools/loop.js +401 -0
  405. package/mcp-server/dist/tools/loop.js.map +1 -0
  406. package/mcp-server/dist/tools/memories.d.ts +56 -0
  407. package/mcp-server/dist/tools/memories.d.ts.map +1 -0
  408. package/mcp-server/dist/tools/memories.js +171 -0
  409. package/mcp-server/dist/tools/memories.js.map +1 -0
  410. package/mcp-server/dist/tools/morelogin.d.ts +24 -0
  411. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -0
  412. package/mcp-server/dist/tools/morelogin.js +79 -0
  413. package/mcp-server/dist/tools/morelogin.js.map +1 -0
  414. package/mcp-server/dist/tools/profile.d.ts +17 -0
  415. package/mcp-server/dist/tools/profile.d.ts.map +1 -0
  416. package/mcp-server/dist/tools/profile.js +114 -0
  417. package/mcp-server/dist/tools/profile.js.map +1 -0
  418. package/mcp-server/dist/tools/recall.d.ts +71 -0
  419. package/mcp-server/dist/tools/recall.d.ts.map +1 -0
  420. package/mcp-server/dist/tools/recall.js +147 -0
  421. package/mcp-server/dist/tools/recall.js.map +1 -0
  422. package/mcp-server/dist/tools/reflect.d.ts +56 -0
  423. package/mcp-server/dist/tools/reflect.d.ts.map +1 -0
  424. package/mcp-server/dist/tools/reflect.js +157 -0
  425. package/mcp-server/dist/tools/reflect.js.map +1 -0
  426. package/mcp-server/dist/tools/remember.d.ts +62 -0
  427. package/mcp-server/dist/tools/remember.d.ts.map +1 -0
  428. package/mcp-server/dist/tools/remember.js +201 -0
  429. package/mcp-server/dist/tools/remember.js.map +1 -0
  430. package/mcp-server/dist/tools/response.d.ts +23 -0
  431. package/mcp-server/dist/tools/response.d.ts.map +1 -0
  432. package/mcp-server/dist/tools/response.js +24 -0
  433. package/mcp-server/dist/tools/response.js.map +1 -0
  434. package/mcp-server/dist/tools/restore.d.ts +14 -0
  435. package/mcp-server/dist/tools/restore.d.ts.map +1 -0
  436. package/mcp-server/dist/tools/restore.js +23 -0
  437. package/mcp-server/dist/tools/restore.js.map +1 -0
  438. package/mcp-server/dist/tools/style-guide-tools.d.ts +51 -0
  439. package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -0
  440. package/mcp-server/dist/tools/style-guide-tools.js +231 -0
  441. package/mcp-server/dist/tools/style-guide-tools.js.map +1 -0
  442. package/mcp-server/dist/tools/style-guide.d.ts +8 -0
  443. package/mcp-server/dist/tools/style-guide.d.ts.map +1 -0
  444. package/mcp-server/dist/tools/style-guide.js +12 -0
  445. package/mcp-server/dist/tools/style-guide.js.map +1 -0
  446. package/mcp-server/dist/tools/sync.d.ts +20 -0
  447. package/mcp-server/dist/tools/sync.d.ts.map +1 -0
  448. package/mcp-server/dist/tools/sync.js +156 -0
  449. package/mcp-server/dist/tools/sync.js.map +1 -0
  450. package/mcp-server/dist/tools/tictactoe-tools.d.ts +18 -0
  451. package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -0
  452. package/mcp-server/dist/tools/tictactoe-tools.js +78 -0
  453. package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -0
  454. package/mcp-server/dist/tools/tictactoe.d.ts +7 -0
  455. package/mcp-server/dist/tools/tictactoe.d.ts.map +1 -0
  456. package/mcp-server/dist/tools/tictactoe.js +11 -0
  457. package/mcp-server/dist/tools/tictactoe.js.map +1 -0
  458. package/mcp-server/dist/tools/utils.d.ts +19 -0
  459. package/mcp-server/dist/tools/utils.d.ts.map +1 -0
  460. package/mcp-server/dist/tools/utils.js +54 -0
  461. package/mcp-server/dist/tools/utils.js.map +1 -0
  462. package/mcp-server/dist/tools/whiteboard-format.d.ts +88 -0
  463. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -0
  464. package/mcp-server/dist/tools/whiteboard-format.js +337 -0
  465. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -0
  466. package/mcp-server/dist/tools/whiteboard-tools.d.ts +326 -0
  467. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -0
  468. package/mcp-server/dist/tools/whiteboard-tools.js +222 -0
  469. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -0
  470. package/mcp-server/dist/tools/whiteboard.d.ts +8 -0
  471. package/mcp-server/dist/tools/whiteboard.d.ts.map +1 -0
  472. package/mcp-server/dist/tools/whiteboard.js +16 -0
  473. package/mcp-server/dist/tools/whiteboard.js.map +1 -0
  474. package/mcp-server/dist/tools/youtube/analytics.d.ts +46 -0
  475. package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -0
  476. package/mcp-server/dist/tools/youtube/analytics.js +83 -0
  477. package/mcp-server/dist/tools/youtube/analytics.js.map +1 -0
  478. package/mcp-server/dist/tools/youtube/dedup.d.ts +35 -0
  479. package/mcp-server/dist/tools/youtube/dedup.d.ts.map +1 -0
  480. package/mcp-server/dist/tools/youtube/dedup.js +56 -0
  481. package/mcp-server/dist/tools/youtube/dedup.js.map +1 -0
  482. package/mcp-server/dist/tools/youtube/download.d.ts +31 -0
  483. package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -0
  484. package/mcp-server/dist/tools/youtube/download.js +30 -0
  485. package/mcp-server/dist/tools/youtube/download.js.map +1 -0
  486. package/mcp-server/dist/tools/youtube/helpers.d.ts +66 -0
  487. package/mcp-server/dist/tools/youtube/helpers.d.ts.map +1 -0
  488. package/mcp-server/dist/tools/youtube/helpers.js +143 -0
  489. package/mcp-server/dist/tools/youtube/helpers.js.map +1 -0
  490. package/mcp-server/dist/tools/youtube/media.d.ts +43 -0
  491. package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -0
  492. package/mcp-server/dist/tools/youtube/media.js +97 -0
  493. package/mcp-server/dist/tools/youtube/media.js.map +1 -0
  494. package/mcp-server/dist/tools/youtube/metadata.d.ts +33 -0
  495. package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -0
  496. package/mcp-server/dist/tools/youtube/metadata.js +106 -0
  497. package/mcp-server/dist/tools/youtube/metadata.js.map +1 -0
  498. package/mcp-server/dist/tools/youtube/navigate.d.ts +30 -0
  499. package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -0
  500. package/mcp-server/dist/tools/youtube/navigate.js +71 -0
  501. package/mcp-server/dist/tools/youtube/navigate.js.map +1 -0
  502. package/mcp-server/dist/tools/youtube/playlist.d.ts +29 -0
  503. package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -0
  504. package/mcp-server/dist/tools/youtube/playlist.js +128 -0
  505. package/mcp-server/dist/tools/youtube/playlist.js.map +1 -0
  506. package/mcp-server/dist/tools/youtube/schedule.d.ts +31 -0
  507. package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -0
  508. package/mcp-server/dist/tools/youtube/schedule.js +147 -0
  509. package/mcp-server/dist/tools/youtube/schedule.js.map +1 -0
  510. package/mcp-server/dist/tools/youtube/source.d.ts +36 -0
  511. package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -0
  512. package/mcp-server/dist/tools/youtube/source.js +85 -0
  513. package/mcp-server/dist/tools/youtube/source.js.map +1 -0
  514. package/mcp-server/dist/tools/youtube/upload.d.ts +53 -0
  515. package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -0
  516. package/mcp-server/dist/tools/youtube/upload.js +255 -0
  517. package/mcp-server/dist/tools/youtube/upload.js.map +1 -0
  518. package/mcp-server/dist/tools/youtube.d.ts +12 -0
  519. package/mcp-server/dist/tools/youtube.d.ts.map +1 -0
  520. package/mcp-server/dist/tools/youtube.js +38 -0
  521. package/mcp-server/dist/tools/youtube.js.map +1 -0
  522. package/mcp-server/dist/tui.d.ts +3 -0
  523. package/mcp-server/dist/tui.d.ts.map +1 -0
  524. package/mcp-server/dist/tui.js +806 -0
  525. package/mcp-server/dist/tui.js.map +1 -0
  526. package/mcp-server/dist/types.d.ts +77 -0
  527. package/mcp-server/dist/types.d.ts.map +1 -0
  528. package/mcp-server/dist/types.js +2 -0
  529. package/mcp-server/dist/types.js.map +1 -0
  530. package/mcp-server/package-lock.json +4798 -0
  531. package/mcp-server/package.json +55 -38
  532. package/mcp-server/run.mjs +12 -2
  533. package/mcp-server/src/config.ts +48 -3
  534. package/mcp-server/src/http.ts +296 -93
  535. package/mcp-server/src/index.ts +546 -203
  536. package/mcp-server/src/preload-http.ts +19 -18
  537. package/mcp-server/src/preload.ts +19 -18
  538. package/mcp-server/src/scripts/setup-embeddings.ts +9 -0
  539. package/mcp-server/src/services/assistant-judgments.ts +157 -0
  540. package/mcp-server/src/services/browser-assist-gate.ts +383 -0
  541. package/mcp-server/src/services/browser-assist.ts +267 -0
  542. package/mcp-server/src/services/browser-bench.ts +477 -0
  543. package/mcp-server/src/services/browser-judgments.ts +414 -0
  544. package/mcp-server/src/services/browser-risk.ts +484 -0
  545. package/mcp-server/src/services/categories.ts +36 -19
  546. package/mcp-server/src/services/codex-config-heal.ts +403 -0
  547. package/mcp-server/src/services/desktop-assist-gate.ts +251 -0
  548. package/mcp-server/src/services/desktop-assist.ts +308 -0
  549. package/mcp-server/src/services/desktop-bench.ts +372 -0
  550. package/mcp-server/src/services/desktop-client.ts +76 -0
  551. package/mcp-server/src/services/desktop-judgments.ts +218 -0
  552. package/mcp-server/src/services/desktop-risk.ts +743 -0
  553. package/mcp-server/src/services/edit-stale.ts +299 -0
  554. package/mcp-server/src/services/embedding-service.ts +108 -0
  555. package/mcp-server/src/services/embedding-worker.ts +46 -0
  556. package/mcp-server/src/services/fb-regions.ts +221 -0
  557. package/mcp-server/src/services/file-checksums.ts +26 -9
  558. package/mcp-server/src/services/file-excerpt.ts +59 -0
  559. package/mcp-server/src/services/identity.ts +291 -0
  560. package/mcp-server/src/services/local-embeddings.ts +2 -77
  561. package/mcp-server/src/services/memory-client.ts +23 -0
  562. package/mcp-server/src/services/memory-judge-backfill.ts +462 -0
  563. package/mcp-server/src/services/memory-judgments.ts +901 -0
  564. package/mcp-server/src/services/memory-maintenance.ts +360 -0
  565. package/mcp-server/src/services/memory-retrieval.ts +349 -0
  566. package/mcp-server/src/services/memory-schema.ts +280 -0
  567. package/mcp-server/src/services/memory-sessions.ts +87 -0
  568. package/mcp-server/src/services/memory-triage.ts +182 -0
  569. package/mcp-server/src/services/memory-verification.ts +68 -0
  570. package/mcp-server/src/services/memory-writer.ts +125 -0
  571. package/mcp-server/src/services/neural-interface.ts +1217 -73
  572. package/mcp-server/src/services/profiles.ts +428 -0
  573. package/mcp-server/src/services/secret-gate.ts +52 -0
  574. package/mcp-server/src/services/server-instructions.ts +122 -0
  575. package/mcp-server/src/services/sqlite.ts +1456 -823
  576. package/mcp-server/src/services/typesafe-bench.ts +247 -0
  577. package/mcp-server/src/services/typesafe-config.ts +790 -0
  578. package/mcp-server/src/services/typesafe.ts +390 -0
  579. package/mcp-server/src/services/vector-cache.ts +255 -0
  580. package/mcp-server/src/services/whiteboard-geometry.ts +711 -0
  581. package/mcp-server/src/tools/agents.ts +539 -0
  582. package/mcp-server/src/tools/bluesky.ts +1186 -0
  583. package/mcp-server/src/tools/browser-advanced.ts +15 -6
  584. package/mcp-server/src/tools/browser-batch.ts +178 -0
  585. package/mcp-server/src/tools/browser-cheatsheet.ts +155 -0
  586. package/mcp-server/src/tools/browser-interact.ts +362 -255
  587. package/mcp-server/src/tools/browser-navigate.ts +164 -77
  588. package/mcp-server/src/tools/browser-observe.ts +2222 -1330
  589. package/mcp-server/src/tools/browser.ts +181 -107
  590. package/mcp-server/src/tools/card.ts +7 -5
  591. package/mcp-server/src/tools/choice.ts +103 -0
  592. package/mcp-server/src/tools/computer.ts +190 -0
  593. package/mcp-server/src/tools/discord.ts +10 -23
  594. package/mcp-server/src/tools/fb-groups.ts +387 -0
  595. package/mcp-server/src/tools/forget.ts +1 -1
  596. package/mcp-server/src/tools/git.ts +3 -1
  597. package/mcp-server/src/tools/gsc/experience.ts +440 -0
  598. package/mcp-server/src/tools/gsc/helpers.ts +288 -0
  599. package/mcp-server/src/tools/gsc/indexing.ts +495 -0
  600. package/mcp-server/src/tools/gsc/inspect.ts +391 -0
  601. package/mcp-server/src/tools/gsc/navigate.ts +236 -0
  602. package/mcp-server/src/tools/gsc/performance.ts +352 -0
  603. package/mcp-server/src/tools/gsc/settings.ts +564 -0
  604. package/mcp-server/src/tools/gsc.ts +95 -0
  605. package/mcp-server/src/tools/image.ts +3 -1
  606. package/mcp-server/src/tools/leonardo-browser-tools.ts +47 -37
  607. package/mcp-server/src/tools/leonardo.ts +7 -5
  608. package/mcp-server/src/tools/loop.ts +118 -25
  609. package/mcp-server/src/tools/memories.ts +65 -5
  610. package/mcp-server/src/tools/morelogin.ts +81 -0
  611. package/mcp-server/src/tools/profile.ts +122 -0
  612. package/mcp-server/src/tools/recall.ts +105 -261
  613. package/mcp-server/src/tools/reflect.ts +171 -145
  614. package/mcp-server/src/tools/remember.ts +223 -115
  615. package/mcp-server/src/tools/style-guide-tools.ts +250 -0
  616. package/mcp-server/src/tools/style-guide.ts +16 -0
  617. package/mcp-server/src/tools/sync.ts +66 -9
  618. package/mcp-server/src/tools/tictactoe.ts +3 -1
  619. package/mcp-server/src/tools/utils.ts +22 -0
  620. package/mcp-server/src/tools/whiteboard-format.ts +394 -0
  621. package/mcp-server/src/tools/whiteboard-tools.ts +191 -183
  622. package/mcp-server/src/tools/whiteboard.ts +7 -5
  623. package/mcp-server/src/tools/youtube/analytics.ts +106 -0
  624. package/mcp-server/src/tools/youtube/dedup.ts +57 -0
  625. package/mcp-server/src/tools/youtube/download.ts +41 -0
  626. package/mcp-server/src/tools/youtube/helpers.ts +183 -0
  627. package/mcp-server/src/tools/youtube/media.ts +109 -0
  628. package/mcp-server/src/tools/youtube/metadata.ts +112 -0
  629. package/mcp-server/src/tools/youtube/navigate.ts +81 -0
  630. package/mcp-server/src/tools/youtube/playlist.ts +134 -0
  631. package/mcp-server/src/tools/youtube/schedule.ts +158 -0
  632. package/mcp-server/src/tools/youtube/source.ts +101 -0
  633. package/mcp-server/src/tools/youtube/upload.ts +296 -0
  634. package/mcp-server/src/tools/youtube.ts +47 -0
  635. package/mcp-server/src/tui.ts +8 -9
  636. package/neural-interface/games/TicTacToe/1.svg +1 -0
  637. package/neural-interface/games/TicTacToe/2.svg +1 -0
  638. package/neural-interface/games/TicTacToe/3.svg +1 -0
  639. package/neural-interface/games/TicTacToe/4.svg +1 -0
  640. package/neural-interface/games/TicTacToe/5.svg +1 -0
  641. package/neural-interface/games/TicTacToe/6.svg +1 -0
  642. package/neural-interface/games/TicTacToe/7.svg +1 -0
  643. package/neural-interface/games/TicTacToe/8.svg +1 -0
  644. package/neural-interface/games/TicTacToe/9.svg +1 -0
  645. package/neural-interface/games/TicTacToe/Board.svg +1 -0
  646. package/neural-interface/games/TicTacToe/Circle.svg +1 -0
  647. package/neural-interface/games/TicTacToe/Cross.svg +1 -0
  648. package/neural-interface/i18n/en.json +7022 -0
  649. package/neural-interface/i18n/pt-BR.json +7026 -0
  650. package/neural-interface/lib/assistant-api.js +1005 -0
  651. package/neural-interface/lib/assistant-attachments.js +121 -0
  652. package/neural-interface/lib/assistant-brains/claude.js +207 -0
  653. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +43 -0
  654. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +56 -0
  655. package/neural-interface/lib/assistant-brains/codex.js +213 -0
  656. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +47 -0
  657. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +62 -0
  658. package/neural-interface/lib/assistant-brains/opencode.js +320 -0
  659. package/neural-interface/lib/assistant-budget.js +318 -0
  660. package/neural-interface/lib/assistant-catalog.js +731 -0
  661. package/neural-interface/lib/assistant-clarify.js +664 -0
  662. package/neural-interface/lib/assistant-config.js +232 -0
  663. package/neural-interface/lib/assistant-dispatch.js +2744 -0
  664. package/neural-interface/lib/assistant-envelope.js +582 -0
  665. package/neural-interface/lib/assistant-evidence.js +236 -0
  666. package/neural-interface/lib/assistant-hidden-models.js +272 -0
  667. package/neural-interface/lib/assistant-jev.js +117 -0
  668. package/neural-interface/lib/assistant-judgments.js +111 -0
  669. package/neural-interface/lib/assistant-media.js +335 -0
  670. package/neural-interface/lib/assistant-memory.js +398 -0
  671. package/neural-interface/lib/assistant-persona.js +444 -0
  672. package/neural-interface/lib/assistant-plan-permissions.js +448 -0
  673. package/neural-interface/lib/assistant-playbooks/design.md +87 -0
  674. package/neural-interface/lib/assistant-playbooks.js +72 -0
  675. package/neural-interface/lib/assistant-pricing.js +159 -0
  676. package/neural-interface/lib/assistant-route-gate-codex.js +163 -0
  677. package/neural-interface/lib/assistant-route-gate.js +212 -0
  678. package/neural-interface/lib/assistant-router.js +1324 -0
  679. package/neural-interface/lib/assistant-runtime.js +3337 -0
  680. package/neural-interface/lib/assistant-task-prompt.js +472 -0
  681. package/neural-interface/lib/assistant-usage.js +1688 -0
  682. package/neural-interface/lib/augmented-path.js +70 -0
  683. package/neural-interface/lib/automation-storage-migration.js +54 -0
  684. package/neural-interface/lib/backup-service.js +851 -0
  685. package/neural-interface/lib/backup-zip-reader.js +262 -0
  686. package/neural-interface/lib/browser-assist-api.js +41 -0
  687. package/neural-interface/lib/browser-console.js +176 -0
  688. package/neural-interface/lib/browser-execution.js +241 -0
  689. package/neural-interface/lib/browser-extraction.js +191 -0
  690. package/neural-interface/lib/browser-language.js +83 -0
  691. package/neural-interface/lib/browser-screenshot.js +399 -0
  692. package/neural-interface/lib/browser-semantic-context.js +639 -0
  693. package/neural-interface/lib/browser-snapshots.js +192 -0
  694. package/neural-interface/lib/browser-tab-recovery.js +119 -0
  695. package/neural-interface/lib/browser-targets.js +70 -0
  696. package/neural-interface/lib/browser-tool-policy.js +926 -0
  697. package/neural-interface/lib/claude-accounts.js +230 -0
  698. package/neural-interface/lib/claude-agent-bridge.js +3203 -0
  699. package/neural-interface/lib/claude-bypass-policy.js +99 -0
  700. package/neural-interface/lib/claude-executable.js +241 -0
  701. package/neural-interface/lib/claude-history.js +429 -0
  702. package/neural-interface/lib/claude-model-catalog.js +444 -0
  703. package/neural-interface/lib/claude-panel-requests.js +165 -0
  704. package/neural-interface/lib/claude-panel-session.js +272 -0
  705. package/neural-interface/lib/claude-permission-rules.js +153 -0
  706. package/neural-interface/lib/claude-session-cleanup.js +71 -0
  707. package/neural-interface/lib/claude-session-list.js +82 -0
  708. package/neural-interface/lib/claude-session-ops.js +140 -0
  709. package/neural-interface/lib/claude-settings-view.js +51 -0
  710. package/neural-interface/lib/claude-skin-connect.js +79 -0
  711. package/neural-interface/lib/claude-stall-policy.js +7 -0
  712. package/neural-interface/lib/claude-temporary.js +435 -0
  713. package/neural-interface/lib/cli-update-planner.js +312 -0
  714. package/neural-interface/lib/codex-capabilities.d.ts +41 -0
  715. package/neural-interface/lib/codex-capabilities.js +148 -0
  716. package/neural-interface/lib/codex-collaboration-mode.js +17 -0
  717. package/neural-interface/lib/codex-compat-rpc.js +241 -0
  718. package/neural-interface/lib/codex-mcp-approvals.js +56 -0
  719. package/neural-interface/lib/codex-mcp-apps.js +47 -0
  720. package/neural-interface/lib/codex-model-context.js +202 -0
  721. package/neural-interface/lib/codex-plan-store.js +71 -0
  722. package/neural-interface/lib/codex-request-journal.js +242 -0
  723. package/neural-interface/lib/codex-runtime-path.js +213 -0
  724. package/neural-interface/lib/codex-session-catalog.js +231 -0
  725. package/neural-interface/lib/codex-session-settings.js +46 -0
  726. package/neural-interface/lib/codex-sidepanel-permissions.js +110 -0
  727. package/neural-interface/lib/codex-thread-history.js +165 -0
  728. package/neural-interface/lib/codex-user-verification.js +34 -0
  729. package/neural-interface/lib/codex-writer-lifecycle.js +152 -0
  730. package/neural-interface/lib/confined-fs.js +256 -0
  731. package/neural-interface/lib/db.js +922 -0
  732. package/neural-interface/lib/design-md.js +9 -0
  733. package/neural-interface/lib/desktop/api.js +92 -0
  734. package/neural-interface/lib/desktop/audit.js +62 -0
  735. package/neural-interface/lib/desktop/build.js +423 -0
  736. package/neural-interface/lib/desktop/config.js +606 -0
  737. package/neural-interface/lib/desktop/coords.js +76 -0
  738. package/neural-interface/lib/desktop/fake-helper.js +1192 -0
  739. package/neural-interface/lib/desktop/frames.js +77 -0
  740. package/neural-interface/lib/desktop/grants.js +84 -0
  741. package/neural-interface/lib/desktop/guards.js +170 -0
  742. package/neural-interface/lib/desktop/helper/SynabunDesktop.swift +3056 -0
  743. package/neural-interface/lib/desktop/lease.js +51 -0
  744. package/neural-interface/lib/desktop/manager.js +545 -0
  745. package/neural-interface/lib/desktop/press-contexts.js +103 -0
  746. package/neural-interface/lib/desktop/protocol.js +495 -0
  747. package/neural-interface/lib/desktop/service.js +1202 -0
  748. package/neural-interface/lib/effort-levels.js +148 -0
  749. package/neural-interface/lib/event-loop-monitor.js +67 -0
  750. package/neural-interface/lib/exec-loop-command.js +209 -0
  751. package/neural-interface/lib/folder-picker.ps1 +77 -0
  752. package/neural-interface/lib/http-guards.js +287 -0
  753. package/neural-interface/lib/jsonl-lines.js +94 -0
  754. package/neural-interface/lib/loop-logger.js +127 -0
  755. package/neural-interface/lib/mcp-client-config.js +133 -0
  756. package/neural-interface/lib/mcp-installer.js +487 -0
  757. package/neural-interface/lib/mcp-profile-refresh-coordinator.js +78 -0
  758. package/neural-interface/lib/mcp-profile-servers.js +120 -0
  759. package/neural-interface/lib/memo-cache.js +84 -0
  760. package/neural-interface/lib/memory-api.js +758 -0
  761. package/neural-interface/lib/memory-map-api.js +1150 -0
  762. package/neural-interface/lib/memory-map-layout.js +872 -0
  763. package/neural-interface/lib/memory-map-worker.js +165 -0
  764. package/neural-interface/lib/memory-session-capture.js +81 -0
  765. package/neural-interface/lib/memory-timers.js +62 -0
  766. package/neural-interface/lib/morelogin.js +301 -0
  767. package/neural-interface/lib/native-binary-runtime.js +382 -0
  768. package/neural-interface/lib/native-loop-goal.js +33 -0
  769. package/neural-interface/lib/native-loop-providers.js +1002 -0
  770. package/neural-interface/lib/native-loop-runtime.js +747 -0
  771. package/neural-interface/lib/offline-page.js +54 -0
  772. package/neural-interface/lib/opencode-hidden-models.js +113 -0
  773. package/neural-interface/lib/opencode-history.js +253 -0
  774. package/neural-interface/lib/opencode-runtime-config.js +58 -0
  775. package/neural-interface/lib/opencode-v2-client.js +486 -0
  776. package/neural-interface/lib/opencode-v2-ws-requests.js +955 -0
  777. package/neural-interface/lib/path-confine.js +213 -0
  778. package/neural-interface/lib/plugin-installer.js +264 -0
  779. package/neural-interface/lib/project-storage.js +766 -0
  780. package/neural-interface/lib/pty-host/core.js +603 -0
  781. package/neural-interface/lib/pty-host/flow.js +43 -0
  782. package/neural-interface/lib/pty-host/host.js +47 -0
  783. package/neural-interface/lib/pty-host/manager.js +360 -0
  784. package/neural-interface/lib/pty-host/protocol.js +81 -0
  785. package/neural-interface/lib/pty-host/snapshot.js +133 -0
  786. package/neural-interface/lib/remote-policy.js +582 -0
  787. package/neural-interface/lib/rulesets/api.js +175 -0
  788. package/neural-interface/lib/rulesets/fs-safe.js +245 -0
  789. package/neural-interface/lib/rulesets/installer.js +1453 -0
  790. package/neural-interface/lib/rulesets/json-edit.js +186 -0
  791. package/neural-interface/lib/rulesets/lock.js +170 -0
  792. package/neural-interface/lib/rulesets/managed-text.js +314 -0
  793. package/neural-interface/lib/rulesets/normalise.js +24 -0
  794. package/neural-interface/lib/rulesets/render.js +84 -0
  795. package/neural-interface/lib/schedule-launch-intent.js +40 -0
  796. package/neural-interface/lib/session-cache-builder.js +524 -0
  797. package/neural-interface/lib/session-chunker.js +344 -0
  798. package/neural-interface/lib/session-indexer.js +327 -0
  799. package/neural-interface/lib/session-title-generator.js +232 -0
  800. package/neural-interface/lib/skill-prompt-bridge.js +11 -0
  801. package/neural-interface/lib/style-guide/api.js +375 -0
  802. package/neural-interface/lib/style-guide/color.js +352 -0
  803. package/neural-interface/lib/style-guide/export-css.js +99 -0
  804. package/neural-interface/lib/style-guide/export-dtcg.js +209 -0
  805. package/neural-interface/lib/style-guide/export-tailwind.js +102 -0
  806. package/neural-interface/lib/style-guide/fonts.json +227 -0
  807. package/neural-interface/lib/style-guide/import.js +1268 -0
  808. package/neural-interface/lib/style-guide/pointers.js +103 -0
  809. package/neural-interface/lib/style-guide/presets.js +232 -0
  810. package/neural-interface/lib/style-guide/render-design-md.js +319 -0
  811. package/neural-interface/lib/style-guide/schema.js +848 -0
  812. package/neural-interface/lib/style-guide/security.js +43 -0
  813. package/neural-interface/lib/style-guide/store.js +491 -0
  814. package/neural-interface/lib/style-guide/summary.js +106 -0
  815. package/neural-interface/lib/style-guide/tokens.js +170 -0
  816. package/neural-interface/lib/synabun-update-plan.js +324 -0
  817. package/neural-interface/lib/synabun-version.js +179 -0
  818. package/neural-interface/lib/system-restore.js +99 -0
  819. package/neural-interface/lib/terminal-env.js +86 -0
  820. package/neural-interface/lib/ui-blob-store.js +106 -0
  821. package/neural-interface/lib/vector-cache.js +2 -0
  822. package/neural-interface/lib/virtual-socket.js +60 -0
  823. package/neural-interface/lib/whatsapp/api.js +146 -0
  824. package/neural-interface/lib/whatsapp/auth-store.js +389 -0
  825. package/neural-interface/lib/whatsapp/baileys-adapter.js +457 -0
  826. package/neural-interface/lib/whatsapp/brain.js +128 -0
  827. package/neural-interface/lib/whatsapp/bridge.js +1499 -0
  828. package/neural-interface/lib/whatsapp/cards.js +519 -0
  829. package/neural-interface/lib/whatsapp/commands.js +92 -0
  830. package/neural-interface/lib/whatsapp/config.js +291 -0
  831. package/neural-interface/lib/whatsapp/connector/entry.mjs +25 -0
  832. package/neural-interface/lib/whatsapp/connector/manifest.json +6 -0
  833. package/neural-interface/lib/whatsapp/connector/npm-shrinkwrap.json +717 -0
  834. package/neural-interface/lib/whatsapp/connector/package.json +8 -0
  835. package/neural-interface/lib/whatsapp/fake-baileys.js +444 -0
  836. package/neural-interface/lib/whatsapp/format.js +552 -0
  837. package/neural-interface/lib/whatsapp/host-core.js +1225 -0
  838. package/neural-interface/lib/whatsapp/host.js +119 -0
  839. package/neural-interface/lib/whatsapp/identity.js +288 -0
  840. package/neural-interface/lib/whatsapp/inbound.js +149 -0
  841. package/neural-interface/lib/whatsapp/installer.js +562 -0
  842. package/neural-interface/lib/whatsapp/manager.js +638 -0
  843. package/neural-interface/lib/whatsapp/paths.js +81 -0
  844. package/neural-interface/lib/whatsapp/protocol.js +327 -0
  845. package/neural-interface/lib/whatsapp/qr-svg.js +57 -0
  846. package/neural-interface/lib/whatsapp/redact.js +130 -0
  847. package/neural-interface/lib/whatsapp/service.js +1305 -0
  848. package/neural-interface/lib/whatsapp/vendor/qrcodegen.js +848 -0
  849. package/neural-interface/package-lock.json +4431 -0
  850. package/neural-interface/package.json +58 -27
  851. package/neural-interface/public/apple-touch-icon.png +0 -0
  852. package/neural-interface/public/category-logos/synabun.png +0 -0
  853. package/neural-interface/public/claude.svg +1 -0
  854. package/neural-interface/public/favicon-16x16.png +0 -0
  855. package/neural-interface/public/favicon-32x32.png +0 -0
  856. package/neural-interface/public/icon-192.png +0 -0
  857. package/neural-interface/public/icon-512.png +0 -0
  858. package/neural-interface/public/index.html +32 -182
  859. package/neural-interface/public/index2d.html +2 -2
  860. package/neural-interface/public/install-app.html +31 -0
  861. package/neural-interface/public/logo-synabun.png +0 -0
  862. package/neural-interface/public/logoHD.png +0 -0
  863. package/neural-interface/public/manifest.json +2 -0
  864. package/neural-interface/public/mcp.svg +4 -0
  865. package/neural-interface/public/offline.html +670 -299
  866. package/neural-interface/public/onboarding.html +804 -161
  867. package/neural-interface/public/opencode-logo-dark-square.svg +1 -0
  868. package/neural-interface/public/shared/agent-runtime-options.js +596 -0
  869. package/neural-interface/public/shared/api.js +339 -28
  870. package/neural-interface/public/shared/assistant/asst-accounts.js +318 -0
  871. package/neural-interface/public/shared/assistant/asst-agents-dock.js +296 -0
  872. package/neural-interface/public/shared/assistant/asst-api.js +270 -0
  873. package/neural-interface/public/shared/assistant/asst-brain-picker.js +744 -0
  874. package/neural-interface/public/shared/assistant/asst-budget.js +333 -0
  875. package/neural-interface/public/shared/assistant/asst-composer.js +656 -0
  876. package/neural-interface/public/shared/assistant/asst-computer.js +803 -0
  877. package/neural-interface/public/shared/assistant/asst-control.js +1018 -0
  878. package/neural-interface/public/shared/assistant/asst-hosts.js +262 -0
  879. package/neural-interface/public/shared/assistant/asst-mascot.js +313 -0
  880. package/neural-interface/public/shared/assistant/asst-menu.js +375 -0
  881. package/neural-interface/public/shared/assistant/asst-panel.js +1845 -0
  882. package/neural-interface/public/shared/assistant/asst-rack-styles.js +586 -0
  883. package/neural-interface/public/shared/assistant/asst-render.js +3305 -0
  884. package/neural-interface/public/shared/assistant/asst-route.js +1653 -0
  885. package/neural-interface/public/shared/assistant/asst-roving.js +75 -0
  886. package/neural-interface/public/shared/assistant/asst-sanitize.js +117 -0
  887. package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +488 -0
  888. package/neural-interface/public/shared/assistant/asst-sidepanel.js +675 -0
  889. package/neural-interface/public/shared/assistant/asst-slash.js +258 -0
  890. package/neural-interface/public/shared/assistant/asst-state.js +727 -0
  891. package/neural-interface/public/shared/assistant/asst-styles.js +1686 -0
  892. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +1017 -0
  893. package/neural-interface/public/shared/assistant/asst-usage.js +444 -0
  894. package/neural-interface/public/shared/assistant/asst-ws.js +175 -0
  895. package/neural-interface/public/shared/automation-context.js +75 -0
  896. package/neural-interface/public/shared/blob-store.js +99 -0
  897. package/neural-interface/public/shared/cdx/cdx-capabilities.js +118 -0
  898. package/neural-interface/public/shared/cdx/cdx-cards.js +37 -0
  899. package/neural-interface/public/shared/cdx/cdx-commands.js +171 -0
  900. package/neural-interface/public/shared/cdx/cdx-context-menu.js +402 -0
  901. package/neural-interface/public/shared/cdx/cdx-context-model.js +220 -0
  902. package/neural-interface/public/shared/cdx/cdx-features.js +408 -0
  903. package/neural-interface/public/shared/cdx/cdx-icons.js +89 -0
  904. package/neural-interface/public/shared/cdx/cdx-mcp-app-host.js +414 -0
  905. package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.html +6 -0
  906. package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.js +56 -0
  907. package/neural-interface/public/shared/cdx/cdx-mcp-app.js +178 -0
  908. package/neural-interface/public/shared/cdx/cdx-model-catalog.js +120 -0
  909. package/neural-interface/public/shared/cdx/cdx-output-model.js +261 -0
  910. package/neural-interface/public/shared/cdx/cdx-output.js +382 -0
  911. package/neural-interface/public/shared/cdx/cdx-panel.js +1372 -0
  912. package/neural-interface/public/shared/cdx/cdx-plan.js +266 -0
  913. package/neural-interface/public/shared/cdx/cdx-protocol.js +682 -0
  914. package/neural-interface/public/shared/cdx/cdx-render.js +3381 -0
  915. package/neural-interface/public/shared/cdx/cdx-requests.js +1136 -0
  916. package/neural-interface/public/shared/cdx/cdx-styles.js +3251 -0
  917. package/neural-interface/public/shared/cdx/cdx-tabs.js +7992 -0
  918. package/neural-interface/public/shared/cdx/cdx-ws.js +363 -0
  919. package/neural-interface/public/shared/cli-status.js +169 -0
  920. package/neural-interface/public/shared/command-runner-model.js +30 -0
  921. package/neural-interface/public/shared/constants.js +22 -5
  922. package/neural-interface/public/shared/cp/cp-agents.js +186 -0
  923. package/neural-interface/public/shared/cp/cp-bash.js +252 -0
  924. package/neural-interface/public/shared/cp/cp-compose.js +159 -0
  925. package/neural-interface/public/shared/cp/cp-context-menu.js +309 -0
  926. package/neural-interface/public/shared/cp/cp-context-model.js +157 -0
  927. package/neural-interface/public/shared/cp/cp-context-window.js +80 -0
  928. package/neural-interface/public/shared/cp/cp-ctx.js +11 -0
  929. package/neural-interface/public/shared/cp/cp-diff.js +255 -0
  930. package/neural-interface/public/shared/cp/cp-event-rows.js +637 -0
  931. package/neural-interface/public/shared/cp/cp-events.js +846 -0
  932. package/neural-interface/public/shared/cp/cp-markdown.js +104 -0
  933. package/neural-interface/public/shared/cp/cp-permission-model.js +905 -0
  934. package/neural-interface/public/shared/cp/cp-permissions.js +626 -0
  935. package/neural-interface/public/shared/cp/cp-rehydrate.js +113 -0
  936. package/neural-interface/public/shared/cp/cp-restore.js +224 -0
  937. package/neural-interface/public/shared/cp/cp-session-model.js +338 -0
  938. package/neural-interface/public/shared/cp/cp-session-settings.js +254 -0
  939. package/neural-interface/public/shared/cp/cp-sessions.js +227 -0
  940. package/neural-interface/public/shared/cp/cp-statusline.js +153 -0
  941. package/neural-interface/public/shared/cp/cp-styles.js +349 -0
  942. package/neural-interface/public/shared/cp/cp-tasks-model.js +282 -0
  943. package/neural-interface/public/shared/cp/cp-tasks.js +107 -0
  944. package/neural-interface/public/shared/cp/cp-temporary-view.js +56 -0
  945. package/neural-interface/public/shared/cp/cp-temporary.js +231 -0
  946. package/neural-interface/public/shared/cp/cp-tool-cards.js +326 -0
  947. package/neural-interface/public/shared/cp/cp-tool-results.js +618 -0
  948. package/neural-interface/public/shared/cp/cp-usage-model.js +124 -0
  949. package/neural-interface/public/shared/graph-activity.js +68 -0
  950. package/neural-interface/public/shared/html-shell.js +116 -17
  951. package/neural-interface/public/shared/i18n.js +132 -34
  952. package/neural-interface/public/shared/node-positions-store.js +44 -0
  953. package/neural-interface/public/shared/ocp-hidden-models.js +116 -0
  954. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +355 -0
  955. package/neural-interface/public/shared/ocp-v2/ocp-v2-autoaccept-button.js +35 -0
  956. package/neural-interface/public/shared/ocp-v2/ocp-v2-automation-ownership.js +72 -0
  957. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +280 -0
  958. package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +67 -0
  959. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +179 -0
  960. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes.js +164 -0
  961. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +725 -0
  962. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +75 -0
  963. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +1204 -0
  964. package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm-logic.js +117 -0
  965. package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm.js +59 -0
  966. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-gauge.js +252 -0
  967. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +478 -0
  968. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +179 -0
  969. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +233 -0
  970. package/neural-interface/public/shared/ocp-v2/ocp-v2-icons.js +30 -0
  971. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +1065 -0
  972. package/neural-interface/public/shared/ocp-v2/ocp-v2-mention-hints.js +182 -0
  973. package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +655 -0
  974. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +2636 -0
  975. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +601 -0
  976. package/neural-interface/public/shared/ocp-v2/ocp-v2-profile-inheritance.js +78 -0
  977. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +649 -0
  978. package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +35 -0
  979. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +357 -0
  980. package/neural-interface/public/shared/ocp-v2/ocp-v2-render-logic.js +189 -0
  981. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +1869 -0
  982. package/neural-interface/public/shared/ocp-v2/ocp-v2-select.js +42 -0
  983. package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +117 -0
  984. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +1714 -0
  985. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +468 -0
  986. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-widgets.js +63 -0
  987. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +366 -0
  988. package/neural-interface/public/shared/ocp-v2/ocp-v2-settings-link.js +19 -0
  989. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +343 -0
  990. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +746 -0
  991. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +347 -0
  992. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +338 -0
  993. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +3543 -0
  994. package/neural-interface/public/shared/ocp-v2/ocp-v2-subagents-logic.js +482 -0
  995. package/neural-interface/public/shared/ocp-v2/ocp-v2-tools-logic.js +351 -0
  996. package/neural-interface/public/shared/ocp-v2/ocp-v2-tools.js +168 -0
  997. package/neural-interface/public/shared/ocp-v2/ocp-v2-variantpicker.js +315 -0
  998. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +545 -0
  999. package/neural-interface/public/shared/provider-icons.js +30 -0
  1000. package/neural-interface/public/shared/pwa-install-page.js +34 -0
  1001. package/neural-interface/public/shared/pwa-install.css +12 -0
  1002. package/neural-interface/public/shared/pwa-install.js +28 -0
  1003. package/neural-interface/public/shared/session-title.js +100 -0
  1004. package/neural-interface/public/shared/settings/settings-ia.js +389 -0
  1005. package/neural-interface/public/shared/settings/settings-kit.js +234 -0
  1006. package/neural-interface/public/shared/settings/settings-rules.js +160 -0
  1007. package/neural-interface/public/shared/settings/settings-search.js +463 -0
  1008. package/neural-interface/public/shared/settings/settings-shell.js +570 -0
  1009. package/neural-interface/public/shared/start-bridge.js +583 -0
  1010. package/neural-interface/public/shared/state.js +5 -0
  1011. package/neural-interface/public/shared/storage.js +50 -7
  1012. package/neural-interface/public/shared/styleguide/sg-agents.js +42 -0
  1013. package/neural-interface/public/shared/styleguide/sg-brand.js +4 -0
  1014. package/neural-interface/public/shared/styleguide/sg-color-client.js +388 -0
  1015. package/neural-interface/public/shared/styleguide/sg-colors.js +151 -0
  1016. package/neural-interface/public/shared/styleguide/sg-components.js +35 -0
  1017. package/neural-interface/public/shared/styleguide/sg-history.js +41 -0
  1018. package/neural-interface/public/shared/styleguide/sg-icons-logo.js +38 -0
  1019. package/neural-interface/public/shared/styleguide/sg-imagery.js +4 -0
  1020. package/neural-interface/public/shared/styleguide/sg-import-export.js +48 -0
  1021. package/neural-interface/public/shared/styleguide/sg-kit.js +284 -0
  1022. package/neural-interface/public/shared/styleguide/sg-labels.js +318 -0
  1023. package/neural-interface/public/shared/styleguide/sg-layout.js +4 -0
  1024. package/neural-interface/public/shared/styleguide/sg-motion.js +5 -0
  1025. package/neural-interface/public/shared/styleguide/sg-panel.js +237 -0
  1026. package/neural-interface/public/shared/styleguide/sg-preview.js +138 -0
  1027. package/neural-interface/public/shared/styleguide/sg-rules.js +9 -0
  1028. package/neural-interface/public/shared/styleguide/sg-shape.js +24 -0
  1029. package/neural-interface/public/shared/styleguide/sg-state.js +132 -0
  1030. package/neural-interface/public/shared/styleguide/sg-typography.js +125 -0
  1031. package/neural-interface/public/shared/styles.css +8614 -1477
  1032. package/neural-interface/public/shared/synabun-mascot.js +1625 -0
  1033. package/neural-interface/public/shared/synabun-ticker.js +376 -0
  1034. package/neural-interface/public/shared/term-ready.js +307 -0
  1035. package/neural-interface/public/shared/term-renderer.js +343 -0
  1036. package/neural-interface/public/shared/ui-assistant-panel.js +10 -0
  1037. package/neural-interface/public/shared/ui-assistant.js +77 -0
  1038. package/neural-interface/public/shared/ui-automation-studio.js +5105 -3573
  1039. package/neural-interface/public/shared/ui-claude-panel.js +11685 -6333
  1040. package/neural-interface/public/shared/ui-codex-panel.js +12 -0
  1041. package/neural-interface/public/shared/ui-command-runner.js +251 -468
  1042. package/neural-interface/public/shared/ui-cost-widget.js +55 -34
  1043. package/neural-interface/public/shared/ui-detail.js +77 -14
  1044. package/neural-interface/public/shared/ui-explorer.js +360 -181
  1045. package/neural-interface/public/shared/ui-file-explorer.js +485 -107
  1046. package/neural-interface/public/shared/ui-help.js +2 -2
  1047. package/neural-interface/public/shared/ui-image-gallery.js +45 -21
  1048. package/neural-interface/public/shared/ui-judgments.js +1346 -0
  1049. package/neural-interface/public/shared/ui-keybinds.js +3 -2
  1050. package/neural-interface/public/shared/ui-link.js +105 -51
  1051. package/neural-interface/public/shared/ui-loading.js +222 -11
  1052. package/neural-interface/public/shared/ui-memory-maintenance.js +111 -0
  1053. package/neural-interface/public/shared/ui-menubar.js +100 -31
  1054. package/neural-interface/public/shared/ui-native-loop-router-state.js +94 -0
  1055. package/neural-interface/public/shared/ui-native-loop-router.js +493 -0
  1056. package/neural-interface/public/shared/ui-native-window-id.js +7 -0
  1057. package/neural-interface/public/shared/ui-navbar.js +357 -222
  1058. package/neural-interface/public/shared/ui-notifications.js +58 -12
  1059. package/neural-interface/public/shared/ui-opencode-panel-v2.js +17 -0
  1060. package/neural-interface/public/shared/ui-panels.js +15 -7
  1061. package/neural-interface/public/shared/ui-resume.js +823 -653
  1062. package/neural-interface/public/shared/ui-schedule-queue.js +172 -0
  1063. package/neural-interface/public/shared/ui-schedules-studio.js +1882 -0
  1064. package/neural-interface/public/shared/ui-scroll-follow.js +194 -0
  1065. package/neural-interface/public/shared/ui-sessions.js +789 -34
  1066. package/neural-interface/public/shared/ui-settings.js +5774 -2194
  1067. package/neural-interface/public/shared/ui-sidepanel-layout.js +109 -0
  1068. package/neural-interface/public/shared/ui-sidepanel-runtime.js +19 -0
  1069. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +40 -0
  1070. package/neural-interface/public/shared/ui-sidepanel-sessions.js +221 -0
  1071. package/neural-interface/public/shared/ui-sidepanel-state.js +131 -0
  1072. package/neural-interface/public/shared/ui-sidepanel-tray.js +547 -0
  1073. package/neural-interface/public/shared/ui-sidepanel-windows.js +387 -0
  1074. package/neural-interface/public/shared/ui-skills.js +18 -32
  1075. package/neural-interface/public/shared/ui-styleguide.js +35 -0
  1076. package/neural-interface/public/shared/ui-sync.js +87 -0
  1077. package/neural-interface/public/shared/ui-terminal.js +7863 -6385
  1078. package/neural-interface/public/shared/ui-tool-updates.js +292 -0
  1079. package/neural-interface/public/shared/ui-tooltip.js +156 -15
  1080. package/neural-interface/public/shared/ui-tutorial-draw.js +119 -683
  1081. package/neural-interface/public/shared/ui-tutorial-layout.js +135 -0
  1082. package/neural-interface/public/shared/ui-tutorial-steps.js +59 -2209
  1083. package/neural-interface/public/shared/ui-tutorial.css +102 -0
  1084. package/neural-interface/public/shared/ui-tutorial.js +557 -1076
  1085. package/neural-interface/public/shared/ui-update.js +420 -68
  1086. package/neural-interface/public/shared/ui-whatsapp.js +941 -0
  1087. package/neural-interface/public/shared/ui-whiteboard.js +1076 -233
  1088. package/neural-interface/public/shared/ui-workspaces.js +2 -1
  1089. package/neural-interface/public/shared/utils.js +58 -0
  1090. package/neural-interface/public/shared/vterm-buffer.js +15 -5
  1091. package/neural-interface/public/shared/whatsapp/wa-phone.js +65 -0
  1092. package/neural-interface/public/shared/whatsapp/wa-view.js +411 -0
  1093. package/neural-interface/public/sidepanel-session.html +18 -0
  1094. package/neural-interface/public/sw.js +10 -2
  1095. package/neural-interface/public/synabun.png +0 -0
  1096. package/neural-interface/public/variant/2d/graph.js +1707 -1635
  1097. package/neural-interface/public/variant/2d/lasso.js +2 -1
  1098. package/neural-interface/public/variant/2d/layout.js +595 -594
  1099. package/neural-interface/public/variant/2d/main.js +557 -521
  1100. package/neural-interface/public/variant/2d/settings-gfx.js +18 -24
  1101. package/neural-interface/public/variant/3d/main.js +517 -500
  1102. package/neural-interface/public/variant/3d/map-controls.js +144 -0
  1103. package/neural-interface/public/variant/3d/map-data.js +251 -0
  1104. package/neural-interface/public/variant/3d/map-labels.js +241 -0
  1105. package/neural-interface/public/variant/3d/map-pick.js +90 -0
  1106. package/neural-interface/public/variant/3d/map-renderer.js +521 -0
  1107. package/neural-interface/public/variant/3d/map-shaders.js +158 -0
  1108. package/neural-interface/public/variant/3d/map-view.js +618 -0
  1109. package/neural-interface/public/vendor/three/manifest.json +3 -0
  1110. package/neural-interface/public/vendor/three/three-0.169.0.min.js +3844 -0
  1111. package/neural-interface/public/vendor/xterm/manifest.json +3 -0
  1112. package/neural-interface/public/vendor/xterm/xterm-6.0.0.css +285 -0
  1113. package/neural-interface/public/vendor/xterm/xterm-6.0.0.min.js +192 -0
  1114. package/neural-interface/scripts/build-vendor.mjs +64 -0
  1115. package/neural-interface/scripts/codex-protocol-fixture.mjs +83 -0
  1116. package/neural-interface/scripts/rebuild-pty.js +39 -22
  1117. package/neural-interface/scripts/vendor-three-entry.js +24 -0
  1118. package/neural-interface/scripts/vendor-xterm-entry.js +8 -0
  1119. package/neural-interface/server.js +33983 -15000
  1120. package/neural-interface/skins/default/skin.css +90 -0
  1121. package/neural-interface/skins/default/skin.json +8 -0
  1122. package/neural-interface/templates/rulesets/README.md +9 -0
  1123. package/neural-interface/templates/rulesets/claude.md +3 -0
  1124. package/neural-interface/templates/rulesets/codex.md +2 -0
  1125. package/neural-interface/templates/rulesets/coexistence.md +6 -0
  1126. package/neural-interface/templates/rulesets/core.md +26 -0
  1127. package/neural-interface/templates/rulesets/cursor.md +2 -0
  1128. package/neural-interface/templates/rulesets/gemini.md +2 -0
  1129. package/neural-interface/templates/rulesets/legacy-hashes.json +227 -0
  1130. package/neural-interface/templates/rulesets/manifest.json +12 -0
  1131. package/neural-interface/templates/rulesets/opencode.md +2 -0
  1132. package/package.json +131 -53
  1133. package/postinstall.js +55 -26
  1134. package/preuninstall.js +187 -0
  1135. package/scripts/restore-data-home-from-backup.mjs +239 -0
  1136. package/setup.js +872 -260
  1137. package/skills/leonardo/SKILL.md +19 -10
  1138. package/skills/leonardo/modules/image-prompter.md +18 -12
  1139. package/skills/leonardo/modules/video-prompter.md +19 -13
  1140. package/skills/synabun/SKILL.md +209 -185
  1141. package/skills/synabun/icon.png +0 -0
  1142. package/skills/synabun/modules/audit.md +380 -374
  1143. package/skills/synabun/modules/changelog.md +15 -6
  1144. package/skills/synabun/modules/idea.md +148 -144
  1145. package/skills/synabun/modules/memorize.md +13 -4
  1146. package/skills/synabun/modules/schedule.md +198 -0
  1147. package/skills/synabun/synabunicon.png +0 -0
  1148. package/updater.cmd +31 -0
  1149. package/updater.mjs +382 -0
  1150. package/updater.sh +29 -0
  1151. package/hooks/claude-code/test-user-learning.mjs +0 -531
  1152. package/neural-interface/public/claude-chat.css +0 -878
  1153. package/neural-interface/public/claude-chat.html +0 -92
  1154. package/neural-interface/public/claude-chat.js +0 -1168
  1155. package/neural-interface/public/index-legacy.html +0 -14677
  1156. package/neural-interface/public/index2d-legacy.html +0 -8193
  1157. package/neural-interface/public/shared/html-term-renderer.js +0 -1022
  1158. package/neural-interface/public/variant/3d/background.js +0 -884
  1159. package/neural-interface/public/variant/3d/camera.js +0 -661
  1160. package/neural-interface/public/variant/3d/gfx.js +0 -116
  1161. package/neural-interface/public/variant/3d/graph.js +0 -2399
  1162. package/neural-interface/public/variant/3d/settings-gfx.js +0 -228
  1163. package/neural-interface/templates/CLAUDE-template.md +0 -211
@@ -0,0 +1,1453 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — rules installer
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // Puts SynaBun's rules where each AI tool loads them for every project, and
6
+ // keeps an untouched copy current:
7
+ //
8
+ // claude owned file ~/.claude/rules/synabun.md (and each Claude account's config dir)
9
+ // opencode owned file <opencode config dir>/synabun.md + one `instructions` entry in config.json
10
+ // codex block ~/.codex/AGENTS.md (account homes link to it, or get their own block)
11
+ // gemini block ~/.gemini/GEMINI.md
12
+ // cursor none User Rules live inside the app: copy and paste ("manual")
13
+ //
14
+ // What it will not do: create the config directory of a CLI that is not
15
+ // installed, overwrite or remove a copy someone edited (unless asked with
16
+ // force: an edited copy that is left in place is the user's from then on, and
17
+ // SynaBun stops managing it), guess at damaged or duplicate markers (force or
18
+ // not), update or remove a block stamped for another tool, delete or replace
19
+ // a symlink it did not make (or one that points somewhere else by now), delete
20
+ // a file it created once the file holds anything but what SynaBun wrote,
21
+ // rewrite JSON it cannot parse or re-serialise JSON it can, take an
22
+ // `instructions` entry it did not add out of OpenCode's config, write over a
23
+ // change made since it read a file, reinstall a copy the user deleted, or
24
+ // touch a project file on its own. Every file is copied to
25
+ // <dataHome>/backups/rulesets/<host>/ before it changes.
26
+ //
27
+ // State: <dataHome>/data/rulesets.json. `revision` goes up by one with every
28
+ // operation that saved. `managed` per host is true (SynaBun manages it), false
29
+ // (the user opted out) or null (never asked). `created` lists the files and
30
+ // directories install made, `links` the symlinks it made and where each
31
+ // pointed. OpenCode's row also holds `instructionsEntry`, the exact string
32
+ // SynaBun added to config.json. `removal` on a row is a removal that is not
33
+ // over: it is saved, with the paths it is about to take, before the first
34
+ // copy goes, and it stays when a copy was refused (damaged markers, an
35
+ // unreadable file). The next boot finishes it (reconcile), and the record is
36
+ // closed with `managed: false` once nothing is left that SynaBun could take.
37
+ //
38
+ // Two writers. Inside one process nothing interleaves: every operation is
39
+ // synchronous from its first read to its last write, and a second one started
40
+ // from inside a running one is refused (REENTRANT). Across processes there is
41
+ // one writer per data home: every operation that changes anything (install,
42
+ // install-all, remove, reconcile, the pasted-copy removal, the setting, the
43
+ // notice) runs inside the writer lock of lock.js, from before the state is
44
+ // loaded until after it is saved. The state is read from disk inside the lock,
45
+ // never from a copy kept in memory. A lock that is still held after
46
+ // `lockTimeoutMs` is BUSY (retryable) and a lock file that cannot be used is
47
+ // LOCK_UNAVAILABLE: either way nothing is changed, and nothing ever runs
48
+ // unlocked. status() only reads and takes no lock.
49
+ //
50
+ // Other programs (the user's editor, the AI tools) do not take that lock, so
51
+ // every carrier write and delete stays conditional: it goes through only while
52
+ // the file still holds the bytes that were inspected. One that changed or
53
+ // vanished in between is left as it is (CHANGED_UNDERNEATH) and is not written
54
+ // again in that call.
55
+ //
56
+ // What a crash can leave behind (the process dies inside an operation; the
57
+ // lock goes with it):
58
+ // - install: a copy on disk that the state does not record yet. It is a
59
+ // copy an explicit install asked for; status shows it, remove takes it,
60
+ // and the boot neither updates nor recreates it until an install records it;
61
+ // - remove: a `removal` record with some copies still there. The next boot
62
+ // finishes it;
63
+ // - a `.synabun-<pid>-*.tmp` file next to a target, and a backup of a file
64
+ // that was not changed after all. Neither is ever read back.
65
+
66
+ import {
67
+ existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmdirSync, statSync,
68
+ symlinkSync, unlinkSync, copyFileSync,
69
+ } from 'node:fs';
70
+ import { basename, dirname, join, resolve } from 'node:path';
71
+ import { homedir } from 'node:os';
72
+ import { isDeepStrictEqual } from 'node:util';
73
+ import { loadLegacyHashes, normalise, renderRuleset, RULESET_HOSTS } from './render.js';
74
+ import { findBlocks, findLegacy, isPristine, removeBlock, removeRange, replaceRange, upsertBlock, wrap } from './managed-text.js';
75
+ import { backupFile, findBrokenLink, readBytes, readJsonStrict, unlinkIfUnchanged, writeTextAtomic } from './fs-safe.js';
76
+ import { addToJsonList, countJsonKey, removeFromJsonList } from './json-edit.js';
77
+ import { createWriterLock, DEFAULT_LOCK_TIMEOUT_MS, LOCK_FILE_NAME } from './lock.js';
78
+
79
+ export const RULESET_STATES = Object.freeze(['not-installed', 'installed', 'newer', 'outdated', 'modified', 'conflict', 'shadowed', 'error', 'manual']);
80
+
81
+ const HOSTS = Object.freeze({
82
+ claude: { label: 'Claude Code', carrier: 'file' },
83
+ codex: { label: 'Codex CLI', carrier: 'block' },
84
+ opencode: { label: 'OpenCode', carrier: 'file' },
85
+ gemini: { label: 'Gemini CLI', carrier: 'block' },
86
+ cursor: { label: 'Cursor', carrier: 'none' },
87
+ });
88
+ const MANAGED_HOSTS = Object.freeze(RULESET_HOSTS.filter((host) => HOSTS[host].carrier !== 'none'));
89
+ const PROJECT_FILES = Object.freeze([['CLAUDE.md', 'claude'], ['AGENTS.md', 'codex'], ['GEMINI.md', 'gemini']]);
90
+ const RANK = Object.freeze({ missing: 0, current: 1, newer: 2, outdated: 3, modified: 4, conflict: 5, error: 6 });
91
+ const MAX_SCAN_BYTES = 2 * 1024 * 1024;
92
+
93
+ function isDir(path) {
94
+ try { return statSync(path).isDirectory(); } catch { return false; }
95
+ }
96
+ function isSymlink(path) {
97
+ try { return lstatSync(path).isSymbolicLink(); } catch { return false; }
98
+ }
99
+ function isPlainObject(value) {
100
+ return !!value && typeof value === 'object' && !Array.isArray(value);
101
+ }
102
+ function unique(list) {
103
+ return [...new Set(list.filter(Boolean))];
104
+ }
105
+ function withoutBom(text) {
106
+ return text.replace(/^/, '');
107
+ }
108
+ // semver.org, to the letter: MAJOR.MINOR.PATCH, an optional pre-release and optional build metadata, and no leading
109
+ // zero on a numeric identifier. `v2.0.0`, `2.0`, `02.0.0` and `2.0.1-01` are not versions.
110
+ const SEMVER_NUMBER = '(?:0|[1-9]\\d*)';
111
+ const SEMVER_PRE_PART = `(?:${SEMVER_NUMBER}|\\d*[A-Za-z-][0-9A-Za-z-]*)`;
112
+ const SEMVER_RE = new RegExp(`^(${SEMVER_NUMBER})\\.(${SEMVER_NUMBER})\\.(${SEMVER_NUMBER})(?:-(${SEMVER_PRE_PART}(?:\\.${SEMVER_PRE_PART})*))?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$`);
113
+ /** {core: [major, minor, patch], pre: [...]} of a semver string, or null for anything else. Build metadata does not count. */
114
+ function parseSemver(version) {
115
+ const match = SEMVER_RE.exec(typeof version === 'string' ? version : '');
116
+ return match ? { core: [BigInt(match[1]), BigInt(match[2]), BigInt(match[3])], pre: match[4] ? match[4].split('.') : [] } : null;
117
+ }
118
+ /** True when version `a` is greater than `b` by semver precedence. A version that is not semver is never greater, and nothing is greater than one. */
119
+ function semverGreater(a, b) {
120
+ const x = parseSemver(a);
121
+ const y = parseSemver(b);
122
+ if (!x || !y) return false;
123
+ for (let i = 0; i < 3; i++) if (x.core[i] !== y.core[i]) return x.core[i] > y.core[i];
124
+ if (!x.pre.length || !y.pre.length) return !x.pre.length && y.pre.length > 0; // a release is above its pre-releases
125
+ for (let i = 0; i < Math.max(x.pre.length, y.pre.length); i++) {
126
+ const p = x.pre[i];
127
+ const q = y.pre[i];
128
+ if (p === undefined) return false;
129
+ if (q === undefined) return true;
130
+ if (p === q) continue;
131
+ const pNumber = /^\d+$/.test(p);
132
+ const qNumber = /^\d+$/.test(q);
133
+ if (pNumber && qNumber) return BigInt(p) > BigInt(q);
134
+ if (pNumber !== qNumber) return qNumber; // a number sorts below a word
135
+ return p > q;
136
+ }
137
+ return false;
138
+ }
139
+ /** One identity per file however it is reached: through a symlink, a linked directory, or not created yet. */
140
+ function realKey(path, depth = 0) {
141
+ try { return realpathSync(path); } catch { /* not there, or a dangling link */ }
142
+ if (depth < 16) {
143
+ try {
144
+ if (lstatSync(path).isSymbolicLink()) return realKey(resolve(dirname(path), readlinkSync(path)), depth + 1);
145
+ } catch { /* not a link */ }
146
+ }
147
+ const parent = dirname(path);
148
+ if (parent === path) return path;
149
+ return join(realKey(parent, depth), basename(path));
150
+ }
151
+
152
+ function emptyHostState() {
153
+ return { managed: null, installedVersion: null, installedSha: null, path: null, paths: [], created: [], links: [], appliedAt: null, backup: null };
154
+ }
155
+ function defaultState() {
156
+ const hosts = {};
157
+ for (const host of MANAGED_HOSTS) hosts[host] = emptyHostState();
158
+ return { revision: 0, autoUpdate: true, hosts, notice: null, offerAcked: [] };
159
+ }
160
+ const INTERRUPTED = 'The removal was interrupted before it finished. SynaBun finishes it the next time it starts.';
161
+ function stillChanging(path) {
162
+ return `${path} changed while SynaBun was working on it, so it was left as it is. Try again.`;
163
+ }
164
+
165
+ export function createRulesetInstaller({
166
+ home = process.env.USERPROFILE || process.env.HOME || homedir(),
167
+ dataHome,
168
+ projects = [],
169
+ env = process.env,
170
+ opencodeConfigPath = null,
171
+ render = renderRuleset,
172
+ legacyHashes = null,
173
+ now = () => new Date(),
174
+ log = () => {},
175
+ hooks = {},
176
+ lockTimeoutMs = DEFAULT_LOCK_TIMEOUT_MS,
177
+ } = {}) {
178
+ if (!dataHome) throw new Error('createRulesetInstaller requires dataHome');
179
+ if (!home) throw new Error('createRulesetInstaller requires home');
180
+ const statePath = join(dataHome, 'data', 'rulesets.json');
181
+ const backupRoot = join(dataHome, 'backups', 'rulesets');
182
+ // The writer lock of this data home. Used for nothing else, and never opened, read, copied or backed up by
183
+ // anything else: see lock.js for why even one stray look at the file can drop the lock.
184
+ const lockPath = join(dataHome, 'data', LOCK_FILE_NAME);
185
+ const lock = createWriterLock(lockPath, { timeoutMs: lockTimeoutMs });
186
+ const legacy = () => legacyHashes || loadLegacyHashes();
187
+ const stamp = () => now().toISOString();
188
+
189
+ // ── One operation at a time ────────────────────────────────────────────────
190
+ // `hooks` are seams for tests, each a point where another program could act
191
+ // (or where a test holds an operation still, inside the lock):
192
+ // afterInspect(host, path, status) once a carrier was read and is about to be
193
+ // written, beforeCommit(path) right before a file (a carrier, config.json,
194
+ // the state file) is checked against what was read and replaced or deleted,
195
+ // afterCheck(path) between that check and a delete, afterSave(revision) once
196
+ // the state file was saved.
197
+ let running = null;
198
+ /** Nothing here is asynchronous, so the only way two operations could overlap in one process is one starting inside the other. */
199
+ function exclusive(name, work) {
200
+ if (running) {
201
+ const error = new Error(`The rules installer is busy with "${running}", so "${name}" cannot start inside it.`);
202
+ error.code = 'REENTRANT';
203
+ throw error;
204
+ }
205
+ running = name;
206
+ try { return work(); } finally { running = null; }
207
+ }
208
+ /**
209
+ * `work()` inside the writer lock of this data home. When the lock cannot be
210
+ * taken, nothing ran and nothing changed: the answer is `refused(why)`, with
211
+ * why = {code: 'BUSY' | 'LOCK_UNAVAILABLE', retryable, error}, and one log line.
212
+ */
213
+ function locked(name, refused, work) {
214
+ return exclusive(name, () => {
215
+ try { lock.enter(); } catch (error) {
216
+ if (error?.code !== 'BUSY' && error?.code !== 'LOCK_UNAVAILABLE') throw error;
217
+ log('rulesets', `${name} skipped: ${error.message}`);
218
+ return refused({ code: error.code, retryable: error.code === 'BUSY', error: error.message });
219
+ }
220
+ try { return work(); } finally { lock.leave(); }
221
+ });
222
+ }
223
+ /** Delete `path` while it still holds `expect`: a file another program changed or took away is left to it. */
224
+ function unlinkUnchanged(path, expect) {
225
+ unlinkIfUnchanged(path, expect, { beforeUnlink: hooks.beforeCommit, afterCheck: hooks.afterCheck });
226
+ }
227
+ /** Replace `path` with `next`, unless it no longer holds `original` (the bytes that were read, or null for no file). */
228
+ function commit(path, next, original) {
229
+ return writeTextAtomic(path, next, { expect: original, beforeRename: hooks.beforeCommit });
230
+ }
231
+ /**
232
+ * Copy `path` to the host's backups, then run `change`. The copy is kept (and returned) only when the change went
233
+ * through: a write that was refused changed nothing, so it leaves no backup and nothing to record either.
234
+ */
235
+ function backedUp(path, host, change) {
236
+ const backup = backupFile(path, join(backupRoot, host));
237
+ try { change(); } catch (error) {
238
+ if (backup) { try { unlinkSync(backup); } catch { /* a stray copy is harmless */ } }
239
+ throw error;
240
+ }
241
+ return backup;
242
+ }
243
+
244
+ // ── State ──────────────────────────────────────────────────────────────────
245
+ /** {state, revision, before, saved}: the state as it is on disk now, its revision, its serialised form as loaded, and whether this view was saved. */
246
+ function readState() {
247
+ const state = defaultState();
248
+ const view = () => ({ state, revision: state.revision, before: serialise(state), saved: false });
249
+ let saved;
250
+ try {
251
+ const bytes = readBytes(statePath);
252
+ if (bytes === null) return view();
253
+ saved = readJsonStrict(statePath, { text: bytes.toString('utf8') });
254
+ } catch (error) {
255
+ // Lost state means "never asked" for every host: nothing gets written on a guess.
256
+ log('rulesets', `state file unreadable, starting clean: ${error?.message || error}`);
257
+ state.corrupt = true;
258
+ return view();
259
+ }
260
+ if (!isPlainObject(saved)) return view();
261
+ if (Number.isSafeInteger(saved.revision) && saved.revision > 0) state.revision = saved.revision;
262
+ if (typeof saved.autoUpdate === 'boolean') state.autoUpdate = saved.autoUpdate;
263
+ if (isPlainObject(saved.notice)) state.notice = saved.notice;
264
+ if (Array.isArray(saved.offerAcked)) state.offerAcked = saved.offerAcked.filter((host) => MANAGED_HOSTS.includes(host));
265
+ for (const host of MANAGED_HOSTS) {
266
+ const row = saved.hosts?.[host];
267
+ if (!isPlainObject(row)) continue;
268
+ state.hosts[host] = {
269
+ ...emptyHostState(),
270
+ ...row,
271
+ managed: row.managed === true ? true : row.managed === false ? false : null,
272
+ paths: Array.isArray(row.paths) ? row.paths : [],
273
+ created: Array.isArray(row.created) ? row.created : [],
274
+ links: Array.isArray(row.links) ? row.links.filter((link) => isPlainObject(link) && typeof link.path === 'string' && typeof link.target === 'string') : [],
275
+ };
276
+ }
277
+ return view();
278
+ }
279
+ function serialise(state) {
280
+ const { corrupt, ...rest } = state;
281
+ return JSON.stringify(rest, null, 2) + '\n';
282
+ }
283
+ /**
284
+ * Save the state this view was loaded from, inside the lock. Writes only when
285
+ * something changed (a quiet reconcile leaves the disk alone), in one atomic
286
+ * replace. The revision is the loaded one plus one, however many times one
287
+ * operation saves (a removal saves its intent first, then its outcome).
288
+ */
289
+ function saveState(view) {
290
+ const { state } = view;
291
+ const text = () => serialise(state);
292
+ if (!state.corrupt && text() === view.before) return false;
293
+ state.revision = view.revision + 1;
294
+ mkdirSync(dirname(statePath), { recursive: true });
295
+ if (state.corrupt) { try { copyFileSync(statePath, `${statePath}.corrupt`); } catch { /* nothing to keep */ } }
296
+ writeTextAtomic(statePath, text(), { beforeRename: hooks.beforeCommit });
297
+ delete state.corrupt;
298
+ view.before = text();
299
+ view.saved = true;
300
+ if (hooks.afterSave) hooks.afterSave(state.revision);
301
+ return true;
302
+ }
303
+
304
+ // ── One operation: lock, load, work, save ──────────────────────────────────
305
+ // What one call did, per host: what changed, the files, directories and
306
+ // links it made, and the copies it found current.
307
+ function newTally() {
308
+ const tally = { changed: false, backupPath: null, legacy: [], warnings: [], created: [], paths: [], links: [] };
309
+ tally.backup = (path) => { if (path && !tally.backupPath) tally.backupPath = path; };
310
+ return tally;
311
+ }
312
+ function newCall(view) {
313
+ const tallies = new Map();
314
+ const tally = (host) => {
315
+ if (!tallies.has(host)) tallies.set(host, newTally());
316
+ return tallies.get(host);
317
+ };
318
+ // checkpoint(): save the state as it stands, before the operation goes on (a removal records its intent this way).
319
+ return { tally, checkpoint: () => saveState(view) };
320
+ }
321
+ /** The row's own record plus what this call added to it. */
322
+ function mergedRecord(row, tally) {
323
+ const relinked = new Set(tally.links.map((link) => link.path));
324
+ return {
325
+ created: unique([...row.created, ...tally.created]),
326
+ paths: unique([...row.paths, ...tally.paths]),
327
+ links: [...row.links.filter((link) => !relinked.has(link.path)), ...tally.links],
328
+ };
329
+ }
330
+ /**
331
+ * One operation against the state file, inside the writer lock: the state is
332
+ * loaded from disk, `work(state, call)` does the carrier work and changes
333
+ * `state`, and the state is saved. Returns [result, wrote]; when the lock
334
+ * could not be taken, [refused(why), false] and nothing ran.
335
+ */
336
+ function transact(name, refused, work) {
337
+ return locked(name, (why) => [refused(why), false], () => {
338
+ const view = readState();
339
+ const result = work(view.state, newCall(view));
340
+ saveState(view);
341
+ return [result, view.saved];
342
+ });
343
+ }
344
+ function uniqueNotes(notes) {
345
+ const seen = new Set();
346
+ return notes.filter((note) => {
347
+ const key = [note.path, note.host, note.kind, ...(note.hosts || [])].join('\n');
348
+ return !seen.has(key) && seen.add(key);
349
+ });
350
+ }
351
+
352
+ // ── Where things live ──────────────────────────────────────────────────────
353
+ // Resolved the way the MCP toggle routes resolve them: Claude, Codex and
354
+ // Gemini under the home directory, OpenCode under XDG_CONFIG_HOME when set.
355
+ function layout() {
356
+ // The server passes the resolver its /api/opencode/mcp toggle uses, so both always edit the same file.
357
+ const opencodeConfig = opencodeConfigPath
358
+ ? resolve(opencodeConfigPath())
359
+ : join(env?.XDG_CONFIG_HOME ? join(env.XDG_CONFIG_HOME, 'opencode') : join(home, '.config', 'opencode'), 'config.json');
360
+ return {
361
+ claudeDir: join(home, '.claude'),
362
+ codexHome: join(home, '.codex'),
363
+ geminiDir: join(home, '.gemini'),
364
+ opencodeDir: dirname(opencodeConfig),
365
+ opencodeConfig,
366
+ };
367
+ }
368
+ /** Extra account directories from SynaBun's own registries (data/claude-accounts.json, data/codex-accounts.json). */
369
+ function accountHomes(registry, defaultHome) {
370
+ try {
371
+ const data = JSON.parse(readFileSync(join(dataHome, 'data', registry), 'utf8'));
372
+ const base = realKey(defaultHome);
373
+ return unique((data?.accounts || [])
374
+ .filter((row) => row && row.id !== 'default' && typeof row.home === 'string' && row.home)
375
+ .map((row) => resolve(row.home)))
376
+ .filter((dir) => isDir(dir) && realKey(dir) !== base);
377
+ } catch {
378
+ return [];
379
+ }
380
+ }
381
+ function codexAccounts() {
382
+ const { codexHome } = layout();
383
+ const primaryKey = realKey(join(codexHome, 'AGENTS.md'));
384
+ return accountHomes('codex-accounts.json', codexHome).map((dir) => {
385
+ const path = join(dir, 'AGENTS.md');
386
+ let present = true;
387
+ try { lstatSync(path); } catch { present = false; }
388
+ const kind = !present ? 'absent' : realKey(path) === primaryKey ? 'linked' : 'file';
389
+ return { dir, path, kind };
390
+ });
391
+ }
392
+ /** Every carrier of a host, the default location first. */
393
+ function targets(host) {
394
+ const where = layout();
395
+ if (host === 'claude') {
396
+ const seen = new Set();
397
+ const list = [];
398
+ for (const dir of [where.claudeDir, ...accountHomes('claude-accounts.json', where.claudeDir)]) {
399
+ const path = join(dir, 'rules', 'synabun.md');
400
+ const key = realKey(path);
401
+ if (seen.has(key)) continue;
402
+ seen.add(key);
403
+ list.push({ path, dir, legacyFile: join(dir, 'CLAUDE.md') });
404
+ }
405
+ return list;
406
+ }
407
+ if (host === 'opencode') {
408
+ return [{ path: join(where.opencodeDir, 'synabun.md'), dir: where.opencodeDir, legacyFile: join(where.opencodeDir, 'AGENTS.md') }];
409
+ }
410
+ if (host === 'codex') {
411
+ return [
412
+ { path: join(where.codexHome, 'AGENTS.md'), dir: where.codexHome },
413
+ ...codexAccounts().filter((account) => account.kind === 'file').map(({ path, dir }) => ({ path, dir })),
414
+ ];
415
+ }
416
+ if (host === 'gemini') return [{ path: join(where.geminiDir, 'GEMINI.md'), dir: where.geminiDir }];
417
+ return [];
418
+ }
419
+ function projectPaths() {
420
+ let list = [];
421
+ try { list = typeof projects === 'function' ? projects() : projects; } catch { list = []; }
422
+ return unique((Array.isArray(list) ? list : []).map((entry) => (typeof entry === 'string' ? entry : entry?.path)).filter((path) => typeof path === 'string' && path));
423
+ }
424
+ function mcpRegistered(host) {
425
+ const where = layout();
426
+ try {
427
+ if (host === 'claude') return !!JSON.parse(readFileSync(join(home, '.claude.json'), 'utf8'))?.mcpServers?.SynaBun;
428
+ if (host === 'gemini') return !!JSON.parse(readFileSync(join(where.geminiDir, 'settings.json'), 'utf8'))?.mcpServers?.SynaBun;
429
+ if (host === 'codex') return /^\[mcp_servers\.SynaBun\]/m.test(readFileSync(join(where.codexHome, 'config.toml'), 'utf8'));
430
+ if (host === 'opencode') return !!JSON.parse(readFileSync(where.opencodeConfig, 'utf8'))?.mcp?.SynaBun;
431
+ } catch { /* no config, no registration */ }
432
+ return false;
433
+ }
434
+
435
+ // ── Reading what is on disk ────────────────────────────────────────────────
436
+ function expectedBlock(host) {
437
+ const rendered = render(host);
438
+ const block = wrap(rendered.text, { version: rendered.version, host });
439
+ return { block, version: rendered.version, sha: findBlocks(block).blocks[0].sha };
440
+ }
441
+ function foreignBlock(host, block, path) {
442
+ return `The SynaBun block in ${path} is stamped for ${HOSTS[block.host]?.label || block.host} (host=${block.host}), not for ${HOSTS[host].label}, so SynaBun leaves it alone.`;
443
+ }
444
+ /** The sentence for a link that leads nowhere: nothing is read through it, and nothing is ever written through it. */
445
+ function brokenLinkDetail(path, broken) {
446
+ const through = broken.link === path ? '' : ` (${path} leads to it)`;
447
+ return `${broken.link}${through} is a symbolic link to ${broken.target}, which does not exist. SynaBun does not write through a broken link: fix or remove the link, then install again.`;
448
+ }
449
+ /** What install and reconcile say about an untouched copy stamped with a version above the one this SynaBun ships. */
450
+ function newerDetail(path, block, expected) {
451
+ return `${path} holds version ${block.version} of the SynaBun rules, written by a newer SynaBun than this one (${expected.version}), so it is left as it is.`;
452
+ }
453
+ /**
454
+ * missing | current | newer (untouched, stamped with a greater version than this SynaBun ships) | outdated (untouched,
455
+ * other text) | modified (edited) | conflict (damaged markers, or another tool's stamp) | error.
456
+ * `bytes` is what the file held when it was read: the one thing a later write or delete is checked against.
457
+ */
458
+ function inspect(host, target, expected) {
459
+ let bytes;
460
+ try { bytes = readBytes(target.path); } catch (error) {
461
+ return { status: 'error', detail: `Could not read ${target.path}: ${error?.message || error}` };
462
+ }
463
+ if (bytes === null) {
464
+ let broken = null;
465
+ try { broken = findBrokenLink(target.path); } catch (error) {
466
+ return { status: 'error', detail: `Could not read ${target.path}: ${error?.message || error}` };
467
+ }
468
+ return broken ? { status: 'error', detail: brokenLinkDetail(target.path, broken) } : { status: 'missing' };
469
+ }
470
+ const text = bytes.toString('utf8');
471
+ const { blocks, conflict } = findBlocks(text);
472
+ if (conflict) {
473
+ // Never settled by overwriting, forced or not: the message is the way out.
474
+ const repair = HOSTS[host].carrier === 'file'
475
+ ? 'remove the extra or damaged SynaBun marker lines, or delete the file, then install again'
476
+ : 'remove the extra or damaged SynaBun marker lines by hand, then install again';
477
+ return { status: 'conflict', text, bytes, detail: `${conflict.message} in ${target.path}. SynaBun will not guess which part is its own, and Replace does not apply here: ${repair}.` };
478
+ }
479
+ const block = blocks[0] || null;
480
+ // A stamp for another tool is not this host's copy, however untouched it is: never updated, never removed.
481
+ if (block && block.host !== host) return { status: 'conflict', text, bytes, detail: `${foreignBlock(host, block, target.path)} Replace does not apply here: take that block out by hand, then install again.` };
482
+ if (HOSTS[host].carrier === 'file') {
483
+ if (normalise(text) === expected.block) return { status: 'current', text, bytes, block };
484
+ if (!block) return { status: 'modified', text, bytes, block, detail: `${target.path} has no SynaBun markers, so SynaBun treats it as your own file and never replaces it. Move or delete it, then install again.` };
485
+ const untouched = isPristine(block) && withoutBom(text.slice(0, block.start) + text.slice(block.eolEnd)).trim() === '';
486
+ if (!untouched) return { status: 'modified', text, bytes, block, detail: `${target.path} was edited, so SynaBun leaves it alone.` };
487
+ return untouchedCopy(target, expected, { text, bytes, block });
488
+ }
489
+ if (!block) return { status: 'missing', text, bytes };
490
+ if (normalise(block.text) === expected.block) return { status: 'current', text, bytes, block };
491
+ if (isPristine(block)) return untouchedCopy(target, expected, { text, bytes, block });
492
+ return { status: 'modified', text, bytes, block, detail: `The SynaBun block in ${target.path} was edited, so SynaBun leaves it alone.` };
493
+ }
494
+
495
+ /**
496
+ * An untouched copy that is not the text this SynaBun ships. Two SynaBun versions can share one data home: a copy
497
+ * stamped with a greater version is the newer one's and is never downgraded (`newer`); anything else is `outdated`.
498
+ */
499
+ function untouchedCopy(target, expected, found) {
500
+ if (!semverGreater(found.block.version, expected.version)) return { status: 'outdated', ...found };
501
+ return { status: 'newer', ...found, detail: newerDetail(target.path, found.block, expected) };
502
+ }
503
+
504
+ // ── OpenCode's `instructions` entry ────────────────────────────────────────
505
+ function opencodeEntry() {
506
+ return join(layout().opencodeDir, 'synabun.md').replace(/\\/g, '/');
507
+ }
508
+ function samePath(a, b) {
509
+ return typeof a === 'string' && resolve(a) === resolve(b);
510
+ }
511
+ /**
512
+ * The file an `instructions` entry of `configPath` names, or null when the entry is not one plain path: a URL, or
513
+ * a pattern with glob characters. A leading `~` is the installer's home, and a relative path starts at the
514
+ * directory of config.json (never at this process's working directory).
515
+ */
516
+ function entryFile(value, configPath) {
517
+ if (typeof value !== 'string' || !value) return null;
518
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value) || /[*?[\]{}]/.test(value)) return null;
519
+ const path = value === '~' || /^~[\\/]/.test(value) ? join(home, value.slice(1)) : value;
520
+ return resolve(dirname(configPath), path);
521
+ }
522
+ /**
523
+ * True when an entry leads to SynaBun's owned file, however it is spelled: `~/...`, a relative path, or a symlink
524
+ * to the file. Compared by real path when both exist, by the normalised string otherwise.
525
+ */
526
+ function entryIsOwnedFile(value, configPath) {
527
+ const path = entryFile(value, configPath);
528
+ if (path === null) return false;
529
+ const owned = join(layout().opencodeDir, 'synabun.md');
530
+ if (path === resolve(owned)) return true;
531
+ try { return realpathSync(path) === realpathSync(owned); } catch { return false; }
532
+ }
533
+ function readOpencodeConfig() {
534
+ const path = layout().opencodeConfig;
535
+ const entry = opencodeEntry();
536
+ const refuse = (why) => ({ path, exists: true, hasEntry: false, cfg: null, text: null, bytes: null, error: `${path} ${why}. SynaBun will not rewrite it: fix the file, or add "${entry}" to its "instructions" list by hand.` });
537
+ let bytes;
538
+ let text;
539
+ let cfg;
540
+ // Read once: the bytes that are parsed are the bytes a later write is checked against.
541
+ try { bytes = readBytes(path); } catch (error) { return refuse(`could not be read (${error?.message || error})`); }
542
+ if (bytes === null) {
543
+ // A link that leads nowhere is not "no config yet": creating the file would write through it, or over it.
544
+ let broken = null;
545
+ try { broken = findBrokenLink(path); } catch (error) { return refuse(`could not be read (${error?.message || error})`); }
546
+ if (broken) return refuse(`is a broken symbolic link (${broken.link} points at ${broken.target}, which does not exist)`);
547
+ return { path, exists: false, hasEntry: false, cfg: null, text: null, bytes: null, error: null };
548
+ }
549
+ try { text = bytes.toString('utf8'); cfg = readJsonStrict(path, { text }); } catch (error) {
550
+ return refuse(error?.code === 'INVALID_JSON' ? 'is not plain JSON (a syntax error, or comments)' : `could not be read (${error?.message || error})`);
551
+ }
552
+ if (!isPlainObject(cfg)) return refuse('does not hold a JSON object');
553
+ if (cfg.instructions !== undefined && !Array.isArray(cfg.instructions)) return refuse('has an "instructions" value that is not a list');
554
+ // JSON.parse keeps the last of two keys: an edit of either one would strand an entry or lose the other list.
555
+ let lists;
556
+ try { lists = countJsonKey(text, 'instructions'); } catch { return refuse('has a layout SynaBun cannot edit safely'); }
557
+ if (lists > 1) return refuse('has more than one top-level "instructions" key (only the last one counts)');
558
+ return { path, exists: true, hasEntry: (cfg.instructions || []).some((value) => entryIsOwnedFile(value, path)), cfg, text, bytes, error: null };
559
+ }
560
+ /**
561
+ * The text of config.json after `edit()`, accepted only when it parses to
562
+ * exactly `cfg`. There is no other way to write this file: when the edit
563
+ * cannot be made on the text, SynaBun refuses rather than write the whole
564
+ * object out again and lose what the user's file spells its own way.
565
+ */
566
+ function configText(path, cfg, edit) {
567
+ try {
568
+ const text = edit();
569
+ if (isDeepStrictEqual(JSON.parse(withoutBom(text)), cfg)) return text;
570
+ } catch { /* text the editor could not follow */ }
571
+ throw new Error(`${path} could not be edited safely, so SynaBun did not write it. Change its "instructions" list by hand: the entry is "${opencodeEntry()}".`);
572
+ }
573
+ /** What install writes when there is no config.json yet: remove deletes the file only while it still holds exactly this. */
574
+ function newConfigText(value) {
575
+ return addToJsonList('{}\n', 'instructions', value);
576
+ }
577
+ /** The string SynaBun added to `config`, when its record is about this very file and the string is still listed. */
578
+ function ownedEntry(row, config) {
579
+ const owned = row.instructionsEntry;
580
+ if (!owned || typeof owned.value !== 'string' || !config.exists || !config.cfg || !samePath(owned.config, config.path)) return null;
581
+ return (config.cfg.instructions || []).includes(owned.value) ? owned.value : null;
582
+ }
583
+ /**
584
+ * The `instructions` entries that resolve to SynaBun's owned file and that SynaBun has no record of adding: the
585
+ * user's own lines, however they are spelled (`~/...`, relative to config.json, a symlink to the file). One listing
586
+ * of the recorded string is SynaBun's; every other is theirs. A URL or a glob pattern is never one of them.
587
+ */
588
+ function userEntries(row, config) {
589
+ if (!config.exists || !config.cfg) return [];
590
+ const list = config.cfg.instructions || [];
591
+ const mine = ownedEntry(row, config) === null ? -1 : list.lastIndexOf(row.instructionsEntry.value);
592
+ return list.filter((value, index) => index !== mine && entryIsOwnedFile(value, config.path));
593
+ }
594
+ /**
595
+ * One change to config.json. `plan(config)` answers null (nothing to do) or
596
+ * {text, done?}; `text: null` deletes the file. The file is replaced only
597
+ * while it still holds the bytes that were read: a change by another program
598
+ * in between is CHANGED_UNDERNEATH, and config.json is left to it.
599
+ */
600
+ function editOpencodeConfig(tally, plan) {
601
+ const config = readOpencodeConfig();
602
+ if (config.error) throw new Error(config.error);
603
+ const step = plan(config);
604
+ if (!step) return;
605
+ const change = () => {
606
+ if (step.text === null) unlinkUnchanged(config.path, config.bytes);
607
+ else writeTextAtomic(config.path, step.text, { expect: config.bytes, beforeRename: hooks.beforeCommit });
608
+ };
609
+ if (config.exists) tally.backup(backedUp(config.path, 'opencode', change));
610
+ else change();
611
+ tally.changed = true;
612
+ if (step.done) step.done();
613
+ }
614
+ /** Lists synabun.md in config.json unless it is listed already, and records the string SynaBun added. */
615
+ function addOpencodeEntry(row, tally) {
616
+ const value = opencodeEntry();
617
+ let listed = null;
618
+ editOpencodeConfig(tally, (config) => {
619
+ listed = config;
620
+ if (config.hasEntry) return null;
621
+ const cfg = { ...(config.cfg || {}), instructions: [...(config.cfg?.instructions || []), value] };
622
+ return {
623
+ text: configText(config.path, cfg, () => (config.exists ? addToJsonList(config.text, 'instructions', value) : newConfigText(value))),
624
+ done: () => {
625
+ if (!config.exists) tally.created.push(config.path);
626
+ if (config.cfg?.instructions === undefined) tally.created.push(`${config.path}#instructions`);
627
+ row.instructionsEntry = { config: config.path, value };
628
+ listed = null;
629
+ },
630
+ };
631
+ });
632
+ // Listed already: ours from an earlier install when the recorded string is still there, otherwise the user's own.
633
+ const owned = row.instructionsEntry;
634
+ if (listed && owned && !(samePath(owned.config, listed.path) && (listed.cfg.instructions || []).includes(owned.value))) delete row.instructionsEntry;
635
+ }
636
+ /**
637
+ * Takes out the one string SynaBun added. An entry the user wrote, however it
638
+ * is spelled, is touched only with `theirs`: a forced remove, which takes
639
+ * every line that points at the owned file so that none is left pointing at
640
+ * a file that is gone. Each entry is edited out of the text on its own.
641
+ */
642
+ function removeOpencodeEntry(row, tally, { theirs = false } = {}) {
643
+ editOpencodeConfig(tally, (config) => {
644
+ if (!config.exists) return null;
645
+ const mine = ownedEntry(row, config);
646
+ const others = theirs ? userEntries(row, config) : [];
647
+ if (mine === null && !others.length) return null;
648
+ // A config.json install created goes only while its bytes are still what install wrote. Anything the user
649
+ // added since makes it their file: the entries are edited out and the file stays.
650
+ if (mine !== null && row.created.includes(config.path) && config.text === newConfigText(mine)) return { text: null };
651
+ const dropKey = row.created.includes(`${config.path}#instructions`);
652
+ const drop = [...(mine === null ? [] : [mine]), ...others];
653
+ const list = [...(config.cfg.instructions || [])];
654
+ for (const value of drop) list.splice(list.lastIndexOf(value), 1);
655
+ const cfg = { ...config.cfg, instructions: list };
656
+ if (!list.length && dropKey) delete cfg.instructions;
657
+ return {
658
+ text: configText(config.path, cfg, () => drop.reduce(
659
+ (text, value, index) => removeFromJsonList(text, 'instructions', value, { dropEmptyKey: dropKey && index === drop.length - 1 }), config.text)),
660
+ };
661
+ });
662
+ }
663
+
664
+ // ── Writing ────────────────────────────────────────────────────────────────
665
+ /**
666
+ * The pasted copies in `text`, sorted by what install may do with them:
667
+ * `own` are hash-exact copies of a ruleset served for this very host, the
668
+ * only ones replaced without asking. A hash-exact copy of another tool's
669
+ * rules and a heading-only match are `notes`: left alone, reported for review.
670
+ */
671
+ function sortLegacy(host, path, text) {
672
+ const spans = findLegacy(text, legacy());
673
+ const exact = spans.filter((span) => span.kind === 'exact');
674
+ const own = exact.filter((span) => (span.hosts || []).includes(host));
675
+ const others = exact.filter((span) => !own.includes(span));
676
+ const notes = [];
677
+ if (spans.some((span) => span.kind === 'heading')) notes.push({ path, host, kind: 'heading' });
678
+ if (others.length) notes.push({ path, host, kind: 'other-host', hosts: unique(others.flatMap((span) => span.hosts || [])) });
679
+ return { own, notes };
680
+ }
681
+ /**
682
+ * Take this host's hash-exact pasted copies out of a file. Everything else is
683
+ * reported, never edited. A file that changed since it was read is left as it
684
+ * is, with a warning: the copy stays on the list of pasted copies.
685
+ */
686
+ function stripLegacy(host, file, tally) {
687
+ let bytes;
688
+ try { bytes = readBytes(file); } catch { return; }
689
+ if (bytes === null) return;
690
+ const text = bytes.toString('utf8');
691
+ const { own, notes } = sortLegacy(host, file, text);
692
+ tally.legacy.push(...notes);
693
+ if (!own.length) return;
694
+ let next = text;
695
+ for (const span of [...own].reverse()) next = removeRange(next, span.start, span.end);
696
+ try { tally.backup(backedUp(file, host, () => commit(file, next, bytes))); } catch (error) {
697
+ if (error?.code !== 'CHANGED_UNDERNEATH') throw error;
698
+ tally.warnings.push(`${file} changed while SynaBun was taking the pasted copy out of it, so it was left as it is.`);
699
+ return;
700
+ }
701
+ tally.changed = true;
702
+ }
703
+ /**
704
+ * One carrier. Returns the status it ended in; writes only when the copy is
705
+ * missing, untouched and old, or (forced) one well-formed copy that was
706
+ * edited or that a newer SynaBun wrote. Damaged or duplicate markers are
707
+ * refused whatever `force` says, and so is an owned file without any marker:
708
+ * neither is a copy SynaBun can vouch for.
709
+ *
710
+ * One look, one write: the file is replaced only while it still holds the
711
+ * bytes that were inspected. When it changed or vanished in between, the
712
+ * answer is an error with code CHANGED_UNDERNEATH and the carrier is left as
713
+ * it is; nothing here looks again and writes after all.
714
+ */
715
+ function applyTarget(host, target, expected, { force = false } = {}, tally) {
716
+ const found = inspect(host, target, expected);
717
+ if (found.status === 'current' || found.status === 'error' || found.status === 'conflict') return found;
718
+ if (found.status === 'modified' && !(force && found.block)) return found;
719
+ if (found.status === 'newer' && !force) return found; // never downgraded on its own
720
+ if (hooks.afterInspect) hooks.afterInspect(host, target.path, found.status);
721
+ const original = found.bytes ?? null;
722
+ const notes = [];
723
+ let next;
724
+ let madeDir = null;
725
+
726
+ if (HOSTS[host].carrier === 'file') {
727
+ next = `${expected.block}\n`;
728
+ const dir = dirname(target.path);
729
+ if (!isDir(dir)) {
730
+ // The file was read a moment ago, so its directory was there: it went away with the file since.
731
+ if (original !== null) return { status: 'error', code: 'CHANGED_UNDERNEATH', detail: stillChanging(target.path) };
732
+ // Only the leaf (`rules/`): a missing CLI directory was refused before we got here.
733
+ mkdirSync(dir);
734
+ tally.created.push(dir);
735
+ madeDir = dir;
736
+ }
737
+ } else {
738
+ const text = found.text ?? '';
739
+ if (found.block) {
740
+ next = upsertBlock(text, expected.block);
741
+ } else {
742
+ const sorted = sortLegacy(host, target.path, text);
743
+ notes.push(...sorted.notes);
744
+ if (sorted.own.length) {
745
+ // The pasted copy becomes the managed block where it stood; later duplicates go.
746
+ next = text;
747
+ for (const span of sorted.own.slice(1).reverse()) next = removeRange(next, span.start, span.end);
748
+ next = replaceRange(next, sorted.own[0].start, sorted.own[0].end, expected.block);
749
+ } else {
750
+ next = upsertBlock(text, expected.block);
751
+ }
752
+ }
753
+ // A block added below a code fence that never closes would be quoted text, and be added again next time.
754
+ const check = findBlocks(next);
755
+ if (check.conflict || check.blocks.length !== 1 || normalise(check.blocks[0].text) !== expected.block) {
756
+ return { status: 'error', text, detail: `${target.path} has a code fence that is never closed, so a block added to it would read as part of that code. Close the fence, then install again.` };
757
+ }
758
+ }
759
+ try {
760
+ if (original === null) commit(target.path, next, original);
761
+ else tally.backup(backedUp(target.path, host, () => commit(target.path, next, original)));
762
+ } catch (error) {
763
+ if (madeDir) {
764
+ // Nothing went into the directory this call made for the file: it goes again, while it is empty.
765
+ try { rmdirSync(madeDir); tally.created = tally.created.filter((path) => path !== madeDir); } catch { /* in use by now: it stays on record */ }
766
+ }
767
+ if (error?.code !== 'CHANGED_UNDERNEATH') throw error;
768
+ return { status: 'error', code: 'CHANGED_UNDERNEATH', detail: error.message };
769
+ }
770
+ if (original === null) tally.created.push(target.path);
771
+ tally.legacy.push(...notes);
772
+ tally.changed = true;
773
+ return { status: 'current', written: true };
774
+ }
775
+ /** True while `link.path` is a symlink that still points where install pointed it. */
776
+ function isOwnLink(link) {
777
+ try {
778
+ return lstatSync(link.path).isSymbolicLink() && resolve(dirname(link.path), readlinkSync(link.path)) === resolve(dirname(link.path), link.target);
779
+ } catch {
780
+ return false;
781
+ }
782
+ }
783
+ /**
784
+ * Codex account homes without an AGENTS.md get a link to the shared one, as config.toml and prompts do.
785
+ * Each link is recorded with where it points: that pair, not the path alone, is what remove may unlink.
786
+ */
787
+ function linkCodexAccounts(row, tally, { onlyNew = false } = {}) {
788
+ const primary = join(layout().codexHome, 'AGENTS.md');
789
+ if (!existsSync(primary)) return [];
790
+ const added = [];
791
+ for (const account of codexAccounts()) {
792
+ if (account.kind !== 'absent') continue;
793
+ if (onlyNew && row.paths.includes(account.path)) continue;
794
+ try {
795
+ symlinkSync(primary, account.path, 'file');
796
+ tally.links = [...tally.links.filter((link) => link.path !== account.path), { path: account.path, target: primary }];
797
+ tally.changed = true;
798
+ } catch (error) {
799
+ tally.warnings.push(`Could not link ${account.path}: ${error?.message || error}`);
800
+ continue;
801
+ }
802
+ added.push(account.path);
803
+ }
804
+ return added;
805
+ }
806
+ function notInstalled(host, primary) {
807
+ return `${HOSTS[host].label} does not seem to be installed: ${primary.dir} does not exist, and SynaBun does not create it.`;
808
+ }
809
+ function assertHost(host) {
810
+ if (!RULESET_HOSTS.includes(host)) {
811
+ const error = new Error(`Unknown host: ${host}. Use ${RULESET_HOSTS.join(', ')}.`);
812
+ error.code = 'UNKNOWN_HOST';
813
+ throw error;
814
+ }
815
+ }
816
+
817
+ // ── Status ─────────────────────────────────────────────────────────────────
818
+ function hostStatus(host, state) {
819
+ const info = HOSTS[host];
820
+ if (info.carrier === 'none') {
821
+ return { carrier: 'none', path: null, state: 'manual', installedVersion: null, managed: null, error: null, detail: 'Cursor keeps User Rules inside the app: copy the text and paste it into Cursor Settings > Rules.' };
822
+ }
823
+ const row = state.hosts[host];
824
+ const list = targets(host);
825
+ const primary = list[0];
826
+ const out = {
827
+ carrier: info.carrier, path: primary.path, state: 'not-installed', installedVersion: null, managed: row.managed, error: null,
828
+ detail: null, detected: isDir(primary.dir), mcp: mcpRegistered(host), paths: [], appliedAt: row.appliedAt || null, backup: row.backup || null,
829
+ };
830
+ if (!out.detected) return out;
831
+ let expected;
832
+ try { expected = expectedBlock(host); } catch (error) {
833
+ return { ...out, state: 'error', error: `Could not render the ${host} rules: ${error?.message || error}` };
834
+ }
835
+ // A removal that could not finish: what stayed behind and why, until a remove or an install completes.
836
+ if (isPlainObject(row.removal)) out.partialRemoval = { at: row.removal.at || null, error: row.removal.error || null, paths: Array.isArray(row.removal.paths) ? row.removal.paths : [] };
837
+ const first = inspect(host, primary, expected);
838
+ let worst = first;
839
+ let replaceable = false;
840
+ let unlisted = null;
841
+ for (const target of list) {
842
+ const found = target === primary ? first : inspect(host, target, expected);
843
+ if (found.status !== 'missing') out.paths.push(target.path);
844
+ if (found.status === 'modified' && found.block) replaceable = true;
845
+ if (RANK[found.status] > RANK[worst.status]) worst = found;
846
+ }
847
+ if (first.block) out.installedVersion = first.block.version;
848
+ if (host === 'opencode') {
849
+ const config = readOpencodeConfig();
850
+ const sideFiles = ['opencode.json', 'opencode.jsonc'].filter((name) => existsSync(join(primary.dir, name)));
851
+ if (sideFiles.length) out.notes = [`OpenCode also reads ${sideFiles.join(' and ')} in ${primary.dir}; SynaBun writes config.json, the same file its MCP toggle uses.`];
852
+ if (config.error) return { ...out, state: 'error', error: config.error, detail: config.error };
853
+ if (first.status !== 'missing' && !config.hasEntry) {
854
+ unlisted = `${config.path} does not list it under "instructions", so OpenCode does not load it.`;
855
+ // An untouched file that is merely not listed is "not installed". An edited or damaged one keeps its own
856
+ // state below, so the status never hides what an install would be refused for.
857
+ if (first.status === 'current' || first.status === 'outdated' || first.status === 'newer') {
858
+ return { ...out, state: 'not-installed', installedVersion: null, detail: `${primary.path} exists, but ${unlisted}` };
859
+ }
860
+ }
861
+ // A line in config.json that SynaBun did not add points at the file: a remove keeps both unless it is forced.
862
+ if (first.status !== 'missing' && userEntries(row, config).length) out.entry = 'user';
863
+ }
864
+ if (first.status === 'missing' && RANK[worst.status] < RANK.error) return { ...out, installedVersion: null };
865
+ out.state = { current: 'installed', newer: 'newer', outdated: 'outdated', modified: 'modified', conflict: 'conflict', error: 'error' }[worst.status];
866
+ out.detail = worst.detail || null;
867
+ // An owned file without markers is the user's own: Replace (force) is refused for it.
868
+ if (out.state === 'modified' && !replaceable) out.replaceable = false;
869
+ if (worst.status === 'conflict' || worst.status === 'error') out.error = worst.detail || null;
870
+ if (unlisted) {
871
+ out.detail = [out.detail, `Also, ${unlisted}`].filter(Boolean).join(' ');
872
+ if (out.error) out.error = out.detail;
873
+ }
874
+ if (host === 'codex') {
875
+ const shadows = unique([primary.dir, ...codexAccounts().map((account) => account.dir)]).map((dir) => join(dir, 'AGENTS.override.md')).filter((path) => existsSync(path));
876
+ if (shadows.length) {
877
+ out.shadowedBy = shadows;
878
+ if (out.state === 'installed') {
879
+ out.state = 'shadowed';
880
+ out.detail = `${shadows.join(', ')} takes precedence: Codex reads it instead of AGENTS.md in that directory, so the rules are not loaded there.`;
881
+ }
882
+ }
883
+ }
884
+ return out;
885
+ }
886
+
887
+ function legacyCandidates() {
888
+ const list = [];
889
+ const seen = new Set();
890
+ const add = (path, host, scope) => {
891
+ if (!path || !existsSync(path)) return;
892
+ const key = realKey(path);
893
+ if (seen.has(key)) return;
894
+ seen.add(key);
895
+ list.push({ path, host, scope });
896
+ };
897
+ for (const target of targets('claude')) add(target.legacyFile, 'claude', 'global');
898
+ for (const target of targets('codex')) add(target.path, 'codex', 'global');
899
+ for (const target of targets('gemini')) add(target.path, 'gemini', 'global');
900
+ for (const target of targets('opencode')) add(target.legacyFile, 'opencode', 'global');
901
+ for (const dir of projectPaths()) {
902
+ for (const [name, host] of PROJECT_FILES) add(join(dir, name), host, 'project');
903
+ }
904
+ return list;
905
+ }
906
+ function scanLegacy() {
907
+ const hashes = legacy();
908
+ const found = [];
909
+ for (const candidate of legacyCandidates()) {
910
+ try {
911
+ if (statSync(candidate.path).size > MAX_SCAN_BYTES) continue;
912
+ const spans = findLegacy(readFileSync(candidate.path, 'utf8'), hashes);
913
+ if (!spans.length) continue;
914
+ found.push({ ...candidate, kind: spans.some((span) => span.kind === 'exact') ? 'exact' : 'heading', count: spans.length });
915
+ } catch { /* unreadable: nothing to report */ }
916
+ }
917
+ return found;
918
+ }
919
+ function offerHosts(state) {
920
+ return MANAGED_HOSTS.filter((host) => state.hosts[host].managed === null && !state.offerAcked.includes(host) && isDir(targets(host)[0].dir) && mcpRegistered(host));
921
+ }
922
+
923
+ function status() {
924
+ const { state } = readState();
925
+ const hosts = {};
926
+ for (const host of RULESET_HOSTS) hosts[host] = hostStatus(host, state);
927
+ let version = null;
928
+ try { version = render('claude').version; } catch { /* reported per host */ }
929
+ const offer = offerHosts(state);
930
+ const notice = state.notice || (offer.length ? { kind: 'offer-install', hosts: offer } : null);
931
+ return { ok: true, version, autoUpdate: state.autoUpdate, hosts, legacy: scanLegacy(), notice };
932
+ }
933
+
934
+ // ── Install / remove ───────────────────────────────────────────────────────
935
+ /** The answer of install and remove when the writer lock could not be taken: nothing ran, nothing changed. */
936
+ function lockRefusal(host, why) {
937
+ let state = 'error';
938
+ try { state = hostStatus(host, readState().state).state; } catch { /* the refusal is the news */ }
939
+ return { ok: false, ...why, host, state, path: targets(host)[0]?.path || null, changed: false, backupPath: null };
940
+ }
941
+
942
+ function install(host, { force = false } = {}) {
943
+ assertHost(host);
944
+ if (HOSTS[host].carrier === 'none') return { ok: true, host, state: 'manual', path: null, changed: false, backupPath: null };
945
+ return transact(`install ${host}`, (why) => lockRefusal(host, why), (state, call) => installHost(state, host, { force }, call))[0];
946
+ }
947
+ function installHost(state, host, { force }, call) {
948
+ const row = state.hosts[host];
949
+ const list = targets(host);
950
+ const primary = list[0];
951
+ const tally = call.tally(host);
952
+ const base = { host, path: primary.path };
953
+ const done = () => ({ changed: tally.changed, backupPath: tally.backupPath });
954
+ if (!isDir(primary.dir)) return { ok: false, ...base, ...done(), state: 'not-installed', error: notInstalled(host, primary) };
955
+
956
+ const expected = expectedBlock(host);
957
+ if (host === 'opencode') {
958
+ const config = readOpencodeConfig();
959
+ if (config.error) return { ok: false, ...base, ...done(), state: 'error', error: config.error };
960
+ }
961
+ // What was already written stays recorded when the install stops halfway (a directory it made, a copy it
962
+ // wrote), so a later remove still finds it. `managed` is not touched: nothing was agreed to yet.
963
+ const keepRecord = () => {
964
+ Object.assign(row, mergedRecord(row, tally));
965
+ if (tally.backupPath) row.backup = tally.backupPath;
966
+ };
967
+ const fail = (found) => {
968
+ keepRecord();
969
+ const result = { ok: false, ...base, state: found.status === 'error' ? 'error' : found.status, ...done(), error: found.detail || `Could not install the ${host} rules.` };
970
+ if (found.code) result.code = found.code;
971
+ return result;
972
+ };
973
+
974
+ try {
975
+ // A copy a newer SynaBun wrote counts as installed: it is left as it is (unless forced) and recorded as found.
976
+ const first = applyTarget(host, primary, expected, { force }, tally);
977
+ if (first.status !== 'current' && first.status !== 'newer') return fail(first);
978
+ tally.paths.push(primary.path);
979
+ for (const target of list.slice(1)) {
980
+ const found = applyTarget(host, target, expected, { force }, tally);
981
+ if (found.status === 'current' || found.status === 'newer') tally.paths.push(target.path);
982
+ else if (found.detail) tally.warnings.push(found.detail);
983
+ }
984
+ if (host === 'codex') tally.paths.push(...linkCodexAccounts(row, tally));
985
+ if (HOSTS[host].carrier === 'file') {
986
+ const seen = new Set();
987
+ for (const target of list) {
988
+ const key = realKey(target.legacyFile);
989
+ if (seen.has(key)) continue;
990
+ seen.add(key);
991
+ stripLegacy(host, target.legacyFile, tally);
992
+ }
993
+ }
994
+ if (host === 'opencode') addOpencodeEntry(row, tally);
995
+
996
+ Object.assign(row, {
997
+ managed: true,
998
+ installedVersion: first.status === 'newer' ? first.block.version : expected.version,
999
+ installedSha: first.status === 'newer' ? first.block.sha : expected.sha,
1000
+ path: primary.path,
1001
+ ...mergedRecord(row, tally),
1002
+ appliedAt: tally.changed || !row.appliedAt ? stamp() : row.appliedAt,
1003
+ backup: tally.backupPath || row.backup || null,
1004
+ });
1005
+ delete row.removal;
1006
+ state.offerAcked = state.offerAcked.filter((name) => name !== host);
1007
+ } catch (error) {
1008
+ log('rulesets', `install ${host} failed: ${error?.message || error}`);
1009
+ keepRecord();
1010
+ const result = { ok: false, ...base, state: error?.name === 'ManagedTextConflict' ? 'conflict' : 'error', ...done(), error: error?.message || String(error) };
1011
+ if (error?.code === 'CHANGED_UNDERNEATH') result.code = error.code;
1012
+ return result;
1013
+ }
1014
+ const result = { ok: true, ...base, state: hostStatus(host, state).state, ...done() };
1015
+ const legacyNotes = uniqueNotes(tally.legacy);
1016
+ const warnings = unique(tally.warnings);
1017
+ if (legacyNotes.length) result.legacy = legacyNotes;
1018
+ if (warnings.length) result.warnings = warnings;
1019
+ return result;
1020
+ }
1021
+
1022
+ /**
1023
+ * What an owned whole-file carrier is, before remove may delete it:
1024
+ * absent, foreign nothing of SynaBun's (foreign: skipped without a word)
1025
+ * pristine still exactly what SynaBun wrote: deleted
1026
+ * edited one well-formed copy the user changed: theirs, deleted only when asked with force
1027
+ * no-markers SynaBun wrote here once, the markers are gone: the user's own file, never deleted
1028
+ * symlink, conflict, error refused, forced or not (conflict: damaged markers, or a block stamped for another tool)
1029
+ */
1030
+ function ownedFile(host, path, recorded) {
1031
+ let info;
1032
+ try { info = lstatSync(path); } catch { return { kind: 'absent' }; }
1033
+ let text;
1034
+ try { text = readFileSync(path, 'utf8'); } catch (error) {
1035
+ if (error?.code === 'ENOENT') return { kind: 'absent' }; // a link to nothing: no rules there, and not ours to tidy
1036
+ return { kind: 'error', message: `Could not read ${path}: ${error?.message || error}` };
1037
+ }
1038
+ const { blocks, conflict } = findBlocks(text);
1039
+ // A rules/synabun.md that never held a SynaBun block is someone's own file.
1040
+ if (!blocks.length && !conflict && !recorded) return { kind: 'foreign' };
1041
+ if (info.isSymbolicLink()) return { kind: 'symlink', message: `${path} is a symbolic link, and SynaBun never deletes a link or the file behind it. Remove the link yourself if you no longer want these rules.` };
1042
+ if (conflict) return { kind: 'conflict', message: `${conflict.message} in ${path}, so SynaBun left the file in place. Delete it yourself if you no longer want these rules.` };
1043
+ const block = blocks[0];
1044
+ if (!block) return { kind: 'no-markers', message: `${path} has no SynaBun markers any more, so it is your own file: SynaBun left it in place and no longer manages it.` };
1045
+ if (block.host !== host) return { kind: 'conflict', foreign: true, message: `${foreignBlock(host, block, path)} Delete the file yourself if you no longer want these rules.` };
1046
+ const pristine = isPristine(block) && withoutBom(text.slice(0, block.start) + text.slice(block.eolEnd)).trim() === '';
1047
+ if (!pristine) return { kind: 'edited', text, message: `${path} was edited, so SynaBun left it in place and no longer manages it.` };
1048
+ return { kind: 'pristine', text };
1049
+ }
1050
+
1051
+ /**
1052
+ * One rule for every carrier: an untouched copy is removed, whatever version
1053
+ * it is stamped with; a copy the user edited is theirs and stays (SynaBun
1054
+ * stops managing it) unless `force` asks for it, and then it is backed up
1055
+ * first; damaged markers, a block stamped for another tool, a symlinked owned
1056
+ * file and a file that cannot be read are refused either way.
1057
+ *
1058
+ * OpenCode: a line in config.json that points at the owned file and that
1059
+ * SynaBun did not add keeps the file too (`kept`, reason `listed-in-config`):
1060
+ * nothing is ever left pointing at a file SynaBun deleted. With `force` that
1061
+ * line goes as well, after a backup of config.json, and then the file.
1062
+ *
1063
+ * Symlinks at a block carrier (AGENTS.md, GEMINI.md): the block is taken out
1064
+ * of the file the link points at and the link stays, exactly as install wrote
1065
+ * through it. The one link remove unlinks is a link install made itself that
1066
+ * still points where install pointed it. A file install created is deleted
1067
+ * only while it is nothing but the untouched block; with anything else in
1068
+ * it, the block goes and the file stays.
1069
+ */
1070
+ function remove(host, { force = false } = {}) {
1071
+ assertHost(host);
1072
+ if (HOSTS[host].carrier === 'none') return { ok: true, host, state: 'manual', path: null, changed: false, backupPath: null };
1073
+ return transact(`remove ${host}`, (why) => lockRefusal(host, why), (state, call) => removeHost(state, host, { force: force === true }, call))[0];
1074
+ }
1075
+ /**
1076
+ * True while a copy that keeps a removal open is still on disk: one SynaBun
1077
+ * should have taken and could not. That is damaged markers, a file that
1078
+ * cannot be read, an untouched copy of this host's rules (behind a symlink,
1079
+ * or held back by another refusal), and for OpenCode a config.json that
1080
+ * cannot be edited while SynaBun's entry is on record. A copy the user
1081
+ * edited, a file without markers and a block stamped for another tool are
1082
+ * not: the keep rule leaves the first two to the user, and the third was
1083
+ * never this host's. Looks at every place a copy can be: the host's carriers,
1084
+ * the row's paths and the paths the removal recorded.
1085
+ */
1086
+ function removalOpen(host, row, extra = []) {
1087
+ const recorded = Array.isArray(row.removal?.paths) ? row.removal.paths : [];
1088
+ const paths = unique([...targets(host).map((target) => target.path), ...row.paths, ...recorded, ...extra].filter((path) => typeof path === 'string'));
1089
+ if (host === 'opencode' && row.instructionsEntry && readOpencodeConfig().error) return true;
1090
+ return paths.some((path) => {
1091
+ try { lstatSync(path); } catch { return false; } // gone
1092
+ let text;
1093
+ try { text = readFileSync(path, 'utf8'); } catch (error) {
1094
+ return error?.code !== 'ENOENT'; // a link to nothing loads no rules; anything else is a file SynaBun cannot read
1095
+ }
1096
+ const { blocks, conflict } = findBlocks(text);
1097
+ if (conflict) return true;
1098
+ const block = blocks[0];
1099
+ if (!block || block.host !== host || !isPristine(block)) return false;
1100
+ return HOSTS[host].carrier !== 'file' || withoutBom(text.slice(0, block.start) + text.slice(block.eolEnd)).trim() === '';
1101
+ });
1102
+ }
1103
+ /** Ends a removal that has nothing left to wait for: the record goes and the host is opted out. True when it did. */
1104
+ function closeRemoval(host, row) {
1105
+ if (!isPlainObject(row.removal) || removalOpen(host, row)) return false;
1106
+ delete row.removal;
1107
+ Object.assign(row, { managed: false, optedOutAt: stamp() });
1108
+ return true;
1109
+ }
1110
+ /**
1111
+ * Take a host's rules out. Every copy is looked at first; then, when at least
1112
+ * one will go, the removal is put on record (`removal`, with the paths it is
1113
+ * about to take) and saved before the first copy is touched. A process that
1114
+ * dies halfway leaves that record behind, and the next boot finishes the job
1115
+ * (reconcile) instead of reading the missing primary copy as an opt-out
1116
+ * while another account still loads the rules.
1117
+ */
1118
+ function removeHost(state, host, { force }, call) {
1119
+ const row = state.hosts[host];
1120
+ const list = targets(host);
1121
+ const primary = list[0];
1122
+ const tally = call.tally(host);
1123
+ const refused = []; // {path, reason, message}: rules SynaBun could not take out
1124
+ const left = []; // {path, reason, message}: copies deliberately left to the user
1125
+ const refuse = (path, reason, message) => refused.push({ path, reason, message });
1126
+ const created = new Set(row.created);
1127
+ // Every place a copy can be: the host's carriers, the row's paths, and what an unfinished removal recorded.
1128
+ const recorded = Array.isArray(row.removal?.paths) ? row.removal.paths.filter((path) => typeof path === 'string') : [];
1129
+ const paths = unique([...list.map((target) => target.path), ...row.paths, ...recorded]);
1130
+ /** Intent first: the paths about to go are saved before the first of them is touched. */
1131
+ const recordIntent = (planned) => {
1132
+ row.removal = { at: stamp(), error: INTERRUPTED, paths: unique(planned) };
1133
+ call.checkpoint();
1134
+ };
1135
+
1136
+ try {
1137
+ if (HOSTS[host].carrier === 'file') {
1138
+ // Look at every copy first, then act.
1139
+ const doomed = [];
1140
+ const seen = new Set();
1141
+ for (const path of paths) {
1142
+ const key = realKey(path);
1143
+ if (seen.has(key)) continue;
1144
+ seen.add(key);
1145
+ const verdict = ownedFile(host, path, row.paths.includes(path));
1146
+ if (verdict.kind === 'absent' || verdict.kind === 'foreign') continue;
1147
+ if (verdict.kind === 'pristine' || (verdict.kind === 'edited' && force)) doomed.push({ path, text: verdict.text });
1148
+ else if (verdict.kind === 'edited' || verdict.kind === 'no-markers') left.push({ path, reason: verdict.kind, message: verdict.message });
1149
+ else refuse(path, verdict.kind, verdict.message);
1150
+ }
1151
+ // OpenCode: the entry and the file go together. A file that stays keeps its entry (it is still loaded), and
1152
+ // with an entry still listed the file stays, so OpenCode never points at a file that is gone: a config we
1153
+ // may not rewrite is a refusal, and a line the user wrote themselves keeps the file unless force takes both.
1154
+ const together = host === 'opencode';
1155
+ if (together && !refused.length && !left.length) {
1156
+ const config = readOpencodeConfig();
1157
+ if (config.error) {
1158
+ if (doomed.length || row.instructionsEntry) refuse(config.path, 'error', config.error);
1159
+ } else if (doomed.length && !force && userEntries(row, config).length) {
1160
+ for (const { path } of doomed) {
1161
+ left.push({
1162
+ path, reason: 'listed-in-config',
1163
+ message: `${config.path} lists ${path} under "instructions" on a line SynaBun did not add, so that line and the file were left in place and SynaBun no longer manages them. Remove with force to take out both.`,
1164
+ });
1165
+ }
1166
+ }
1167
+ }
1168
+ const go = !(together && (refused.length || left.length));
1169
+ if (go && doomed.length) recordIntent(doomed.map((entry) => entry.path));
1170
+ if (go && together) {
1171
+ try { removeOpencodeEntry(row, tally, { theirs: force && doomed.length > 0 }); } catch (error) { refuse(layout().opencodeConfig, 'error', error?.message || String(error)); }
1172
+ }
1173
+ if (!(together && refused.length)) {
1174
+ for (const { path, text } of go ? doomed : []) {
1175
+ try { tally.backup(backedUp(path, host, () => unlinkUnchanged(path, text))); } catch (error) {
1176
+ refuse(path, 'error', error?.message || String(error));
1177
+ continue;
1178
+ }
1179
+ tally.changed = true;
1180
+ const dir = dirname(path);
1181
+ if (created.has(dir)) { try { if (!readdirSync(dir).length) rmdirSync(dir); } catch { /* still in use */ } }
1182
+ }
1183
+ }
1184
+ } else {
1185
+ // Each file once, however many paths lead to it (an account's AGENTS.md may be a link to the shared one).
1186
+ const doomed = [];
1187
+ const seen = new Set();
1188
+ for (const path of paths) {
1189
+ if (!existsSync(path)) continue; // no file, or a link to nothing
1190
+ const key = realKey(path);
1191
+ if (seen.has(key)) continue;
1192
+ seen.add(key);
1193
+ let text;
1194
+ try { text = readFileSync(path, 'utf8'); } catch (error) { refuse(path, 'error', `Could not read ${path}: ${error?.message || error}`); continue; }
1195
+ const { blocks, conflict } = findBlocks(text);
1196
+ if (conflict) { refuse(path, 'conflict', `${conflict.message} in ${path}. SynaBun will not guess: remove the SynaBun marker lines by hand, then remove again.`); continue; }
1197
+ const block = blocks[0];
1198
+ if (!block) continue;
1199
+ if (block.host !== host) { refuse(path, 'conflict', `${foreignBlock(host, block, path)} Take that block out by hand if you no longer want it.`); continue; }
1200
+ const pristine = isPristine(block);
1201
+ if (!force && !pristine) {
1202
+ left.push({ path, reason: 'edited', message: `The SynaBun block in ${path} was edited, so SynaBun left it in place and no longer manages it.` });
1203
+ continue;
1204
+ }
1205
+ doomed.push({ path, text, pristine });
1206
+ }
1207
+ if (doomed.length) recordIntent(doomed.map((entry) => entry.path));
1208
+ for (const { path, text, pristine } of doomed) {
1209
+ const next = removeBlock(text);
1210
+ try {
1211
+ // "Install created it" is not enough to delete a file: it has to be a regular file that is still nothing
1212
+ // but the untouched block. A symlink at the path (the user moved the file and linked it back) is written
1213
+ // through like any other link, and a file that holds anything else keeps everything but the block.
1214
+ tally.backup(backedUp(path, host, () => {
1215
+ if (created.has(path) && !isSymlink(path) && pristine && withoutBom(next).trim() === '') unlinkUnchanged(path, text);
1216
+ else commit(path, next, text);
1217
+ }));
1218
+ } catch (error) {
1219
+ refuse(path, 'error', error?.message || String(error));
1220
+ continue;
1221
+ }
1222
+ tally.changed = true;
1223
+ }
1224
+ if (!refused.length && !left.some((entry) => entry.path === primary.path)) {
1225
+ // The links this installer made go with the rules, each only while it still points where install pointed
1226
+ // it. A link that points elsewhere by now, and every link SynaBun did not make, is the user's and stays.
1227
+ for (const link of row.links) {
1228
+ if (!isOwnLink(link)) continue;
1229
+ try { unlinkSync(link.path); tally.changed = true; } catch { /* leave it */ }
1230
+ }
1231
+ }
1232
+ }
1233
+ } catch (error) {
1234
+ refuse(primary.path, 'error', error?.message || String(error));
1235
+ }
1236
+
1237
+ const kept = [...refused, ...left].map(({ path, reason }) => ({ path, reason }));
1238
+ const error = unique(refused.map((entry) => entry.message)).join(' ');
1239
+ if (refused.length && removalOpen(host, row, refused.map((entry) => entry.path))) {
1240
+ // Rules SynaBun could not take out are still there, so nothing is called "opted out": `managed` stays as it
1241
+ // was. The row says what stayed behind until a remove or an install completes, the next boot finishes the
1242
+ // removal (a copy that was repaired meanwhile goes then), or none of it is left to take (closeRemoval).
1243
+ row.removal = { at: stamp(), error, paths: unique(refused.map((entry) => entry.path)) };
1244
+ row.backup = tally.backupPath || row.backup || null;
1245
+ return {
1246
+ ok: false, code: 'REMOVE_INCOMPLETE', host, state: hostStatus(host, state).state, path: primary.path, changed: tally.changed, backupPath: tally.backupPath,
1247
+ partial: true, managed: row.managed, kept, error,
1248
+ };
1249
+ }
1250
+
1251
+ if (left.length || refused.length) {
1252
+ // A deliberate keep, not a failure: the copies that stayed are the user's now. What install made around them
1253
+ // (`created`, OpenCode's entry) stays on record, so a forced remove or a Replace can still clean up. The same
1254
+ // row for a refusal that leaves nothing to wait for (a block stamped for another tool, a copy that turned out
1255
+ // edited): the host is opted out, and no removal is kept open over a file that was never SynaBun's to take.
1256
+ // `paths` are places a copy of the rules is: config.json, which a refusal can name, is not one of them.
1257
+ const config = host === 'opencode' ? layout().opencodeConfig : null;
1258
+ Object.assign(row, {
1259
+ managed: false, installedVersion: null, installedSha: null, path: primary.path,
1260
+ paths: unique([...left, ...refused].map((entry) => entry.path).filter((path) => path !== config)),
1261
+ appliedAt: stamp(), backup: tally.backupPath || row.backup || null,
1262
+ });
1263
+ } else {
1264
+ Object.assign(row, { ...emptyHostState(), managed: false, path: primary.path, appliedAt: stamp(), backup: tally.backupPath || row.backup || null });
1265
+ delete row.instructionsEntry;
1266
+ }
1267
+ delete row.removal;
1268
+ const result = { ok: true, host, state: hostStatus(host, state).state, path: primary.path, changed: tally.changed, backupPath: tally.backupPath };
1269
+ if (refused.length) return { ...result, ok: false, code: 'REMOVE_INCOMPLETE', partial: false, managed: row.managed, kept, error };
1270
+ if (left.length) Object.assign(result, { kept, message: unique(left.map((entry) => entry.message)).join(' ') });
1271
+ return result;
1272
+ }
1273
+
1274
+ /**
1275
+ * Every host the user has not opted out of whose CLI is connected to SynaBun
1276
+ * (or already managed). A host with a removal on record is not one of them:
1277
+ * the user asked for those rules to go, and putting the primary copy back
1278
+ * because another copy could not be taken would undo that. Naming the host
1279
+ * in `hosts` is the explicit way back.
1280
+ */
1281
+ function installAll({ hosts = null, force = false } = {}) {
1282
+ return transact('install-all', (why) => ({ ok: false, ...why, changed: false, results: {} }), (state, call) => {
1283
+ const explicit = Array.isArray(hosts) && hosts.length > 0;
1284
+ for (const host of MANAGED_HOSTS) {
1285
+ try { closeRemoval(host, state.hosts[host]); } catch (error) { log('rulesets', `install-all ${host}: ${error?.message || error}`); }
1286
+ }
1287
+ const wanted = (explicit ? unique(hosts) : MANAGED_HOSTS).filter((host) => MANAGED_HOSTS.includes(host)).filter((host) => {
1288
+ if (explicit) return true;
1289
+ const row = state.hosts[host];
1290
+ return row.managed !== false && !isPlainObject(row.removal) && isDir(targets(host)[0].dir) && (row.managed === true || mcpRegistered(host));
1291
+ });
1292
+ const results = {};
1293
+ for (const host of wanted) {
1294
+ // One host that cannot even be looked at must not cost the others their record: they share one save.
1295
+ try { results[host] = installHost(state, host, { force }, call); } catch (error) {
1296
+ log('rulesets', `install ${host} failed: ${error?.message || error}`);
1297
+ results[host] = { ok: false, host, path: null, state: 'error', changed: false, backupPath: null, error: error?.message || String(error) };
1298
+ }
1299
+ }
1300
+ return { ok: Object.values(results).every((result) => result.ok), changed: Object.values(results).some((result) => result.changed), results };
1301
+ })[0];
1302
+ }
1303
+
1304
+ // ── Reconcile (server boot) ────────────────────────────────────────────────
1305
+ // The server calls this from its listen callback. Every check is one read; the only wait is for the writer lock,
1306
+ // and that is bounded by `lockTimeoutMs`: when another SynaBun process holds it that long, this boot skips the
1307
+ // reconcile (one log line, `ok: false`, code BUSY) and the next one does it.
1308
+ function reconcile() {
1309
+ const [result, wrote] = transact('reconcile', (why) => ({ ok: false, ...why, updated: [], optedOut: [], states: {}, notice: null }), reconcileHosts);
1310
+ return { ...result, wrote };
1311
+ }
1312
+ /** Same refusal, same paths: a removal the boot tried again and could not finish either. */
1313
+ function sameRemoval(a, b) {
1314
+ return isPlainObject(a) && isPlainObject(b) && a.error === b.error && isDeepStrictEqual(a.paths, b.paths);
1315
+ }
1316
+ function reconcileHosts(state, call) {
1317
+ const updated = [];
1318
+ const optedOut = [];
1319
+ const states = {};
1320
+ let from = null;
1321
+ let to = null;
1322
+
1323
+ for (const host of MANAGED_HOSTS) {
1324
+ const row = state.hosts[host];
1325
+ try {
1326
+ if (isPlainObject(row.removal)) {
1327
+ // A removal that is not over: one a process died in the middle of, or one that was refused a copy. The
1328
+ // user asked for these rules to go, so nothing is updated or put back for the host. A removal with nothing
1329
+ // left to wait for is closed (every copy it could not take is gone, or is the user's own by now); any
1330
+ // other is finished here by the rules of a plain remove: untouched copies go, edited ones stay with the
1331
+ // user, damaged and foreign ones are refused again. Never with force.
1332
+ const wasManaged = row.managed === true;
1333
+ const pending = row.removal;
1334
+ if (!closeRemoval(host, row)) {
1335
+ removeHost(state, host, { force: false }, call);
1336
+ // Refused again for the same reason: the record stays as it was, and a quiet boot writes nothing.
1337
+ if (sameRemoval(pending, row.removal)) row.removal = pending;
1338
+ }
1339
+ if (wasManaged) {
1340
+ if (row.managed !== true) optedOut.push(host);
1341
+ states[host] = hostStatus(host, state).state;
1342
+ }
1343
+ continue;
1344
+ }
1345
+ if (row.managed !== true) continue;
1346
+ const list = targets(host);
1347
+ const primary = list[0];
1348
+ const expected = expectedBlock(host);
1349
+ const config = host === 'opencode' ? readOpencodeConfig() : null;
1350
+ if (config?.error) { states[host] = 'error'; continue; }
1351
+ const first = isDir(primary.dir) ? inspect(host, primary, expected) : { status: 'missing' };
1352
+ if (first.status === 'missing' || (config && !config.hasEntry)) {
1353
+ // The user deleted it: an opt-out, never a reason to put it back. (A copy SynaBun's own removal took is
1354
+ // not this case: that removal is on record and was dealt with above.)
1355
+ Object.assign(row, { managed: false, optedOutAt: stamp() });
1356
+ optedOut.push(host);
1357
+ states[host] = 'not-installed';
1358
+ continue;
1359
+ }
1360
+ const tally = call.tally(host);
1361
+ const known = new Set(row.paths.map((path) => realKey(path)));
1362
+ let primaryCurrent = first.status === 'current';
1363
+ for (const target of list) {
1364
+ const found = target === primary ? first : inspect(host, target, expected);
1365
+ // An account directory that appeared since the install gets the rules too; a copy the user deleted does not.
1366
+ // A copy a newer SynaBun wrote (`newer`) is neither old nor missing: it is left byte for byte, with no notice.
1367
+ const isNew = target !== primary && found.status === 'missing' && !known.has(realKey(target.path));
1368
+ if (found.status === 'outdated' ? !state.autoUpdate : !(isNew && primaryCurrent)) continue;
1369
+ const applied = applyTarget(host, target, expected, {}, tally);
1370
+ if (applied.status !== 'current') continue;
1371
+ tally.paths.push(target.path);
1372
+ if (target === primary) primaryCurrent = true;
1373
+ if (found.status === 'outdated' && tally.refreshedFrom === undefined) tally.refreshedFrom = found.block?.version || row.installedVersion || null;
1374
+ }
1375
+ if (host === 'codex' && primaryCurrent) tally.paths.push(...linkCodexAccounts(row, tally, { onlyNew: true }));
1376
+ if (tally.changed) {
1377
+ if (tally.refreshedFrom !== undefined) {
1378
+ updated.push(host);
1379
+ from = from || tally.refreshedFrom;
1380
+ to = expected.version;
1381
+ if (primaryCurrent) Object.assign(row, { installedVersion: expected.version, installedSha: expected.sha });
1382
+ }
1383
+ Object.assign(row, { ...mergedRecord(row, tally), appliedAt: stamp(), backup: tally.backupPath || row.backup || null });
1384
+ }
1385
+ states[host] = hostStatus(host, state).state;
1386
+ } catch (error) {
1387
+ states[host] = 'error';
1388
+ log('rulesets', `reconcile ${host} failed: ${error?.message || error}`);
1389
+ }
1390
+ }
1391
+
1392
+ if (updated.length) {
1393
+ const pending = state.notice?.kind === 'updated' ? state.notice : null;
1394
+ state.notice = { kind: 'updated', from: pending?.from || from, to, hosts: unique([...(pending?.hosts || []), ...updated]), at: stamp() };
1395
+ }
1396
+ return { ok: true, updated, optedOut, states, notice: state.notice };
1397
+ }
1398
+
1399
+ // ── Pasted copies in project files ─────────────────────────────────────────
1400
+ // The user asked for this file by name, from the list status() shows: every
1401
+ // hash-exact copy goes, whichever tool it was written for. Quoted (fenced)
1402
+ // copies and edited ones are not matches and stay. The state file is not
1403
+ // involved, but the writer lock is taken like for every other change; the
1404
+ // file is replaced only while it still holds what was read.
1405
+ function removeLegacy(path) {
1406
+ if (typeof path !== 'string' || !path) return { ok: false, code: 'PATH_REQUIRED', error: 'path is required', changed: false };
1407
+ return locked('remove-legacy', (why) => ({ ok: false, ...why, changed: false }), () => {
1408
+ const key = realKey(resolve(path));
1409
+ const candidate = legacyCandidates().find((entry) => realKey(entry.path) === key);
1410
+ if (!candidate) return { ok: false, code: 'NOT_A_CANDIDATE', error: 'SynaBun only edits the instruction files it lists as holding a pasted copy.', changed: false };
1411
+ const changed = (message) => ({ ok: false, code: 'CHANGED_UNDERNEATH', error: message, path: candidate.path, host: candidate.host, changed: false });
1412
+ const bytes = readBytes(candidate.path);
1413
+ if (bytes === null) return changed(stillChanging(candidate.path));
1414
+ const text = bytes.toString('utf8');
1415
+ const spans = findLegacy(text, legacy());
1416
+ const exact = spans.filter((span) => span.kind === 'exact');
1417
+ const remaining = spans.some((span) => span.kind === 'heading') ? 'heading' : null;
1418
+ if (!exact.length) return { ok: true, path: candidate.path, host: candidate.host, changed: false, removed: 0, backupPath: null, remaining };
1419
+ let next = text;
1420
+ for (const span of [...exact].reverse()) next = removeRange(next, span.start, span.end);
1421
+ let backupPath;
1422
+ try { backupPath = backedUp(candidate.path, candidate.host, () => commit(candidate.path, next, bytes)); } catch (error) {
1423
+ if (error?.code !== 'CHANGED_UNDERNEATH') throw error;
1424
+ return changed(error.message);
1425
+ }
1426
+ return { ok: true, path: candidate.path, host: candidate.host, changed: true, removed: exact.length, backupPath, remaining };
1427
+ });
1428
+ }
1429
+
1430
+ // ── Settings ───────────────────────────────────────────────────────────────
1431
+ // Nothing beyond the lock: of two processes that set it, the one that ran last wins.
1432
+ const settingRefused = (why) => ({ ok: false, ...why });
1433
+ function setAutoUpdate(value) {
1434
+ return transact('settings', settingRefused, (state) => {
1435
+ state.autoUpdate = value === true;
1436
+ return { ok: true, autoUpdate: state.autoUpdate };
1437
+ })[0];
1438
+ }
1439
+ /** Clears the "rules were updated" notice and stops offering the hosts that were offered. */
1440
+ function ackNotice() {
1441
+ return transact('notice-ack', settingRefused, (state) => {
1442
+ state.notice = null;
1443
+ state.offerAcked = unique([...state.offerAcked, ...offerHosts(state)]);
1444
+ return { ok: true, notice: null };
1445
+ })[0];
1446
+ }
1447
+ /** Close this installer's connection to the lock file (tests; a server keeps it for its whole life). */
1448
+ function close() {
1449
+ lock.close();
1450
+ }
1451
+
1452
+ return { statePath, backupRoot, lockPath, status, install, remove, installAll, reconcile, removeLegacy, setAutoUpdate, ackNotice, close };
1453
+ }