synabun 2.0.0-beta → 2.0.5

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 (374) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/THIRD-PARTY-LICENSES.md +51 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +2 -2
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +20 -0
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/launcher.mjs +40 -0
  14. package/lib/claude-hooks.js +61 -8
  15. package/lib/client-config-repair.js +4 -3
  16. package/lib/data-home-migration.js +4 -0
  17. package/lib/desktop-pwa.js +206 -0
  18. package/lib/external-tools.js +221 -0
  19. package/lib/first-launch-protection.js +5 -0
  20. package/lib/packaged-runtime.js +110 -0
  21. package/lib/start-launcher.js +1396 -0
  22. package/mcp-server/package-lock.json +2 -2
  23. package/mcp-server/package.json +1 -3
  24. package/neural-interface/i18n/en.json +1540 -154
  25. package/neural-interface/i18n/pt-BR.json +1538 -153
  26. package/neural-interface/lib/assistant-api.js +62 -6
  27. package/neural-interface/lib/assistant-brains/claude.js +4 -0
  28. package/neural-interface/lib/assistant-catalog.js +179 -27
  29. package/neural-interface/lib/assistant-config.js +31 -2
  30. package/neural-interface/lib/assistant-dispatch.js +62 -13
  31. package/neural-interface/lib/assistant-envelope.js +9 -6
  32. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  33. package/neural-interface/lib/assistant-memory.js +24 -10
  34. package/neural-interface/lib/assistant-persona.js +6 -2
  35. package/neural-interface/lib/assistant-pricing.js +21 -6
  36. package/neural-interface/lib/assistant-runtime.js +181 -12
  37. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  38. package/neural-interface/lib/assistant-test-density.js +142 -0
  39. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  40. package/neural-interface/lib/assistant-user-context.js +908 -0
  41. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  42. package/neural-interface/lib/assistant-user-learn.js +404 -0
  43. package/neural-interface/lib/assistant-user-threads.js +226 -0
  44. package/neural-interface/lib/assistant-user-writer.js +181 -0
  45. package/neural-interface/lib/augmented-path.js +23 -3
  46. package/neural-interface/lib/browser-background-tab.js +134 -0
  47. package/neural-interface/lib/browser-language.js +83 -0
  48. package/neural-interface/lib/browser-screenshot.js +678 -61
  49. package/neural-interface/lib/claude-agent-bridge.js +87 -153
  50. package/neural-interface/lib/claude-executable.js +241 -0
  51. package/neural-interface/lib/claude-model-catalog.js +82 -93
  52. package/neural-interface/lib/claude-temporary.js +46 -7
  53. package/neural-interface/lib/cloudflared/install.js +533 -0
  54. package/neural-interface/lib/cloudflared/manager.js +545 -0
  55. package/neural-interface/lib/cloudflared/pins.js +115 -0
  56. package/neural-interface/lib/cloudflared/pins.json +63 -0
  57. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  58. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  59. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  60. package/neural-interface/lib/editor-completion.js +714 -0
  61. package/neural-interface/lib/editor-config.js +149 -0
  62. package/neural-interface/lib/editor-files-api.js +268 -0
  63. package/neural-interface/lib/editor-git-api.js +134 -0
  64. package/neural-interface/lib/file-explorer-api.js +1039 -0
  65. package/neural-interface/lib/file-explorer-system.js +355 -0
  66. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  67. package/neural-interface/lib/file-explorer-watch.js +239 -0
  68. package/neural-interface/lib/git-diff-api.js +103 -0
  69. package/neural-interface/lib/git-read.js +202 -0
  70. package/neural-interface/lib/greeting/api.js +89 -0
  71. package/neural-interface/lib/greeting/budgets.js +90 -0
  72. package/neural-interface/lib/greeting/engine.js +461 -0
  73. package/neural-interface/lib/greeting/facts.js +320 -0
  74. package/neural-interface/lib/greeting/sources.js +252 -0
  75. package/neural-interface/lib/greeting/writer.js +139 -0
  76. package/neural-interface/lib/http-guards.js +189 -13
  77. package/neural-interface/lib/machine-names.js +88 -0
  78. package/neural-interface/lib/memory-api.js +31 -7
  79. package/neural-interface/lib/native-binary-runtime.js +69 -337
  80. package/neural-interface/lib/native-loop-providers.js +40 -49
  81. package/neural-interface/lib/offline-page.js +54 -0
  82. package/neural-interface/lib/plugin-installer.js +4 -2
  83. package/neural-interface/lib/project-storage.js +5 -7
  84. package/neural-interface/lib/request-gate.js +589 -0
  85. package/neural-interface/lib/session-title-generator.js +5 -0
  86. package/neural-interface/lib/synabun-update-plan.js +19 -0
  87. package/neural-interface/lib/ui-blob-api.js +108 -0
  88. package/neural-interface/lib/ui-blob-store.js +68 -0
  89. package/neural-interface/package-lock.json +701 -253
  90. package/neural-interface/package.json +13 -2
  91. package/neural-interface/public/install-app.html +34 -0
  92. package/neural-interface/public/manifest.json +2 -0
  93. package/neural-interface/public/offline.html +402 -66
  94. package/neural-interface/public/onboarding.html +264 -147
  95. package/neural-interface/public/shared/agent-runtime-options.js +22 -6
  96. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  97. package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
  98. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  99. package/neural-interface/public/shared/assistant/asst-mascot.js +26 -64
  100. package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
  101. package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
  102. package/neural-interface/public/shared/assistant/asst-rack-styles.js +0 -3
  103. package/neural-interface/public/shared/assistant/asst-render.js +219 -67
  104. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  105. package/neural-interface/public/shared/assistant/asst-route.js +23 -329
  106. package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
  107. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  108. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  109. package/neural-interface/public/shared/assistant/asst-state.js +185 -1
  110. package/neural-interface/public/shared/assistant/asst-styles.js +162 -6
  111. package/neural-interface/public/shared/blob-store.js +111 -22
  112. package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
  113. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  114. package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
  115. package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
  116. package/neural-interface/public/shared/command-runner-model.js +30 -0
  117. package/neural-interface/public/shared/constants.js +6 -1
  118. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  119. package/neural-interface/public/shared/cp/cp-styles.js +15 -0
  120. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  121. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  122. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  123. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  124. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  125. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  126. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  127. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  128. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  129. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  130. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  131. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  132. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  133. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  134. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  135. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  136. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  137. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  138. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  139. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  140. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  141. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  142. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  143. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  144. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  145. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  146. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  147. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  148. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  149. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  150. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  151. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  152. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  153. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  154. package/neural-interface/public/shared/file-links.js +339 -0
  155. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  156. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  157. package/neural-interface/public/shared/html-shell.js +169 -170
  158. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  159. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  160. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
  161. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  162. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
  163. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
  164. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  165. package/neural-interface/public/shared/pwa-install-page.js +62 -0
  166. package/neural-interface/public/shared/pwa-install.css +12 -0
  167. package/neural-interface/public/shared/pwa-install.js +127 -0
  168. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  169. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  170. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  171. package/neural-interface/public/shared/settings/settings-ia.js +18 -4
  172. package/neural-interface/public/shared/settings/settings-rules.js +160 -0
  173. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  174. package/neural-interface/public/shared/start-bridge.js +802 -0
  175. package/neural-interface/public/shared/styleguide/sg-colors.js +5 -2
  176. package/neural-interface/public/shared/styleguide/sg-import-export.js +4 -3
  177. package/neural-interface/public/shared/styleguide/sg-kit.js +4 -1
  178. package/neural-interface/public/shared/styleguide/sg-panel.js +9 -1
  179. package/neural-interface/public/shared/styleguide/sg-typography.js +1 -0
  180. package/neural-interface/public/shared/styles.css +2219 -1117
  181. package/neural-interface/public/shared/synabun-mascot.js +20 -56
  182. package/neural-interface/public/shared/term-ready.js +307 -0
  183. package/neural-interface/public/shared/tunnel-view.js +167 -0
  184. package/neural-interface/public/shared/ui-claude-panel.js +160 -19
  185. package/neural-interface/public/shared/ui-command-runner.js +251 -468
  186. package/neural-interface/public/shared/ui-detail.js +15 -0
  187. package/neural-interface/public/shared/ui-explorer.js +22 -0
  188. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  189. package/neural-interface/public/shared/ui-file-links.js +103 -0
  190. package/neural-interface/public/shared/ui-help.js +26 -0
  191. package/neural-interface/public/shared/ui-invite.js +126 -116
  192. package/neural-interface/public/shared/ui-judgments.js +1 -1
  193. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  194. package/neural-interface/public/shared/ui-loading.js +367 -12
  195. package/neural-interface/public/shared/ui-menubar.js +21 -55
  196. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  197. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  198. package/neural-interface/public/shared/ui-navbar.js +45 -21
  199. package/neural-interface/public/shared/ui-settings.js +491 -547
  200. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  201. package/neural-interface/public/shared/ui-sync.js +16 -0
  202. package/neural-interface/public/shared/ui-terminal.js +153 -149
  203. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  204. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  205. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  206. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  207. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  208. package/neural-interface/public/shared/worker-end.js +251 -0
  209. package/neural-interface/public/sw.js +3 -1
  210. package/neural-interface/public/variant/2d/main.js +2 -1
  211. package/neural-interface/public/variant/3d/main.js +2 -1
  212. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  213. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  214. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  215. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  216. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  217. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  218. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  219. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  220. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  221. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  222. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  223. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  224. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  225. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  226. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  227. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  228. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  229. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  230. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  231. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  232. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  233. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  234. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  235. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  236. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  237. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  238. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  239. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  240. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  241. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  242. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  243. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  244. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  245. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  246. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  247. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  248. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  249. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  250. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  251. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  252. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  253. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  254. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  255. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  256. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  257. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  258. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  259. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  260. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  261. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  262. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  263. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  264. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  265. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  266. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  267. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  268. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  269. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  270. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  271. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  272. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  273. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  274. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  275. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  276. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  277. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  278. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  279. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  280. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  281. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  282. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  283. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  284. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  285. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  286. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  287. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  288. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  289. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  290. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  291. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  292. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  293. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  294. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  295. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  296. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  297. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  298. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  299. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  300. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  301. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  302. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  303. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  304. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  305. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  306. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  307. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  308. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  309. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  310. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  311. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  312. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  313. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  314. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  315. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  316. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  317. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  318. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  319. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  320. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  321. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  322. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  323. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  324. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  325. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  326. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  327. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  328. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  329. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  330. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  331. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  332. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  333. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  334. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  335. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  336. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  337. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  338. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  339. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  340. package/neural-interface/scripts/build-vendor.mjs +78 -2
  341. package/neural-interface/scripts/rebuild-pty.js +15 -12
  342. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  343. package/neural-interface/server.js +1092 -1212
  344. package/neural-interface/templates/rulesets/README.md +1 -1
  345. package/package.json +13 -7
  346. package/postinstall.js +11 -81
  347. package/setup.js +111 -6
  348. package/benchmarks/browser-live-results.json +0 -79
  349. package/benchmarks/browser-v2-results.json +0 -258
  350. package/docs/browser-v2.md +0 -109
  351. package/docs/hook-integrity.md +0 -89
  352. package/docs/judgments.md +0 -182
  353. package/docs/style-guide.md +0 -246
  354. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  355. package/neural-interface/public/category-logos/claude.svg +0 -1
  356. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  357. package/neural-interface/public/category-logos/openai.svg +0 -1
  358. package/neural-interface/public/category-logos/synabun.png +0 -0
  359. package/neural-interface/public/claude.svg +0 -1
  360. package/neural-interface/public/gallery.svg +0 -6
  361. package/neural-interface/public/leonardoai.svg +0 -1
  362. package/neural-interface/public/mcp.svg +0 -4
  363. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  364. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  365. package/neural-interface/public/sessionicon.svg +0 -8
  366. package/neural-interface/public/toolbar-album.svg +0 -8
  367. package/neural-interface/public/toolbar-question.svg +0 -6
  368. package/neural-interface/public/toolbar-sessions.svg +0 -6
  369. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  370. package/scripts/audit-claude-hook-usage.mjs +0 -12
  371. package/scripts/audit-memory-duplicates.mjs +0 -44
  372. package/scripts/measure-claude-hook-overhead.mjs +0 -77
  373. package/scripts/recover-user-config.mjs +0 -254
  374. package/skills/synabun/synabunicon.png +0 -0
