synabun 2.0.0 → 2.0.7

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 (507) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/NOTICE +5 -0
  3. package/README.md +2 -1
  4. package/THIRD-PARTY-LICENSES.md +59 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +153 -18
  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 +26 -3
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/lib/child-path.js +303 -0
  14. package/lib/claude-hooks.js +2 -1
  15. package/lib/data-home-migration.js +2 -0
  16. package/lib/desktop-app.js +265 -0
  17. package/lib/external-tools.js +7 -3
  18. package/lib/log-redact.js +102 -0
  19. package/lib/packaged-runtime.js +138 -11
  20. package/lib/start-launcher.js +88 -1
  21. package/mcp-server/dist/http.d.ts +3 -1
  22. package/mcp-server/dist/http.d.ts.map +1 -1
  23. package/mcp-server/dist/http.js +15 -4
  24. package/mcp-server/dist/http.js.map +1 -1
  25. package/mcp-server/dist/index.d.ts.map +1 -1
  26. package/mcp-server/dist/index.js +9 -6
  27. package/mcp-server/dist/index.js.map +1 -1
  28. package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
  29. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
  30. package/mcp-server/dist/services/budget-judgments.js +153 -0
  31. package/mcp-server/dist/services/budget-judgments.js.map +1 -0
  32. package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
  33. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  34. package/mcp-server/dist/services/codex-config-heal.js +41 -4
  35. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  36. package/mcp-server/dist/services/identity.d.ts +11 -0
  37. package/mcp-server/dist/services/identity.d.ts.map +1 -1
  38. package/mcp-server/dist/services/identity.js +24 -0
  39. package/mcp-server/dist/services/identity.js.map +1 -1
  40. package/mcp-server/dist/services/neural-interface.d.ts +13 -0
  41. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  42. package/mcp-server/dist/services/neural-interface.js +34 -2
  43. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  44. package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
  45. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
  46. package/mcp-server/dist/services/typesafe-config.js +4 -1
  47. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  48. package/mcp-server/dist/tools/agents.d.ts +10 -7
  49. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  50. package/mcp-server/dist/tools/agents.js +34 -13
  51. package/mcp-server/dist/tools/agents.js.map +1 -1
  52. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
  53. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
  54. package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
  55. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
  56. package/mcp-server/dist/tui.js +2 -1
  57. package/mcp-server/dist/tui.js.map +1 -1
  58. package/mcp-server/dist/version.d.ts +5 -0
  59. package/mcp-server/dist/version.d.ts.map +1 -0
  60. package/mcp-server/dist/version.js +21 -0
  61. package/mcp-server/dist/version.js.map +1 -0
  62. package/mcp-server/package-lock.json +2 -2
  63. package/mcp-server/package.json +1 -3
  64. package/mcp-server/run.mjs +15 -9
  65. package/mcp-server/src/http.ts +18 -3
  66. package/mcp-server/src/index.ts +9 -6
  67. package/mcp-server/src/services/budget-judgments.ts +183 -0
  68. package/mcp-server/src/services/codex-config-heal.ts +35 -4
  69. package/mcp-server/src/services/identity.ts +27 -0
  70. package/mcp-server/src/services/neural-interface.ts +34 -2
  71. package/mcp-server/src/services/typesafe-config.ts +4 -1
  72. package/mcp-server/src/tools/agents.ts +33 -14
  73. package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
  74. package/mcp-server/src/tui.ts +2 -1
  75. package/mcp-server/src/version.ts +22 -0
  76. package/neural-interface/i18n/en.json +2126 -209
  77. package/neural-interface/i18n/pt-BR.json +2123 -207
  78. package/neural-interface/lib/account-usage.js +163 -0
  79. package/neural-interface/lib/assistant-api.js +316 -57
  80. package/neural-interface/lib/assistant-brains/claude.js +19 -2
  81. package/neural-interface/lib/assistant-brains/codex.js +3 -0
  82. package/neural-interface/lib/assistant-brains/opencode.js +17 -4
  83. package/neural-interface/lib/assistant-budget-planner.js +390 -0
  84. package/neural-interface/lib/assistant-budget-split.js +408 -0
  85. package/neural-interface/lib/assistant-budget.js +94 -95
  86. package/neural-interface/lib/assistant-catalog.js +179 -27
  87. package/neural-interface/lib/assistant-config.js +91 -14
  88. package/neural-interface/lib/assistant-dispatch.js +494 -138
  89. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  90. package/neural-interface/lib/assistant-jev.js +26 -3
  91. package/neural-interface/lib/assistant-memory.js +24 -10
  92. package/neural-interface/lib/assistant-persona.js +73 -12
  93. package/neural-interface/lib/assistant-plan-permissions.js +8 -1
  94. package/neural-interface/lib/assistant-pricing.js +21 -6
  95. package/neural-interface/lib/assistant-route-accounts.js +14 -0
  96. package/neural-interface/lib/assistant-route-gate.js +19 -3
  97. package/neural-interface/lib/assistant-router.js +373 -42
  98. package/neural-interface/lib/assistant-runtime.js +488 -59
  99. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  100. package/neural-interface/lib/assistant-test-density.js +142 -0
  101. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  102. package/neural-interface/lib/assistant-user-context.js +908 -0
  103. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  104. package/neural-interface/lib/assistant-user-learn.js +404 -0
  105. package/neural-interface/lib/assistant-user-threads.js +226 -0
  106. package/neural-interface/lib/assistant-user-writer.js +183 -0
  107. package/neural-interface/lib/augmented-path.js +317 -53
  108. package/neural-interface/lib/browser-background-tab.js +134 -0
  109. package/neural-interface/lib/browser-screenshot.js +678 -61
  110. package/neural-interface/lib/claude-accounts.js +342 -31
  111. package/neural-interface/lib/claude-agent-bridge.js +123 -9
  112. package/neural-interface/lib/claude-executable.js +6 -1
  113. package/neural-interface/lib/claude-model-catalog.js +33 -5
  114. package/neural-interface/lib/claude-panel-auth.js +221 -0
  115. package/neural-interface/lib/claude-profile-api-key.js +62 -0
  116. package/neural-interface/lib/claude-temporary.js +46 -7
  117. package/neural-interface/lib/cli-install-api.js +137 -0
  118. package/neural-interface/lib/cli-install.js +797 -0
  119. package/neural-interface/lib/cli-path.js +130 -0
  120. package/neural-interface/lib/cli-resolver.js +744 -0
  121. package/neural-interface/lib/cli-update-planner.js +143 -7
  122. package/neural-interface/lib/cloudflared/install.js +533 -0
  123. package/neural-interface/lib/cloudflared/manager.js +545 -0
  124. package/neural-interface/lib/cloudflared/pins.js +115 -0
  125. package/neural-interface/lib/cloudflared/pins.json +63 -0
  126. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  127. package/neural-interface/lib/codex-account-auth.js +178 -0
  128. package/neural-interface/lib/codex-accounts.js +489 -0
  129. package/neural-interface/lib/codex-capabilities.js +2 -1
  130. package/neural-interface/lib/codex-compat-rpc.js +8 -2
  131. package/neural-interface/lib/codex-mcp-approvals.js +5 -1
  132. package/neural-interface/lib/codex-panel-auth.js +62 -0
  133. package/neural-interface/lib/codex-session-settings.js +1 -1
  134. package/neural-interface/lib/codex-settings-schema.js +120 -0
  135. package/neural-interface/lib/desktop/config.js +3 -0
  136. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  137. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  138. package/neural-interface/lib/editor-completion.js +714 -0
  139. package/neural-interface/lib/editor-config.js +149 -0
  140. package/neural-interface/lib/editor-files-api.js +268 -0
  141. package/neural-interface/lib/editor-git-api.js +134 -0
  142. package/neural-interface/lib/file-explorer-api.js +1039 -0
  143. package/neural-interface/lib/file-explorer-system.js +355 -0
  144. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  145. package/neural-interface/lib/file-explorer-watch.js +239 -0
  146. package/neural-interface/lib/git-bash/bzip2.js +339 -0
  147. package/neural-interface/lib/git-bash/claude-settings.js +73 -0
  148. package/neural-interface/lib/git-bash/extract-worker.js +34 -0
  149. package/neural-interface/lib/git-bash/install.js +529 -0
  150. package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
  151. package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
  152. package/neural-interface/lib/git-bash/manager.js +268 -0
  153. package/neural-interface/lib/git-bash/pins.js +206 -0
  154. package/neural-interface/lib/git-bash/pins.json +34 -0
  155. package/neural-interface/lib/git-bash/resolver.js +194 -0
  156. package/neural-interface/lib/git-diff-api.js +103 -0
  157. package/neural-interface/lib/git-read.js +234 -0
  158. package/neural-interface/lib/greeting/api.js +89 -0
  159. package/neural-interface/lib/greeting/budgets.js +90 -0
  160. package/neural-interface/lib/greeting/engine.js +461 -0
  161. package/neural-interface/lib/greeting/facts.js +320 -0
  162. package/neural-interface/lib/greeting/sources.js +252 -0
  163. package/neural-interface/lib/greeting/writer.js +139 -0
  164. package/neural-interface/lib/http-guards.js +189 -13
  165. package/neural-interface/lib/install-details.js +145 -0
  166. package/neural-interface/lib/machine-names.js +88 -0
  167. package/neural-interface/lib/mcp-installer.js +14 -2
  168. package/neural-interface/lib/memory-api.js +31 -7
  169. package/neural-interface/lib/native-loop-providers.js +26 -8
  170. package/neural-interface/lib/native-loop-runtime.js +9 -4
  171. package/neural-interface/lib/os-path.js +319 -0
  172. package/neural-interface/lib/plugin-installer.js +4 -2
  173. package/neural-interface/lib/project-storage.js +5 -7
  174. package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
  175. package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
  176. package/neural-interface/lib/pty-host/core.js +2 -1
  177. package/neural-interface/lib/pty-host/manager.js +27 -2
  178. package/neural-interface/lib/remote-policy.js +20 -11
  179. package/neural-interface/lib/request-gate.js +589 -0
  180. package/neural-interface/lib/session-title-generator.js +4 -3
  181. package/neural-interface/lib/system-about.js +340 -0
  182. package/neural-interface/lib/system-activity.js +66 -0
  183. package/neural-interface/lib/terminal-env.js +11 -3
  184. package/neural-interface/lib/terminal-path.js +55 -0
  185. package/neural-interface/lib/ui-blob-api.js +108 -0
  186. package/neural-interface/lib/ui-blob-store.js +68 -0
  187. package/neural-interface/lib/virtual-socket.js +12 -1
  188. package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
  189. package/neural-interface/lib/whatsapp/bridge.js +78 -34
  190. package/neural-interface/lib/whatsapp/commands.js +13 -14
  191. package/neural-interface/lib/whatsapp/config.js +134 -20
  192. package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
  193. package/neural-interface/lib/whatsapp/host-core.js +168 -35
  194. package/neural-interface/lib/whatsapp/inbound.js +5 -3
  195. package/neural-interface/lib/whatsapp/local-file.js +524 -0
  196. package/neural-interface/lib/whatsapp/manager.js +92 -32
  197. package/neural-interface/lib/whatsapp/paths.js +11 -1
  198. package/neural-interface/lib/whatsapp/protocol.js +65 -5
  199. package/neural-interface/lib/whatsapp/redact.js +63 -0
  200. package/neural-interface/lib/whatsapp/service.js +343 -91
  201. package/neural-interface/lib/whatsapp/transcription.js +463 -0
  202. package/neural-interface/package-lock.json +701 -2
  203. package/neural-interface/package.json +13 -2
  204. package/neural-interface/public/index.html +2 -0
  205. package/neural-interface/public/index2d.html +2 -0
  206. package/neural-interface/public/install-app.html +9 -6
  207. package/neural-interface/public/offline.html +19 -3
  208. package/neural-interface/public/onboarding.html +107 -2
  209. package/neural-interface/public/shared/account-i18n.js +7 -0
  210. package/neural-interface/public/shared/account-menu.js +539 -0
  211. package/neural-interface/public/shared/account-usage.js +19 -0
  212. package/neural-interface/public/shared/account-view.js +335 -0
  213. package/neural-interface/public/shared/agent-runtime-options.js +50 -14
  214. package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
  215. package/neural-interface/public/shared/assistant/asst-api.js +32 -2
  216. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  217. package/neural-interface/public/shared/assistant/asst-brain-picker.js +430 -81
  218. package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
  219. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  220. package/neural-interface/public/shared/assistant/asst-mascot.js +140 -61
  221. package/neural-interface/public/shared/assistant/asst-menu.js +27 -11
  222. package/neural-interface/public/shared/assistant/asst-panel.js +198 -42
  223. package/neural-interface/public/shared/assistant/asst-render.js +197 -23
  224. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  225. package/neural-interface/public/shared/assistant/asst-route.js +95 -343
  226. package/neural-interface/public/shared/assistant/asst-routes-editor.js +534 -0
  227. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  228. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  229. package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
  230. package/neural-interface/public/shared/assistant/asst-state.js +186 -2
  231. package/neural-interface/public/shared/assistant/asst-styles.js +247 -11
  232. package/neural-interface/public/shared/blob-store.js +111 -22
  233. package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
  234. package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
  235. package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
  236. package/neural-interface/public/shared/cdx/cdx-panel.js +80 -118
  237. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  238. package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
  239. package/neural-interface/public/shared/cdx/cdx-styles.js +164 -107
  240. package/neural-interface/public/shared/cdx/cdx-tabs.js +348 -668
  241. package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
  242. package/neural-interface/public/shared/claude-account-view.js +3 -0
  243. package/neural-interface/public/shared/cli-install-view.js +392 -0
  244. package/neural-interface/public/shared/cli-status.js +282 -22
  245. package/neural-interface/public/shared/constants.js +6 -1
  246. package/neural-interface/public/shared/cp/cp-events.js +2 -1
  247. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  248. package/neural-interface/public/shared/cp/cp-styles.js +61 -0
  249. package/neural-interface/public/shared/desktop-bridge.js +39 -0
  250. package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
  251. package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
  252. package/neural-interface/public/shared/desktop-titlebar.js +153 -0
  253. package/neural-interface/public/shared/desktop-update-view.js +462 -0
  254. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  255. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  256. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  257. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  258. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  259. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  260. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  261. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  262. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  263. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  264. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  265. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  266. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  267. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  268. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  269. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  270. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  271. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  272. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  273. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  274. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  275. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  276. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  277. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  278. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  279. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  280. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  281. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  282. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  283. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  284. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  285. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  286. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  287. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  288. package/neural-interface/public/shared/file-links.js +339 -0
  289. package/neural-interface/public/shared/git-bash-view.js +145 -0
  290. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  291. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  292. package/neural-interface/public/shared/html-shell.js +161 -165
  293. package/neural-interface/public/shared/i18n.js +19 -1
  294. package/neural-interface/public/shared/install-details-view.js +86 -0
  295. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  296. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  297. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +131 -17
  298. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  299. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +22 -0
  300. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +158 -0
  301. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  302. package/neural-interface/public/shared/pwa-install-page.js +32 -2
  303. package/neural-interface/public/shared/pwa-install.css +3 -3
  304. package/neural-interface/public/shared/pwa-install.js +100 -1
  305. package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
  306. package/neural-interface/public/shared/settings/settings-about.js +254 -0
  307. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  308. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  309. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  310. package/neural-interface/public/shared/settings/settings-ia.js +33 -4
  311. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  312. package/neural-interface/public/shared/showcase-privacy.js +489 -0
  313. package/neural-interface/public/shared/start-bridge.js +238 -19
  314. package/neural-interface/public/shared/storage.js +30 -3
  315. package/neural-interface/public/shared/styles.css +1710 -770
  316. package/neural-interface/public/shared/synabun-mascot.js +68 -58
  317. package/neural-interface/public/shared/tunnel-view.js +167 -0
  318. package/neural-interface/public/shared/ui-claude-panel.js +369 -229
  319. package/neural-interface/public/shared/ui-cli-banner.js +355 -0
  320. package/neural-interface/public/shared/ui-detail.js +15 -0
  321. package/neural-interface/public/shared/ui-explorer.js +22 -0
  322. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  323. package/neural-interface/public/shared/ui-file-links.js +103 -0
  324. package/neural-interface/public/shared/ui-git-bash.js +124 -0
  325. package/neural-interface/public/shared/ui-help.js +26 -0
  326. package/neural-interface/public/shared/ui-invite.js +126 -116
  327. package/neural-interface/public/shared/ui-judgments.js +10 -1
  328. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  329. package/neural-interface/public/shared/ui-loading.js +187 -19
  330. package/neural-interface/public/shared/ui-menubar.js +25 -33
  331. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  332. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  333. package/neural-interface/public/shared/ui-navbar.js +48 -22
  334. package/neural-interface/public/shared/ui-provider-auth.js +104 -0
  335. package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
  336. package/neural-interface/public/shared/ui-sessions.js +9 -3
  337. package/neural-interface/public/shared/ui-settings.js +509 -490
  338. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  339. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
  340. package/neural-interface/public/shared/ui-sync.js +22 -0
  341. package/neural-interface/public/shared/ui-terminal.js +40 -5
  342. package/neural-interface/public/shared/ui-tool-updates.js +5 -0
  343. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  344. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  345. package/neural-interface/public/shared/ui-update.js +594 -5
  346. package/neural-interface/public/shared/ui-whatsapp.js +21 -6
  347. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  348. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  349. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  350. package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
  351. package/neural-interface/public/shared/worker-end.js +253 -0
  352. package/neural-interface/public/sw.js +2 -2
  353. package/neural-interface/public/variant/2d/main.js +10 -1
  354. package/neural-interface/public/variant/3d/main.js +10 -1
  355. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  356. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  357. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  358. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  359. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  360. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  361. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  362. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  363. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  364. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  365. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  366. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  367. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  368. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  369. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  370. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  371. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  372. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  373. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  374. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  375. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  376. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  377. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  378. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  379. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  380. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  381. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  382. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  383. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  384. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  385. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  386. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  387. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  388. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  389. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  390. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  391. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  392. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  393. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  394. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  395. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  396. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  397. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  398. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  399. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  400. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  401. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  402. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  403. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  404. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  405. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  406. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  407. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  408. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  409. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  410. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  411. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  412. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  413. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  414. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  415. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  416. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  417. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  418. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  419. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  420. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  421. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  422. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  423. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  424. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  425. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  426. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  427. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  428. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  429. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  430. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  431. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  432. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  433. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  434. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  435. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  436. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  437. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  438. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  439. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  440. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  441. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  442. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  443. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  444. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  445. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  446. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  447. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  448. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  449. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  450. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  451. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  452. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  453. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  454. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  455. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  456. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  457. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  458. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  459. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  460. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  461. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  462. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  463. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  464. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  465. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  466. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  467. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  468. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  469. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  470. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  471. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  472. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  473. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  474. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  475. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  476. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  477. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  478. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  479. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  480. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  481. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  482. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  483. package/neural-interface/scripts/build-vendor.mjs +78 -2
  484. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  485. package/neural-interface/server.js +2085 -1629
  486. package/neural-interface/templates/rulesets/README.md +1 -1
  487. package/package.json +5 -8
  488. package/setup.js +53 -15
  489. package/benchmarks/browser-live-results.json +0 -79
  490. package/benchmarks/browser-v2-results.json +0 -258
  491. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  492. package/neural-interface/public/category-logos/claude.svg +0 -1
  493. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  494. package/neural-interface/public/category-logos/openai.svg +0 -1
  495. package/neural-interface/public/category-logos/synabun.png +0 -0
  496. package/neural-interface/public/claude.svg +0 -1
  497. package/neural-interface/public/gallery.svg +0 -6
  498. package/neural-interface/public/leonardoai.svg +0 -1
  499. package/neural-interface/public/mcp.svg +0 -4
  500. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  501. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  502. package/neural-interface/public/sessionicon.svg +0 -8
  503. package/neural-interface/public/toolbar-album.svg +0 -8
  504. package/neural-interface/public/toolbar-question.svg +0 -6
  505. package/neural-interface/public/toolbar-sessions.svg +0 -6
  506. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  507. package/skills/synabun/synabunicon.png +0 -0
@@ -0,0 +1,194 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — Git Bash: which one Claude Code runs
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // Claude Code on Windows looks for Git Bash itself, in this order (read from
6
+ // its 2.1.302 build): CLAUDE_CODE_GIT_BASH_PATH when it names an existing
7
+ // bash.exe / sh.exe (the `env` block of ~/.claude/settings.json is applied
8
+ // before it looks, so a value there counts the same), then
9
+ // C:\Program Files\Git\bin\bash.exe and C:\Program Files (x86)\Git\bin\bash.exe,
10
+ // then the first `git` on its PATH, two folders up, bin\bash.exe. Without one,
11
+ // its Bash tool is gone and every hook runs in PowerShell.
12
+ //
13
+ // resolveGitBash() answers which Git Bash a Claude Code started by SynaBun ends
14
+ // up with, and whether SynaBun has to name it:
15
+ //
16
+ // env / settings CLAUDE_CODE_GIT_BASH_PATH of SynaBun's own environment, or
17
+ // of the user's ~/.claude/settings.json: the user's choice,
18
+ // which Claude Code reads by itself
19
+ // system a Git Bash of the PC's own. When Claude Code finds it by
20
+ // its own rules nothing is passed (SynaBun never overrides
21
+ // what already works); one only SynaBun finds (git on PATH
22
+ // in a layout Claude Code does not walk, a per-user or Scoop
23
+ // install that is not on PATH) is passed to it
24
+ // bundled SynaBun's copy inside an installed app (<resources>\git)
25
+ // downloaded SynaBun's copy in the data home (a Windows npm install)
26
+ //
27
+ // A path inside SynaBun's own copies is never taken for the user's, whatever
28
+ // variable or PATH entry leads to it (a server started from a SynaBun terminal
29
+ // inherits both): it is found again as SynaBun's own, last. Every file system
30
+ // question is injectable, so the Windows rules are tested from any system.
31
+
32
+ import { statSync } from 'node:fs';
33
+ import { win32 } from 'node:path';
34
+
35
+ /** The variable Claude Code reads (and the settings `env` key SynaBun writes for the user's own CLI). */
36
+ export const GIT_BASH_ENV = 'CLAUDE_CODE_GIT_BASH_PATH';
37
+
38
+ /** The file names Claude Code accepts in CLAUDE_CODE_GIT_BASH_PATH. */
39
+ const BASH_NAMES = new Set(['bash.exe', 'sh.exe', 'bash', 'sh']);
40
+
41
+ /** Where Claude Code looks before PATH: these two, literally. */
42
+ export const CLAUDE_FIXED_BASH = Object.freeze(['C:\\Program Files\\Git\\bin\\bash.exe', 'C:\\Program Files (x86)\\Git\\bin\\bash.exe']);
43
+
44
+ const key = (path) => win32.normalize(String(path || '')).replace(/[\\/]+$/, '').toLowerCase();
45
+
46
+ const defaultIsFile = (path) => { try { return statSync(path).isFile(); } catch { return false; } };
47
+
48
+ /** Is `path` inside one of `roots` (or one of them)? Windows rules: case and slashes do not matter. */
49
+ export function insideAny(path, roots) {
50
+ const at = key(path);
51
+ return roots.filter(Boolean).some((root) => { const top = key(root); return at === top || at.startsWith(`${top}\\`); });
52
+ }
53
+
54
+ /** A value of CLAUDE_CODE_GIT_BASH_PATH that Claude Code would take: a bash.exe / sh.exe that exists. */
55
+ export function acceptableBashPath(value, { isFile = defaultIsFile } = {}) {
56
+ const path = String(value || '').trim();
57
+ if (!path || !BASH_NAMES.has(win32.basename(path).toLowerCase())) return false;
58
+ return isFile(path);
59
+ }
60
+
61
+ /** The PATH folders, in order, `%VARS%` of an unexpanded entry left as they are (they name no git either way). */
62
+ function pathFolders(pathValue) {
63
+ return String(pathValue || '').split(';').map((entry) => entry.trim().replace(/^"(.*)"$/, '$1')).filter(Boolean);
64
+ }
65
+
66
+ /** The first git on `pathValue`, as Windows finds it (git.exe, then git.cmd), skipping SynaBun's own folders. */
67
+ export function firstGitOnPath(pathValue, { isFile = defaultIsFile, own = [] } = {}) {
68
+ for (const folder of pathFolders(pathValue)) {
69
+ for (const name of ['git.exe', 'git.cmd']) {
70
+ const file = win32.join(folder, name);
71
+ if (!isFile(file)) continue;
72
+ if (insideAny(file, own)) continue;
73
+ return file;
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+
79
+ /** The Git Bash a git found on PATH belongs to: Claude Code's own rule first (git\..\..\bin), then one more level up. */
80
+ export function bashOfGit(gitFile, { isFile = defaultIsFile } = {}) {
81
+ if (!gitFile) return null;
82
+ const folder = win32.dirname(gitFile);
83
+ for (const candidate of [win32.join(folder, '..', 'bin', 'bash.exe'), win32.join(folder, '..', '..', 'bin', 'bash.exe')]) {
84
+ if (isFile(candidate)) return win32.normalize(candidate);
85
+ }
86
+ return null;
87
+ }
88
+
89
+ /** The folders Git for Windows is installed in when nothing names it: every installer's default, per machine and per user, and Scoop's. */
90
+ export function standardGitRoots(env = {}) {
91
+ const roots = [];
92
+ const add = (base, ...rest) => { if (base) roots.push(win32.join(base, ...rest)); };
93
+ add(env.ProgramFiles || env.PROGRAMFILES, 'Git');
94
+ add(env.ProgramW6432, 'Git');
95
+ add(env['ProgramFiles(x86)'] || env['PROGRAMFILES(X86)'], 'Git');
96
+ roots.push('C:\\Program Files\\Git', 'C:\\Program Files (x86)\\Git');
97
+ add(env.LOCALAPPDATA, 'Programs', 'Git');
98
+ add(env.SCOOP, 'apps', 'git', 'current');
99
+ add(env.USERPROFILE, 'scoop', 'apps', 'git', 'current');
100
+ const seen = new Set();
101
+ return roots.filter((root) => { const id = key(root); if (seen.has(id)) return false; seen.add(id); return true; });
102
+ }
103
+
104
+ /**
105
+ * What Claude Code finds by itself, by its own rules, or null: its variable (from SynaBun's environment, then the
106
+ * user's settings), its two fixed places, the first git on PATH. `{ path, source }`.
107
+ */
108
+ export function claudeCodeFinds({ env = {}, settingsValue = null, pathValue = '', isFile = defaultIsFile, own = [] } = {}) {
109
+ const fromEnv = env[GIT_BASH_ENV];
110
+ if (fromEnv && !insideAny(fromEnv, own) && acceptableBashPath(fromEnv, { isFile })) return { path: win32.normalize(fromEnv), source: 'env' };
111
+ if (settingsValue && !insideAny(settingsValue, own) && acceptableBashPath(settingsValue, { isFile })) return { path: win32.normalize(settingsValue), source: 'settings' };
112
+ for (const fixed of CLAUDE_FIXED_BASH) if (isFile(fixed)) return { path: fixed, source: 'system' };
113
+ const git = firstGitOnPath(pathValue, { isFile, own });
114
+ if (git) {
115
+ const bash = win32.normalize(win32.join(win32.dirname(git), '..', 'bin', 'bash.exe'));
116
+ if (isFile(bash) && !insideAny(bash, own)) return { path: bash, source: 'system' };
117
+ }
118
+ return null;
119
+ }
120
+
121
+ /** The cmd folder (with its git.exe) of the Git for Windows a bin\bash.exe belongs to, or null. */
122
+ export function gitOfBash(bashPath, { isFile = defaultIsFile } = {}) {
123
+ if (!bashPath || win32.basename(win32.dirname(bashPath)).toLowerCase() !== 'bin') return null;
124
+ const root = win32.dirname(win32.dirname(bashPath));
125
+ const git = win32.join(root, 'cmd', 'git.exe');
126
+ return isFile(git) ? { git, cmdDir: win32.dirname(git) } : null;
127
+ }
128
+
129
+ /**
130
+ * Which Git Bash a Claude Code started by SynaBun ends up with, and what SynaBun has to give it.
131
+ *
132
+ * @param {object} o
133
+ * @param {string} [o.platform]
134
+ * @param {object} [o.env] SynaBun's environment (CLAUDE_CODE_GIT_BASH_PATH, the folders above)
135
+ * @param {string|null} [o.settingsValue] env.CLAUDE_CODE_GIT_BASH_PATH of the user's ~/.claude/settings.json
136
+ * @param {string} [o.pathValue] the PATH a git is looked for on (this process's, then the OS PATH)
137
+ * @param {Array<{ source: 'bundled' | 'downloaded', root: string, ok: boolean, bash: string, git: string }>} [o.copies]
138
+ * SynaBun's own copies, already checked (manager.js), in this order
139
+ * @param {string[]} [o.ownRoots] SynaBun's own Git folders: nothing inside them is the user's
140
+ * @returns {{ supported: boolean, path: string|null, source: string|null, passToClaude: boolean, git: string|null, cmdDir: string|null, claudeFindsItself: boolean }}
141
+ */
142
+ export function resolveGitBash({ platform = process.platform, env = process.env, settingsValue = null, pathValue = '', copies = [], ownRoots = [], isFile = defaultIsFile } = {}) {
143
+ const none = { supported: platform === 'win32', path: null, source: null, passToClaude: false, git: null, cmdDir: null, claudeFindsItself: false };
144
+ if (platform !== 'win32') return none;
145
+ const own = ownRoots.filter(Boolean);
146
+
147
+ // 1. What Claude Code finds by itself: nothing is passed, nothing downloaded.
148
+ const found = claudeCodeFinds({ env, settingsValue, pathValue, isFile, own });
149
+ if (found) {
150
+ const git = found.source === 'system' ? gitOfBash(found.path, { isFile }) : null;
151
+ return { ...none, path: found.path, source: found.source, git: git?.git || null, cmdDir: git?.cmdDir || null, claudeFindsItself: true };
152
+ }
153
+
154
+ // 2. A Git Bash of the PC that only SynaBun finds: named to Claude Code.
155
+ const candidates = [];
156
+ const onPath = firstGitOnPath(pathValue, { isFile, own });
157
+ const derived = bashOfGit(onPath, { isFile });
158
+ if (derived) candidates.push(derived);
159
+ for (const root of standardGitRoots(env)) candidates.push(win32.join(root, 'bin', 'bash.exe'));
160
+ for (const candidate of candidates) {
161
+ if (insideAny(candidate, own) || !isFile(candidate)) continue;
162
+ const git = gitOfBash(candidate, { isFile });
163
+ return { ...none, path: candidate, source: 'system', passToClaude: true, git: git?.git || null, cmdDir: git?.cmdDir || null };
164
+ }
165
+
166
+ // 3. SynaBun's own copy: the one inside the app, then the one in the data home.
167
+ for (const copy of copies) {
168
+ if (!copy?.ok) continue;
169
+ return { ...none, path: copy.bash, source: copy.source, passToClaude: true, git: copy.git, cmdDir: win32.dirname(copy.git) };
170
+ }
171
+ return none;
172
+ }
173
+
174
+ /** `env` with PATH (whatever its spelling: Windows keeps one, under the first name) ending in `folder`, once. */
175
+ function appendToPath(env, folder) {
176
+ const keys = Object.keys(env).filter((name) => name.toUpperCase() === 'PATH');
177
+ const name = keys[0] || 'Path';
178
+ const parts = String(env[name] || '').split(';').filter(Boolean);
179
+ const out = { ...env };
180
+ for (const each of keys) delete out[each];
181
+ out[name] = [...parts.filter((part) => key(part) !== key(folder)), folder].join(';');
182
+ return out;
183
+ }
184
+
185
+ /**
186
+ * The environment of a Claude Code process SynaBun starts: `env` itself, unless SynaBun has to name its Git Bash
187
+ * (its own copy, or one only it finds): then CLAUDE_CODE_GIT_BASH_PATH, and that Git's cmd folder at the end of
188
+ * PATH (Claude Code's own git commands find git there). The system's PATH and registry are never changed.
189
+ */
190
+ export function gitBashChildEnv(env, resolution, { platform = process.platform } = {}) {
191
+ if (platform !== 'win32' || !resolution?.passToClaude || !resolution.path) return env;
192
+ const out = { ...env, [GIT_BASH_ENV]: resolution.path };
193
+ return resolution.cmdDir ? appendToPath(out, resolution.cmdDir) : out;
194
+ }
@@ -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,234 @@
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
+ // On a Windows PC without git, SynaBun's own copy of Git for Windows
61
+ // (lib/git-bash/) is the git of every read: its folder is put at the end of
62
+ // the read's PATH (setGitFallbackFolder, withGitFallback, which the commit
63
+ // routes use too). Nothing changes where the computer has a git of its own.
64
+
65
+ import { execFile, spawnSync } from 'node:child_process';
66
+ import { devNull } from 'node:os';
67
+ import { delimiter } from 'node:path';
68
+
69
+ /** What the environment of a read adds. */
70
+ export const GIT_READ_ENV = Object.freeze({
71
+ GIT_OPTIONAL_LOCKS: '0',
72
+ GIT_NO_LAZY_FETCH: '1',
73
+ GIT_ALLOW_PROTOCOL: '',
74
+ GIT_TERMINAL_PROMPT: '0',
75
+ });
76
+
77
+ /** Commands that take diff options. */
78
+ const DIFF_COMMANDS = new Set(['diff', 'show', 'log']);
79
+ /** Commands that compare the index with the working tree: the file system monitor is asked by these. */
80
+ const INDEX_COMMANDS = new Set(['status', 'diff', 'ls-files']);
81
+
82
+ const PROBE = ['config', '--type=bool', '--get', 'core.fsmonitor'];
83
+ const PROBE_TIMEOUT_MS = 3000;
84
+ export const MONITOR_MEMO_MS = 10_000;
85
+ const MONITOR_MEMO_MAX = 200;
86
+
87
+ let fallbackFolder = () => null;
88
+
89
+ /**
90
+ * Where SynaBun's own git is on a computer that has none (Windows: the cmd folder of SynaBun's copy of Git for
91
+ * Windows, lib/git-bash/manager.js), as a function asked at every start; null while git is the computer's own.
92
+ * The folder goes at the end of PATH, so a git of the computer's, installed meanwhile, still comes first.
93
+ */
94
+ export function setGitFallbackFolder(folder) {
95
+ fallbackFolder = typeof folder === 'function' ? folder : () => null;
96
+ }
97
+
98
+ /** `env` with the fallback folder at the end of its PATH (whatever its spelling), or `env` itself without one. */
99
+ export function withGitFallback(env = process.env) {
100
+ let folder = null;
101
+ try { folder = fallbackFolder(); } catch { folder = null; }
102
+ if (!folder) return env;
103
+ const keys = Object.keys(env).filter((name) => name.toUpperCase() === 'PATH');
104
+ const name = keys[0] || (process.platform === 'win32' ? 'Path' : 'PATH');
105
+ const parts = String(env[name] || '').split(delimiter).filter(Boolean);
106
+ if (parts.some((part) => part.toLowerCase() === folder.toLowerCase())) return env;
107
+ const out = { ...env };
108
+ for (const each of keys) delete out[each];
109
+ out[name] = [...parts, folder].join(delimiter);
110
+ return out;
111
+ }
112
+
113
+ /** The environment a read is run in: `env` and what a read adds to it (and SynaBun's git, where the computer has none). */
114
+ export function gitReadEnv(env = process.env) {
115
+ return withGitFallback({ ...env, ...GIT_READ_ENV });
116
+ }
117
+
118
+ /** Does this command compare the index with the working tree (and so ask the file system monitor)? */
119
+ export function readsIndex(args) {
120
+ return Array.isArray(args) && INDEX_COMMANDS.has(args[0]);
121
+ }
122
+
123
+ /**
124
+ * The arguments of one read: git's own options first, then the command with what switches its external programs
125
+ * off, then the caller's own arguments unchanged.
126
+ * @param {string[]} args the command and its arguments, the command first (['status', '--porcelain'])
127
+ * @param {{ monitor?: boolean }} [o] `monitor`: the repository has git's built-in file system monitor switched on
128
+ */
129
+ export function gitReadArgs(args, { monitor = false } = {}) {
130
+ const [command, ...rest] = Array.isArray(args) ? args : [];
131
+ const own = [
132
+ '--no-pager',
133
+ '-c', `core.fsmonitor=${monitor === true ? 'true' : 'false'}`,
134
+ '-c', `core.hooksPath=${devNull}`,
135
+ '-c', 'log.showSignature=false',
136
+ ];
137
+ if (command === undefined) return own;
138
+ return [...own, command, ...(DIFF_COMMANDS.has(command) ? ['--no-ext-diff', '--no-textconv'] : []), ...rest];
139
+ }
140
+
141
+ /**
142
+ * What is remembered of the repositories that were asked for their monitor: per folder, for a few seconds, for at
143
+ * most so many folders. Whatever is remembered, a read is given a boolean on its command line: a stale answer
144
+ * can keep git's own monitor on or off for a moment, it can never let a program through.
145
+ */
146
+ export function createMonitorMemo({ ttlMs = MONITOR_MEMO_MS, max = MONITOR_MEMO_MAX, now = Date.now } = {}) {
147
+ const known = new Map(); // folder → { on, at }, the one asked longest ago first
148
+ return {
149
+ get(cwd) {
150
+ const entry = known.get(cwd);
151
+ if (!entry) return undefined;
152
+ if (now() - entry.at >= ttlMs) { known.delete(cwd); return undefined; }
153
+ return entry.on;
154
+ },
155
+ set(cwd, on) {
156
+ known.delete(cwd);
157
+ known.set(cwd, { on: on === true, at: now() });
158
+ while (known.size > max) known.delete(known.keys().next().value);
159
+ return on === true;
160
+ },
161
+ size: () => known.size,
162
+ };
163
+ }
164
+
165
+ const memo = createMonitorMemo();
166
+ // The question itself: no value is given for core.fsmonitor here (the command line's would be the answer), and
167
+ // `git config` reads no index, so it asks no monitor.
168
+ const probeArgs = () => ['--no-pager', '-c', `core.hooksPath=${devNull}`, ...PROBE];
169
+ // "true" and nothing else: unset (exit 1), a program's path (exit 128: not a boolean), false, no git, a timeout.
170
+ const saysOn = (status, stdout) => status === 0 && String(stdout || '').trim() === 'true';
171
+
172
+ /**
173
+ * Is git's built-in file system monitor switched on for the repository `cwd` is in? False for every other answer:
174
+ * not set, a program's path, no repository, no git.
175
+ * @param {string} cwd
176
+ * @param {{ binary?: string, env?: object, remembered?: ReturnType<typeof createMonitorMemo> }} [o]
177
+ * @returns {Promise<boolean>}
178
+ */
179
+ export function builtInMonitor(cwd, { binary = 'git', env = process.env, remembered = memo } = {}) {
180
+ const known = remembered.get(cwd);
181
+ if (known !== undefined) return Promise.resolve(known);
182
+ return new Promise((done) => {
183
+ try {
184
+ execFile(binary, probeArgs(), { cwd, env: gitReadEnv(env), timeout: PROBE_TIMEOUT_MS, windowsHide: true, shell: false, encoding: 'utf8' }, (error, stdout) => {
185
+ done(remembered.set(cwd, saysOn(error ? (typeof error.code === 'number' ? error.code : -1) : 0, stdout)));
186
+ });
187
+ } catch {
188
+ done(remembered.set(cwd, false));
189
+ }
190
+ });
191
+ }
192
+
193
+ /** builtInMonitor, for a caller that runs git synchronously. */
194
+ export function builtInMonitorSync(cwd, { binary = 'git', env = process.env, remembered = memo, spawn = spawnSync } = {}) {
195
+ const known = remembered.get(cwd);
196
+ if (known !== undefined) return known;
197
+ let on = false;
198
+ try {
199
+ const result = spawn(binary, probeArgs(), { cwd, env: gitReadEnv(env), timeout: PROBE_TIMEOUT_MS, windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] });
200
+ on = !result.error && saysOn(result.status, result.stdout);
201
+ } catch { on = false; }
202
+ return remembered.set(cwd, on);
203
+ }
204
+
205
+ /**
206
+ * How to run one read in `cwd`: `{ args, env }` for the program `git`. The repository is asked for its monitor
207
+ * only for a command that compares the index with the working tree.
208
+ * @param {string} cwd
209
+ * @param {string[]} args the command first
210
+ * @param {{ binary?: string, env?: object, remembered?: object }} [o]
211
+ * @returns {Promise<{ args: string[], env: object }>}
212
+ */
213
+ export async function gitRead(cwd, args, o = {}) {
214
+ const monitor = readsIndex(args) ? await builtInMonitor(cwd, o) : false;
215
+ return { args: gitReadArgs(args, { monitor }), env: gitReadEnv(o.env) };
216
+ }
217
+
218
+ /** gitRead, for a caller that runs git synchronously. */
219
+ export function gitReadSync(cwd, args, o = {}) {
220
+ const monitor = readsIndex(args) ? builtInMonitorSync(cwd, o) : false;
221
+ return { args: gitReadArgs(args, { monitor }), env: gitReadEnv(o.env) };
222
+ }
223
+
224
+ /**
225
+ * One read, run now, for a caller that has no runner of its own: what spawnSync answers.
226
+ * @param {string} cwd
227
+ * @param {string[]} args the command first
228
+ * @param {object} [options] spawnSync's (`encoding`, `timeout`, `maxBuffer`, …); `cwd` and what a read adds to
229
+ * the environment are not the caller's to change
230
+ */
231
+ export function runGitReadSync(cwd, args, options = {}) {
232
+ const read = gitReadSync(cwd, args, { env: options.env });
233
+ return spawnSync('git', read.args, { windowsHide: true, ...options, cwd, env: read.env });
234
+ }
@@ -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
+ }