@@ -0,0 +1,239 @@
1
+ // ── The File Explorer's watcher ──
2
+ // Tells the page which of the folders it shows changed on disk, and when the
3
+ // git state of the project may have, without the page asking again and again.
4
+ // It costs nothing while nothing changes: the operating system reports changes,
5
+ // nothing is polled.
6
+ //
7
+ // Two ways to watch, by what the system offers:
8
+ // recursive one watcher on the project's folder (macOS and Windows have it
9
+ // natively). Every change below the folder arrives; the ones the
10
+ // page did not ask about are dropped here.
11
+ // folders one watcher per folder the page shows (Linux, where a recursive
12
+ // watch is a watch per sub-folder), plus one on .git.
13
+ // Changes are collected for a moment and sent together. Dependencies are
14
+ // injected: the tests drive it with real folders and with a fake `watch`.
15
+ //
16
+ // A watcher that exists is not a watch that works: the system may have no
17
+ // watches left (ENOSPC on Linux), or lose one it had. `state()` says what is
18
+ // really watched, and `onState` when that changes, so the page can read again
19
+ // by itself where nothing will tell it.
20
+ //
21
+ // A watch that failed is not asked for again at once. A watch that is lost
22
+ // each time it is opened would otherwise be opened, lost and opened again as
23
+ // fast as the page and the system can answer one another. It is tried again
24
+ // after a wait that doubles (a second, two, four, ... a minute at most) and
25
+ // starts over once a watch has held for a minute; until then it stays a watch
26
+ // that is not there, whatever folders the page shows meanwhile.
27
+
28
+ import { existsSync, watch as fsWatch } from 'node:fs';
29
+ import { dirname, join, relative, sep } from 'node:path';
30
+
31
+ export const MAX_WATCHED_DIRS = 600;
32
+ /** A watch that failed is tried again after this long, then after twice that, and so on. */
33
+ export const RETRY_FIRST_MS = 1000;
34
+ /** ... and never less often than this. */
35
+ export const RETRY_MAX_MS = 60000;
36
+ /** A watch that held this long was no part of a run of failures: the next wait is the first one again. */
37
+ export const RETRY_QUIET_MS = 60000;
38
+ /** The files of .git whose change means "commit, stage, checkout or merge": the git state is read again. */
39
+ const GIT_STATE_FILES = /^(index|HEAD|ORIG_HEAD|MERGE_HEAD|CHERRY_PICK_HEAD|REVERT_HEAD|packed-refs)$/;
40
+ /** A folder that is not there (any more) is no watch that failed: its next listing says what is true. */
41
+ const NOT_THERE = new Set(['ENOENT', 'ENOTDIR']);
42
+
43
+ /** Which way this system is watched. */
44
+ export function watchStrategy(platform = process.platform) {
45
+ return platform === 'darwin' || platform === 'win32' ? 'recursive' : 'folders';
46
+ }
47
+
48
+ /**
49
+ * @param {object} o
50
+ * @param {string} o.root the project's folder, canonical
51
+ * @param {'recursive' | 'folders'} [o.strategy]
52
+ * @param {(change: { dirs: string[], git: boolean }) => void} o.onChange `dirs`: watched folders (canonical) whose
53
+ * entries changed; `git`: the git state may have changed
54
+ * @param {(error: Error) => void} [o.onError] the watch on the project's folder was lost
55
+ * @param {(state: { scope: 'tree' | 'folders', live: 'full' | 'partial' | 'none' }) => void} [o.onState] what is
56
+ * really watched changed (see `state()`); not called for the state it starts in
57
+ * @param {(relativePath: string) => boolean} [o.ignoresGit] a path git ignores: a change there is no change of state
58
+ * @param {typeof fsWatch} [o.watch] @param {(path: string) => boolean} [o.exists]
59
+ * @param {number} [o.debounceMs] @param {number} [o.maxWaitMs]
60
+ * @param {() => number} [o.now] @param {(fn: () => void, ms: number) => any} [o.setTimer] @param {(timer: any) => void} [o.clearTimer]
61
+ * the clock of the waits between two attempts at a watch that failed (a test moves it by hand)
62
+ * @returns {{ setDirs: (dirs: string[]) => void, close: () => void, count: () => number, strategy: string,
63
+ * state: () => { scope: 'tree' | 'folders', live: 'full' | 'partial' | 'none' } }}
64
+ */
65
+ export function createTreeWatcher({
66
+ root, strategy = watchStrategy(), onChange, onError = () => {}, onState = () => {}, ignoresGit = () => false,
67
+ watch = fsWatch, exists = existsSync, debounceMs = 120, maxWaitMs = 500,
68
+ now = () => Date.now(), setTimer = (fn, ms) => setTimeout(fn, ms), clearTimer = (timer) => clearTimeout(timer),
69
+ }) {
70
+ let closed = false;
71
+ let wanted = new Set([root]); // the folders the page shows
72
+ let capped = false; // the page shows more folders than are watched
73
+ const watchers = new Map(); // what is watched → FSWatcher
74
+ const failed = new Set(); // what should be watched and is not (no watches left, a watch that was lost)
75
+ const openedAt = new Map(); // what is watched → when that watch was opened
76
+ let retryMs = 0; // the wait that is running, or ran last
77
+ let retryAt = 0; // nothing that failed is asked for again before this
78
+ let retryTimer = null;
79
+ let refused = false; // the system refused a watch in the pass that is under way
80
+ let said = null; // the `live` that was last reported
81
+ const pending = new Set();
82
+ let pendingGit = false;
83
+ let timer = null;
84
+ let firstAt = 0;
85
+
86
+ const slashed = (path) => (sep === '/' ? path : path.split(sep).join('/'));
87
+
88
+ function flush() {
89
+ timer = null;
90
+ if (closed || (!pending.size && !pendingGit)) return;
91
+ const change = { dirs: [...pending], git: pendingGit };
92
+ pending.clear();
93
+ pendingGit = false;
94
+ try { onChange(change); } catch { /* a listener's failure is not the watcher's */ }
95
+ }
96
+
97
+ // Sent a moment after the last change, and never later than `maxWaitMs` after the first of a burst.
98
+ function schedule() {
99
+ const now = Date.now();
100
+ if (!timer) firstAt = now;
101
+ else clearTimeout(timer);
102
+ const wait = Math.max(0, Math.min(debounceMs, firstAt + maxWaitMs - now));
103
+ timer = setTimeout(flush, wait);
104
+ timer.unref?.();
105
+ }
106
+
107
+ // One change, by its path below the root ('' is the root itself).
108
+ function note(rel) {
109
+ if (closed) return;
110
+ const path = slashed(rel || '');
111
+ if (path === '.git' || path.startsWith('.git/')) {
112
+ const inside = path.slice(5);
113
+ if (GIT_STATE_FILES.test(inside) || inside.startsWith('refs/')) pendingGit = true;
114
+ } else if (!path || !ignoresGit(path)) {
115
+ pendingGit = true;
116
+ }
117
+ const parent = path.includes('/') ? join(root, dirname(path)) : root;
118
+ if (wanted.has(parent)) pending.add(parent);
119
+ // A watched folder that was itself removed or renamed: whoever shows it reads it again and finds out.
120
+ const self = path ? join(root, path) : root;
121
+ if (self !== parent && wanted.has(self)) pending.add(self);
122
+ if (pending.size || pendingGit) schedule();
123
+ }
124
+
125
+ /**
126
+ * What is really watched. `scope`: every change below the project (`tree`), or only changes in the folders the
127
+ * page shows and in git's own files (`folders`: a change inside a closed folder is not seen). `live`: `full`
128
+ * when all of that is watched, `partial` when some of it could not be, `none` when the project's folder is not.
129
+ */
130
+ const state = () => ({
131
+ scope: strategy === 'recursive' ? 'tree' : 'folders',
132
+ live: failed.has(root) || !watchers.has(root) ? 'none' : failed.size || capped ? 'partial' : 'full',
133
+ });
134
+ function report() {
135
+ if (closed) return;
136
+ const is = state();
137
+ if (is.live === said) return;
138
+ said = is.live;
139
+ try { onState(is); } catch { /* a listener's failure is not the watcher's */ }
140
+ }
141
+
142
+ // Something that should be watched is not: everything that failed is asked for again together, after a wait.
143
+ // One wait covers whatever fails while it runs; the next one is twice as long, unless the watch that failed
144
+ // had held for a while (`heldSince`): that was no part of a run of failures.
145
+ function backOff(heldSince) {
146
+ const at = now();
147
+ if (retryTimer !== null && at < retryAt) return;
148
+ if (heldSince !== undefined && at - heldSince >= RETRY_QUIET_MS) retryMs = 0;
149
+ retryMs = retryMs ? Math.min(retryMs * 2, RETRY_MAX_MS) : RETRY_FIRST_MS;
150
+ retryAt = at + retryMs;
151
+ if (retryTimer !== null) clearTimer(retryTimer);
152
+ retryTimer = setTimer(() => { retryTimer = null; if (closed) return; sync(); report(); }, retryMs);
153
+ retryTimer?.unref?.();
154
+ }
155
+
156
+ function open(key, path, options, handler) {
157
+ if (watchers.has(key) || closed) return;
158
+ // Still in its wait: the folders the page shows do not ask the system again for a watch it just refused or lost.
159
+ if (failed.has(key) && now() < retryAt) return;
160
+ let watcher;
161
+ try { watcher = watch(path, { persistent: false, ...options }, handler); }
162
+ catch (error) {
163
+ // A folder that is gone says so in its next listing. Anything else (no watches left) is a change nobody
164
+ // will be told of: it is counted, and the page reads again by itself.
165
+ if (key === root || !NOT_THERE.has(error?.code)) { failed.add(key); refused = true; }
166
+ return;
167
+ }
168
+ failed.delete(key);
169
+ openedAt.set(key, now());
170
+ watcher.on?.('error', () => {
171
+ try { watcher.close(); } catch { /* already closed */ }
172
+ if (watchers.get(key) !== watcher) return; // let go of since: not a watch that was lost
173
+ watchers.delete(key);
174
+ if (closed) return;
175
+ // Tried again after its wait; until then it is a watch that is not there.
176
+ if (key === root || exists(path)) { failed.add(key); backOff(openedAt.get(key)); }
177
+ report();
178
+ if (key === root) { try { onError(new Error('The watch on the project folder was lost.')); } catch { /* as above */ } } else note(relative(root, path));
179
+ });
180
+ watchers.set(key, watcher);
181
+ }
182
+
183
+ function sync() {
184
+ if (closed) return;
185
+ refused = false;
186
+ try { place(); } finally { if (refused) backOff(); }
187
+ }
188
+
189
+ function place() {
190
+ if (strategy === 'recursive') {
191
+ open(root, root, { recursive: true }, (type, name) => note(name ? String(name) : ''));
192
+ return;
193
+ }
194
+ for (const [key, watcher] of watchers) {
195
+ if (key === '.git' || wanted.has(key)) continue;
196
+ try { watcher.close(); } catch { /* already closed */ }
197
+ watchers.delete(key);
198
+ openedAt.delete(key);
199
+ }
200
+ for (const key of failed) if (key !== '.git' && !wanted.has(key)) failed.delete(key);
201
+ for (const dir of wanted) {
202
+ const rel = relative(root, dir);
203
+ open(dir, dir, {}, (type, name) => note(name ? join(rel, String(name)) : rel));
204
+ }
205
+ open('.git', join(root, '.git'), {}, (type, name) => { if (name) note(join('.git', String(name))); });
206
+ }
207
+
208
+ sync();
209
+ said = state().live;
210
+
211
+ return {
212
+ strategy,
213
+ /** The folders the page shows now (canonical paths, the root or below it). */
214
+ setDirs(dirs) {
215
+ const next = new Set([root]);
216
+ capped = false;
217
+ for (const dir of dirs || []) {
218
+ if (typeof dir !== 'string' || !(dir === root || dir.startsWith(root.endsWith(sep) ? root : root + sep)) || next.has(dir)) continue;
219
+ if (next.size >= MAX_WATCHED_DIRS) { capped = strategy !== 'recursive'; break; }
220
+ next.add(dir);
221
+ }
222
+ wanted = next;
223
+ sync();
224
+ report();
225
+ },
226
+ state,
227
+ count: () => watchers.size,
228
+ close() {
229
+ closed = true;
230
+ if (timer) clearTimeout(timer);
231
+ timer = null;
232
+ if (retryTimer !== null) clearTimer(retryTimer);
233
+ retryTimer = null;
234
+ for (const watcher of watchers.values()) { try { watcher.close(); } catch { /* already closed */ } }
235
+ watchers.clear();
236
+ pending.clear();
237
+ },
238
+ };
239
+ }
@@ -0,0 +1,103 @@
1
+ // ── The working tree's changes of one folder, as text ──
2
+ // GET /api/git/diff?path=&maxLines= answers with what `git diff` and
3
+ // `git diff --cached` print for a folder, and the files git does not track.
4
+ // The Codex side panel shows it; the MCP `git` tool reads it.
5
+ //
6
+ // The folder is checked like every file path of the code editor: inside a
7
+ // registered root as written, and as the filesystem resolves it (a link that
8
+ // leaves its project is refused). Who may ask is the route's own business: the
9
+ // `guard` it is handed (server.js: lib/http-guards.js ownerRouteGuard) refuses
10
+ // invite guests. git is run with an argument array, never through a shell, and
11
+ // only with commands that read: nothing in the repository, its index or its
12
+ // working tree is changed. Dependencies are injected: tested without server.js.
13
+
14
+ import { Router } from 'express';
15
+ import { statSync } from 'node:fs';
16
+ import { PathConfineError } from './path-confine.js';
17
+ import { canonicalInside } from './confined-fs.js';
18
+ import { useFileRoot } from './editor-files-api.js';
19
+ import { runGit } from './editor-git-api.js';
20
+
21
+ /** The most one git command may print here. */
22
+ export const GIT_DIFF_MAX_BYTES = 2 * 1024 * 1024;
23
+ export const GIT_DIFF_LINES = Object.freeze({ fallback: 500, min: 50, max: 2000 });
24
+
25
+ const text = (result) => result.stdout.toString('utf8');
26
+
27
+ /** A text cut to `maxLines` lines. */
28
+ function firstLines(value, maxLines) {
29
+ const lines = value.split('\n');
30
+ if (lines.length <= maxLines) return { text: value, truncated: false };
31
+ return { text: lines.slice(0, maxLines).join('\n'), truncated: true };
32
+ }
33
+
34
+ /**
35
+ * GET /api/git/diff?path=&maxLines=
36
+ * 200 { ok, branch, diff, stagedDiff, untrackedFiles, truncated }
37
+ * 400 no path · 400 { error: 'Not a git repository' } (also a path that is not a folder)
38
+ * 403 outside the registered roots, or out of one through a link; an invite guest (the guard's answer)
39
+ * 500 git failed, or printed more than `maxBytes`
40
+ * @param {object} deps
41
+ * @param {() => { dirs: string[], files: string[] }} deps.fileRoots the roots the editor's file routes allow
42
+ * @param {(args: string[], o: object) => Promise<object>} [deps.git] lib/editor-git-api.js runGit, or a stand-in
43
+ * @param {(req: object, res: object, next: () => void) => void} [deps.guard] who may use the route; on the route
44
+ * itself, as the router is mounted at the root
45
+ * @param {number} [deps.maxBytes]
46
+ */
47
+ export function createGitDiffRouter({ fileRoots, git = runGit, guard = null, maxBytes = GIT_DIFF_MAX_BYTES } = {}) {
48
+ if (typeof fileRoots !== 'function') throw new Error('createGitDiffRouter requires fileRoots');
49
+ const router = Router();
50
+ const owner = typeof guard === 'function' ? guard : (req, res, next) => next();
51
+
52
+ router.get('/api/git/diff', owner, async (req, res) => {
53
+ try {
54
+ const dir = req.query.path;
55
+ if (!dir || typeof dir !== 'string') return res.status(400).json({ error: 'path required' });
56
+ const asked = parseInt(req.query.maxLines, 10) || GIT_DIFF_LINES.fallback;
57
+ const maxLines = Math.min(Math.max(asked, GIT_DIFF_LINES.min), GIT_DIFF_LINES.max);
58
+
59
+ const folder = useFileRoot(dir, fileRoots(), (match) => canonicalInside(match.base, match.path, { allowBase: true }));
60
+ let isFolder = false;
61
+ try { isFolder = statSync(folder).isDirectory(); } catch { isFolder = false; }
62
+ const run = (args, timeoutMs) => git(args, { cwd: folder, maxBuffer: maxBytes, timeoutMs });
63
+ if (!isFolder || !(await run(['rev-parse', '--is-inside-work-tree'], 3000)).ok) {
64
+ return res.status(400).json({ error: 'Not a git repository' });
65
+ }
66
+
67
+ let branch = '';
68
+ const current = await run(['branch', '--show-current'], 3000);
69
+ if (current.ok) branch = text(current).trim();
70
+ if (!branch) {
71
+ const head = await run(['rev-parse', '--short', 'HEAD'], 3000);
72
+ if (head.ok) branch = text(head).trim();
73
+ }
74
+
75
+ const [unstaged, staged, others] = await Promise.all([
76
+ run(['diff', '--no-color'], 10000),
77
+ run(['diff', '--cached', '--no-color'], 10000),
78
+ run(['ls-files', '--others', '--exclude-standard'], 5000),
79
+ ]);
80
+ const failed = [unstaged, staged, others].find((result) => !result.ok);
81
+ if (failed) {
82
+ return res.status(500).json({ error: failed.tooLarge ? `The changes are larger than ${Math.round(maxBytes / (1024 * 1024))} MB` : 'git could not read the changes' });
83
+ }
84
+
85
+ const d = firstLines(text(unstaged), maxLines);
86
+ const s = firstLines(text(staged), maxLines);
87
+ const untracked = text(others).trim();
88
+ res.json({
89
+ ok: true,
90
+ branch,
91
+ diff: d.text,
92
+ stagedDiff: s.text,
93
+ untrackedFiles: untracked ? untracked.split('\n').filter(Boolean) : [],
94
+ truncated: d.truncated || s.truncated,
95
+ });
96
+ } catch (error) {
97
+ if (error instanceof PathConfineError) return res.status(error.status).json({ error: error.message });
98
+ res.status(500).json({ error: error?.message || String(error) });
99
+ }
100
+ });
101
+
102
+ return router;
103
+ }
@@ -0,0 +1,202 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — git, for a read
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // A repository's own configuration can name programs, and git starts them for
6
+ // commands that only read. `git status` in a folder runs what that folder's
7
+ // `core.fsmonitor` names; `git diff` runs its external diff and textconv
8
+ // drivers; `git log` runs `gpg.program` when `log.showSignature` is on; a
9
+ // partial clone fetches a missing object through whatever its remote names
10
+ // (`uploadpack`, an `ext::` address, a credential helper); an index that a read
11
+ // refreshes runs the `post-index-change` hook. The routes that read (the
12
+ // status, the log, the changes, the committed version of a file) take the
13
+ // folder from the request, and a folder may hold a repository somebody else
14
+ // prepared: for a read none of those programs is started.
15
+ //
16
+ // gitRead() / gitReadSync() give the arguments and the environment one read is
17
+ // run with (runGitReadSync() runs one, for a caller without a runner of its
18
+ // own). What they switch off, and by what:
19
+ //
20
+ // core.fsmonitor `-c core.fsmonitor=false` when the repository names a
21
+ // program there. When its value is a boolean, git's own
22
+ // (built-in) monitor is kept as it is set: a large
23
+ // repository stays fast. The repository is asked once
24
+ // (`git config --type=bool --get core.fsmonitor`, which
25
+ // runs nothing) and the answer is remembered for a few
26
+ // seconds; the value is always given on the command
27
+ // line, so what the file says later does not matter.
28
+ // hooks `-c core.hooksPath=<the null device>`: no hook of the
29
+ // repository is found (post-index-change is the one a
30
+ // read reaches).
31
+ // optional locks GIT_OPTIONAL_LOCKS=0: `git status` takes no index lock
32
+ // and writes no index. (`git diff` still refreshes the
33
+ // index's file times for a file that was only touched:
34
+ // git does not put that under this setting. Nothing is
35
+ // started for it: the hook an index write has is one of
36
+ // the hooks above.)
37
+ // external diff, `--no-ext-diff --no-textconv` on diff, show and log
38
+ // textconv (diff.external, diff.<driver>.command, .textconv).
39
+ // signatures `-c log.showSignature=false`: gpg.program is not asked.
40
+ // fetching GIT_NO_LAZY_FETCH=1, and GIT_ALLOW_PROTOCOL set and
41
+ // empty: no transport is allowed, so nothing a remote
42
+ // names is started (uploadpack, ext::, ssh, a credential
43
+ // helper, a proxy command). A read never needs a remote.
44
+ // a pager, a prompt `--no-pager`, GIT_TERMINAL_PROMPT=0.
45
+ //
46
+ // The options given with `-c` reach the git that git starts inside a submodule
47
+ // as well: a submodule's own configuration is under the same rule.
48
+ //
49
+ // What is NOT switched off: clean and process filters (`filter.<driver>.clean`,
50
+ // `filter.<driver>.process`, for the files `.gitattributes` gives that driver).
51
+ // git reads a working-tree file through its filter to compare it with the
52
+ // index: `git diff` against the working tree for every such file it looks at,
53
+ // `git status` for one whose time changed while its size did not. Without the
54
+ // filter the comparison is wrong for every repository that uses one on purpose
55
+ // (git-lfs, git-crypt), so it stays. Smudge filters are not run by a read.
56
+ //
57
+ // Writes are not run through here: a commit by the owner runs the
58
+ // repository's hooks, and that is what a commit does.
59
+
60
+ import { execFile, spawnSync } from 'node:child_process';
61
+ import { devNull } from 'node:os';
62
+
63
+ /** What the environment of a read adds. */
64
+ export const GIT_READ_ENV = Object.freeze({
65
+ GIT_OPTIONAL_LOCKS: '0',
66
+ GIT_NO_LAZY_FETCH: '1',
67
+ GIT_ALLOW_PROTOCOL: '',
68
+ GIT_TERMINAL_PROMPT: '0',
69
+ });
70
+
71
+ /** Commands that take diff options. */
72
+ const DIFF_COMMANDS = new Set(['diff', 'show', 'log']);
73
+ /** Commands that compare the index with the working tree: the file system monitor is asked by these. */
74
+ const INDEX_COMMANDS = new Set(['status', 'diff', 'ls-files']);
75
+
76
+ const PROBE = ['config', '--type=bool', '--get', 'core.fsmonitor'];
77
+ const PROBE_TIMEOUT_MS = 3000;
78
+ export const MONITOR_MEMO_MS = 10_000;
79
+ const MONITOR_MEMO_MAX = 200;
80
+
81
+ /** The environment a read is run in: `env` and what a read adds to it. */
82
+ export function gitReadEnv(env = process.env) {
83
+ return { ...env, ...GIT_READ_ENV };
84
+ }
85
+
86
+ /** Does this command compare the index with the working tree (and so ask the file system monitor)? */
87
+ export function readsIndex(args) {
88
+ return Array.isArray(args) && INDEX_COMMANDS.has(args[0]);
89
+ }
90
+
91
+ /**
92
+ * The arguments of one read: git's own options first, then the command with what switches its external programs
93
+ * off, then the caller's own arguments unchanged.
94
+ * @param {string[]} args the command and its arguments, the command first (['status', '--porcelain'])
95
+ * @param {{ monitor?: boolean }} [o] `monitor`: the repository has git's built-in file system monitor switched on
96
+ */
97
+ export function gitReadArgs(args, { monitor = false } = {}) {
98
+ const [command, ...rest] = Array.isArray(args) ? args : [];
99
+ const own = [
100
+ '--no-pager',
101
+ '-c', `core.fsmonitor=${monitor === true ? 'true' : 'false'}`,
102
+ '-c', `core.hooksPath=${devNull}`,
103
+ '-c', 'log.showSignature=false',
104
+ ];
105
+ if (command === undefined) return own;
106
+ return [...own, command, ...(DIFF_COMMANDS.has(command) ? ['--no-ext-diff', '--no-textconv'] : []), ...rest];
107
+ }
108
+
109
+ /**
110
+ * What is remembered of the repositories that were asked for their monitor: per folder, for a few seconds, for at
111
+ * most so many folders. Whatever is remembered, a read is given a boolean on its command line: a stale answer
112
+ * can keep git's own monitor on or off for a moment, it can never let a program through.
113
+ */
114
+ export function createMonitorMemo({ ttlMs = MONITOR_MEMO_MS, max = MONITOR_MEMO_MAX, now = Date.now } = {}) {
115
+ const known = new Map(); // folder → { on, at }, the one asked longest ago first
116
+ return {
117
+ get(cwd) {
118
+ const entry = known.get(cwd);
119
+ if (!entry) return undefined;
120
+ if (now() - entry.at >= ttlMs) { known.delete(cwd); return undefined; }
121
+ return entry.on;
122
+ },
123
+ set(cwd, on) {
124
+ known.delete(cwd);
125
+ known.set(cwd, { on: on === true, at: now() });
126
+ while (known.size > max) known.delete(known.keys().next().value);
127
+ return on === true;
128
+ },
129
+ size: () => known.size,
130
+ };
131
+ }
132
+
133
+ const memo = createMonitorMemo();
134
+ // The question itself: no value is given for core.fsmonitor here (the command line's would be the answer), and
135
+ // `git config` reads no index, so it asks no monitor.
136
+ const probeArgs = () => ['--no-pager', '-c', `core.hooksPath=${devNull}`, ...PROBE];
137
+ // "true" and nothing else: unset (exit 1), a program's path (exit 128: not a boolean), false, no git, a timeout.
138
+ const saysOn = (status, stdout) => status === 0 && String(stdout || '').trim() === 'true';
139
+
140
+ /**
141
+ * Is git's built-in file system monitor switched on for the repository `cwd` is in? False for every other answer:
142
+ * not set, a program's path, no repository, no git.
143
+ * @param {string} cwd
144
+ * @param {{ binary?: string, env?: object, remembered?: ReturnType<typeof createMonitorMemo> }} [o]
145
+ * @returns {Promise<boolean>}
146
+ */
147
+ export function builtInMonitor(cwd, { binary = 'git', env = process.env, remembered = memo } = {}) {
148
+ const known = remembered.get(cwd);
149
+ if (known !== undefined) return Promise.resolve(known);
150
+ return new Promise((done) => {
151
+ try {
152
+ execFile(binary, probeArgs(), { cwd, env: gitReadEnv(env), timeout: PROBE_TIMEOUT_MS, windowsHide: true, shell: false, encoding: 'utf8' }, (error, stdout) => {
153
+ done(remembered.set(cwd, saysOn(error ? (typeof error.code === 'number' ? error.code : -1) : 0, stdout)));
154
+ });
155
+ } catch {
156
+ done(remembered.set(cwd, false));
157
+ }
158
+ });
159
+ }
160
+
161
+ /** builtInMonitor, for a caller that runs git synchronously. */
162
+ export function builtInMonitorSync(cwd, { binary = 'git', env = process.env, remembered = memo, spawn = spawnSync } = {}) {
163
+ const known = remembered.get(cwd);
164
+ if (known !== undefined) return known;
165
+ let on = false;
166
+ try {
167
+ const result = spawn(binary, probeArgs(), { cwd, env: gitReadEnv(env), timeout: PROBE_TIMEOUT_MS, windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] });
168
+ on = !result.error && saysOn(result.status, result.stdout);
169
+ } catch { on = false; }
170
+ return remembered.set(cwd, on);
171
+ }
172
+
173
+ /**
174
+ * How to run one read in `cwd`: `{ args, env }` for the program `git`. The repository is asked for its monitor
175
+ * only for a command that compares the index with the working tree.
176
+ * @param {string} cwd
177
+ * @param {string[]} args the command first
178
+ * @param {{ binary?: string, env?: object, remembered?: object }} [o]
179
+ * @returns {Promise<{ args: string[], env: object }>}
180
+ */
181
+ export async function gitRead(cwd, args, o = {}) {
182
+ const monitor = readsIndex(args) ? await builtInMonitor(cwd, o) : false;
183
+ return { args: gitReadArgs(args, { monitor }), env: gitReadEnv(o.env) };
184
+ }
185
+
186
+ /** gitRead, for a caller that runs git synchronously. */
187
+ export function gitReadSync(cwd, args, o = {}) {
188
+ const monitor = readsIndex(args) ? builtInMonitorSync(cwd, o) : false;
189
+ return { args: gitReadArgs(args, { monitor }), env: gitReadEnv(o.env) };
190
+ }
191
+
192
+ /**
193
+ * One read, run now, for a caller that has no runner of its own: what spawnSync answers.
194
+ * @param {string} cwd
195
+ * @param {string[]} args the command first
196
+ * @param {object} [options] spawnSync's (`encoding`, `timeout`, `maxBuffer`, …); `cwd` and what a read adds to
197
+ * the environment are not the caller's to change
198
+ */
199
+ export function runGitReadSync(cwd, args, options = {}) {
200
+ const read = gitReadSync(cwd, args, { env: options.env });
201
+ return spawnSync('git', read.args, { windowsHide: true, ...options, cwd, env: read.env });
202
+ }
@@ -0,0 +1,89 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — Greeting: the HTTP surface (/api/greeting)
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // GET / the setting, what the engine can do, the last greeting shown
6
+ // PUT / { enabled } the one switch
7
+ // POST /open { surface, cwd?, project?, session?, note? } a fresh interactive session opened
8
+ // POST /preview { project?, cwd?, write? } what a new session would be greeted with now
9
+ // POST /rearm { project?, cwd? } greet the next session there once (first live look)
10
+ //
11
+ // Mounted synchronously, before the engine exists: until then, and when it did
12
+ // not start, every route answers 503 with the reason, so Settings can tell an
13
+ // engine that failed from a server that is older than the page (404). A
14
+ // greeting is about the owner's own work: invite guests get 403.
15
+
16
+ import { Router, json } from 'express';
17
+ import { ID_RE, SURFACES } from './budgets.js';
18
+
19
+ const text = (value, max) => (typeof value === 'string' && value.length <= max ? value : null);
20
+
21
+ export function createGreetingApi({ getEngine = () => null, getError = () => null, isGuestRequest = () => false, defaultProject = () => 'global', log = () => {} } = {}) {
22
+ const router = Router();
23
+ router.use(json({ limit: '32kb' }));
24
+ router.use((req, res, next) => {
25
+ let guest = false;
26
+ try { guest = !!isGuestRequest(req); } catch { guest = true; }
27
+ if (guest) return res.status(403).json({ ok: false, code: 'FORBIDDEN', error: 'Not available to guests' });
28
+ const engine = getEngine();
29
+ if (!engine) {
30
+ let reason = null;
31
+ try { reason = getError(); } catch { reason = null; }
32
+ return res.status(503).json({ ok: false, code: 'GREETING_UNAVAILABLE', error: 'The greeting engine is not running', reason: reason || 'starting' });
33
+ }
34
+ req.greeting = engine;
35
+ next();
36
+ });
37
+ const place = (body = {}) => {
38
+ const project = text(body.project, 120);
39
+ const cwd = text(body.cwd, 2048);
40
+ if (project && /^[A-Za-z0-9_.-]+$/.test(project)) return { project };
41
+ if (cwd) return { cwd };
42
+ return {};
43
+ };
44
+ const fail = (res, error) => {
45
+ log('greeting:api-error', error?.message || String(error));
46
+ res.status(500).json({ ok: false, error: String(error?.message || error).slice(0, 200) });
47
+ };
48
+
49
+ router.get('/', (req, res) => {
50
+ try { res.json({ ok: true, ...req.greeting.status() }); } catch (error) { fail(res, error); }
51
+ });
52
+
53
+ router.put('/', (req, res) => {
54
+ try {
55
+ if (typeof req.body?.enabled !== 'boolean') return res.status(400).json({ ok: false, code: 'BAD_REQUEST', error: 'enabled must be true or false' });
56
+ req.greeting.setEnabled(req.body.enabled);
57
+ res.json({ ok: true, ...req.greeting.status() });
58
+ } catch (error) { fail(res, error); }
59
+ });
60
+
61
+ router.post('/open', async (req, res) => {
62
+ try {
63
+ const body = req.body || {};
64
+ const surface = text(body.surface, 40);
65
+ if (!surface || !Object.prototype.hasOwnProperty.call(SURFACES, surface)) return res.status(400).json({ ok: false, code: 'BAD_REQUEST', error: 'Unknown surface' });
66
+ const session = text(body.session, 160);
67
+ const answer = await req.greeting.open({ surface, ...place(body), session: session && ID_RE.test(session) ? session : null, note: body.note === true });
68
+ res.json({ ok: true, ...answer });
69
+ } catch (error) { fail(res, error); }
70
+ });
71
+
72
+ router.post('/preview', async (req, res) => {
73
+ try {
74
+ const where = place(req.body || {});
75
+ if (!where.project && !where.cwd) { try { where.project = String(defaultProject() || 'global'); } catch { where.project = 'global'; } }
76
+ res.json({ ok: true, preview: await req.greeting.preview({ ...where, write: req.body?.write === true }) });
77
+ } catch (error) { fail(res, error); }
78
+ });
79
+
80
+ router.post('/rearm', (req, res) => {
81
+ try {
82
+ const where = place(req.body || {});
83
+ if (!where.project && !where.cwd) { try { where.project = String(defaultProject() || 'global'); } catch { where.project = 'global'; } }
84
+ res.json({ ok: true, project: req.greeting.rearm(where) });
85
+ } catch (error) { fail(res, error); }
86
+ });
87
+
88
+ return router;
89
+ }