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,257 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Code editor: what a file is on disk
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // CodeMirror holds every line break as "\n". These helpers read what the file
6
+ // used (line ending, byte-order mark, indentation) when it is opened, and write
7
+ // the document back the same way when it is saved. No DOM and no imports: loaded
8
+ // by `node --test` as it is.
9
+
10
+ export const BOM = '\uFEFF';
11
+ const REPLACEMENT = '\uFFFD';
12
+
13
+ /**
14
+ * Take a leading byte-order mark off `content`.
15
+ * @param {string} content what the server sent
16
+ * @param {boolean} [reported] the server's `bom` field. A server that sends it has already taken the mark off;
17
+ * one that does not (it predates the field) sends the mark as the first character.
18
+ * @returns {{ text: string, bom: boolean }}
19
+ */
20
+ export function splitBom(content, reported) {
21
+ const text = String(content ?? '');
22
+ if (typeof reported === 'boolean') return { text, bom: reported };
23
+ return text.charCodeAt(0) === 0xFEFF ? { text: text.slice(1), bom: true } : { text, bom: false };
24
+ }
25
+
26
+ /**
27
+ * The line ending a text uses. `eol` is the style it is saved in: the more common of LF and CRLF (LF when
28
+ * neither leads). `mixed` says the text holds more than one kind of line break, a lone CR included, so a save
29
+ * rewrites some of them.
30
+ * @returns {{ eol: 'LF' | 'CRLF', mixed: boolean, counts: { lf: number, crlf: number, cr: number } }}
31
+ */
32
+ export function detectLineEnding(text) {
33
+ const s = String(text ?? '');
34
+ let lf = 0; let crlf = 0; let cr = 0;
35
+ for (let i = s.indexOf('\n'); i !== -1; i = s.indexOf('\n', i + 1)) {
36
+ if (i > 0 && s.charCodeAt(i - 1) === 13) crlf++;
37
+ else lf++;
38
+ }
39
+ for (let i = s.indexOf('\r'); i !== -1; i = s.indexOf('\r', i + 1)) {
40
+ if (s.charCodeAt(i + 1) !== 10) cr++;
41
+ }
42
+ const kinds = (lf ? 1 : 0) + (crlf ? 1 : 0) + (cr ? 1 : 0);
43
+ return { eol: crlf > lf ? 'CRLF' : 'LF', mixed: kinds > 1, counts: { lf, crlf, cr } };
44
+ }
45
+
46
+ const SPACE_STEPS = [2, 4, 8];
47
+ /** The width a tab is drawn at when nothing says otherwise. */
48
+ export const DEFAULT_TAB_WIDTH = 4;
49
+ /** What Tab inserts in a file that shows no indentation of its own. */
50
+ export const DEFAULT_SPACE_STEP = 2;
51
+
52
+ /**
53
+ * How a text is indented, read from its leading whitespace: with tabs, or with the step of 2, 4 or 8 spaces
54
+ * that most often separates one line from the line before it.
55
+ * @param {string} text
56
+ * @param {{ maxLines?: number }} [o] lines read from the start (a large file is not read whole)
57
+ * @returns {{ kind: 'spaces' | 'tabs', size: number, detected: boolean }} `size` is the space step, or the
58
+ * default tab width for a file indented with tabs; `detected` is false when the text gave no evidence
59
+ */
60
+ export function detectIndentation(text, { maxLines = 5000 } = {}) {
61
+ const s = String(text ?? '');
62
+ let tabLines = 0; let spaceLines = 0;
63
+ const steps = new Map(SPACE_STEPS.map((n) => [n, 0]));
64
+ let previous = 0; // leading spaces of the last line that had content and was not indented with a tab
65
+ let at = 0;
66
+ for (let n = 0; n < maxLines && at <= s.length; n++) {
67
+ let end = s.indexOf('\n', at);
68
+ if (end === -1) end = s.length;
69
+ let i = at;
70
+ let spaces = 0; let tab = false;
71
+ for (; i < end; i++) {
72
+ const c = s.charCodeAt(i);
73
+ if (c === 32) spaces++;
74
+ else if (c === 9) tab = true;
75
+ else break;
76
+ }
77
+ const blank = i >= end || s.charCodeAt(i) === 13;
78
+ if (!blank) {
79
+ if (tab) tabLines++;
80
+ else {
81
+ if (spaces) spaceLines++;
82
+ const step = Math.abs(spaces - previous);
83
+ if (steps.has(step)) steps.set(step, steps.get(step) + 1);
84
+ previous = spaces;
85
+ }
86
+ }
87
+ at = end + 1;
88
+ }
89
+ if (tabLines > spaceLines) return { kind: 'tabs', size: DEFAULT_TAB_WIDTH, detected: true };
90
+ let best = 0; let bestCount = 0;
91
+ for (const n of SPACE_STEPS) {
92
+ if (steps.get(n) > bestCount) { best = n; bestCount = steps.get(n); }
93
+ }
94
+ if (!best) return { kind: 'spaces', size: DEFAULT_SPACE_STEP, detected: false };
95
+ return { kind: 'spaces', size: best, detected: true };
96
+ }
97
+
98
+ /**
99
+ * The indentation the editor uses for a file: what was detected, unless a preference says otherwise.
100
+ * @param {{ kind: 'spaces' | 'tabs', size: number }} detected from detectIndentation()
101
+ * @param {{ tabSize?: 'auto' | number, indentWith?: 'auto' | 'spaces' | 'tabs' }} [editing] the `editing` preferences
102
+ * @returns {{ kind: 'spaces' | 'tabs', tabSize: number, unit: string }} `unit` is what one level of indentation inserts
103
+ */
104
+ export function resolveIndentation(detected, editing = {}) {
105
+ const found = detected && (detected.kind === 'tabs' || detected.kind === 'spaces')
106
+ ? detected : { kind: 'spaces', size: DEFAULT_SPACE_STEP };
107
+ const kind = editing.indentWith === 'tabs' || editing.indentWith === 'spaces' ? editing.indentWith : found.kind;
108
+ const wanted = Number(editing.tabSize);
109
+ let tabSize;
110
+ if (editing.tabSize !== 'auto' && Number.isInteger(wanted) && wanted >= 1 && wanted <= 16) tabSize = wanted;
111
+ else if (kind === found.kind) tabSize = found.size;
112
+ else tabSize = kind === 'tabs' ? DEFAULT_TAB_WIDTH : DEFAULT_SPACE_STEP;
113
+ return { kind, tabSize, unit: kind === 'tabs' ? '\t' : ' '.repeat(tabSize) };
114
+ }
115
+
116
+ /**
117
+ * The indentation a file uses when the user picked one in the status bar: that choice, over what was detected
118
+ * and over the preferences. Without a choice it is resolveIndentation().
119
+ * @param {{ kind: 'spaces' | 'tabs', size: number }} detected from detectIndentation()
120
+ * @param {{ tabSize?: 'auto' | number, indentWith?: string }} [editing] the `editing` preferences
121
+ * @param {{ kind: 'spaces', size: number } | { kind: 'tabs' } | null} [choice]
122
+ * @returns {{ kind: 'spaces' | 'tabs', tabSize: number, unit: string }}
123
+ */
124
+ export function chooseIndentation(detected, editing = {}, choice = null) {
125
+ if (!choice) return resolveIndentation(detected, editing);
126
+ if (choice.kind === 'tabs') {
127
+ // How wide a tab is drawn is not part of the choice: the preference, else what the file showed.
128
+ const wanted = Number(editing.tabSize);
129
+ const tabSize = editing.tabSize !== 'auto' && Number.isInteger(wanted) && wanted >= 1 && wanted <= 16
130
+ ? wanted : (detected?.kind === 'tabs' ? detected.size : DEFAULT_TAB_WIDTH);
131
+ return { kind: 'tabs', tabSize, unit: '\t' };
132
+ }
133
+ const size = SPACE_STEPS.includes(choice.size) ? choice.size : DEFAULT_SPACE_STEP;
134
+ return { kind: 'spaces', tabSize: size, unit: ' '.repeat(size) };
135
+ }
136
+
137
+ /**
138
+ * The one change that turns `before` into `after`: what lies between the start and the end the two share.
139
+ * A file reloaded from disk is applied this way, so the caret, the view and the undo history outside the
140
+ * change stay where they are.
141
+ * @returns {{ from: number, to: number, insert: string } | null} null when the texts are the same
142
+ */
143
+ export function minimalChange(before, after) {
144
+ const a = String(before ?? '');
145
+ const b = String(after ?? '');
146
+ if (a === b) return null;
147
+ const shortest = Math.min(a.length, b.length);
148
+ let start = 0;
149
+ while (start < shortest && a.charCodeAt(start) === b.charCodeAt(start)) start++;
150
+ let end = 0;
151
+ while (end < shortest - start && a.charCodeAt(a.length - 1 - end) === b.charCodeAt(b.length - 1 - end)) end++;
152
+ return { from: start, to: a.length - end, insert: b.slice(start, b.length - end) };
153
+ }
154
+
155
+ /**
156
+ * The text a save writes: `text` (line breaks as "\n", the way CodeMirror holds it) in the file's line ending,
157
+ * behind its byte-order mark when it had one.
158
+ */
159
+ export function serializeDocument(text, { eol = 'LF', bom = false } = {}) {
160
+ const body = eol === 'CRLF' ? String(text ?? '').replace(/\n/g, '\r\n') : String(text ?? '');
161
+ return bom ? BOM + body : body;
162
+ }
163
+
164
+ /**
165
+ * Whether `content` lost bytes when it was decoded. A server that predates the encoding check decodes a file
166
+ * that is not UTF-8 anyway, with U+FFFD in place of each invalid sequence: the text then no longer encodes to
167
+ * the file's size, and saving it would write the damage to disk.
168
+ * @param {string} content the text as received, byte-order mark included
169
+ * @param {number} sizeOnDisk the size the server reported, in bytes
170
+ */
171
+ export function looksLossyDecoded(content, sizeOnDisk) {
172
+ const s = String(content ?? '');
173
+ if (!Number.isFinite(sizeOnDisk) || !s.includes(REPLACEMENT)) return false;
174
+ return new TextEncoder().encode(s).length !== sizeOnDisk;
175
+ }
176
+
177
+ /**
178
+ * The document position of a 1-based line and column, held inside the document: a line past the end is the
179
+ * last line, a column past the end of its line is the end of that line.
180
+ * @param {{ lines: number, line: (n: number) => { from: number, length: number } }} doc a CodeMirror Text
181
+ * @returns {{ pos: number, line: number, column: number }}
182
+ */
183
+ export function positionAt(doc, lineNumber, columnNumber = 1) {
184
+ const line = Math.min(Math.max(1, Math.floor(Number(lineNumber)) || 1), Math.max(1, doc.lines));
185
+ const info = doc.line(line);
186
+ const column = Math.min(Math.max(1, Math.floor(Number(columnNumber)) || 1), info.length + 1);
187
+ return { pos: info.from + column - 1, line, column };
188
+ }
189
+
190
+ /**
191
+ * What a "go to line" field asks for: `12`, `12:8` or `12,8`.
192
+ * @returns {{ line: number, column: number } | null} null when it is not a line number
193
+ */
194
+ export function parseLineTarget(input) {
195
+ const m = /^\s*(\d{1,9})\s*(?:[:,]\s*(\d{1,9})\s*)?$/.exec(String(input ?? ''));
196
+ if (!m) return null;
197
+ const line = Number(m[1]);
198
+ if (line < 1) return null;
199
+ return { line, column: m[2] ? Math.max(1, Number(m[2])) : 1 };
200
+ }
201
+
202
+ /** Above this size the minimap, the git markers and the problem check are off for a document. */
203
+ export const LARGE_DOCUMENT_BYTES = 2 * 1024 * 1024;
204
+
205
+ /**
206
+ * Whether a document is too large for the minimap, the git markers and the problem check.
207
+ * @param {{ size?: number, length?: number }} o `size`: the file on disk, in bytes; `length`: the text in the editor
208
+ */
209
+ export function isLargeDocument({ size = 0, length = 0 } = {}) {
210
+ return Math.max(Number(size) || 0, Number(length) || 0) > LARGE_DOCUMENT_BYTES;
211
+ }
212
+
213
+ /**
214
+ * What a save changes in the text before it is written, for the two on-save preferences.
215
+ * trimTrailingWhitespace spaces and tabs at the end of a line go
216
+ * insertFinalNewline the text ends with exactly one line break: a missing one is added, empty lines after
217
+ * the last line with text go (with trimming on, a line of only spaces is an empty line)
218
+ * An empty text stays empty. With both off nothing changes.
219
+ * @param {string} text line breaks as "\n", the way CodeMirror holds it
220
+ * @param {{ trimTrailingWhitespace?: boolean, insertFinalNewline?: boolean }} [o]
221
+ * @returns {{ from: number, to: number, insert: string }[]} changes in document order that do not touch each other
222
+ */
223
+ export function saveFixes(text, { trimTrailingWhitespace = false, insertFinalNewline = false } = {}) {
224
+ const s = String(text ?? '');
225
+ const changes = [];
226
+ if (!s || (!trimTrailingWhitespace && !insertFinalNewline)) return changes;
227
+ let contentEnd = s.length;
228
+ let ending = null;
229
+ if (insertFinalNewline) {
230
+ const blank = trimTrailingWhitespace ? (c) => c === 10 || c === 32 || c === 9 : (c) => c === 10;
231
+ while (contentEnd > 0 && blank(s.charCodeAt(contentEnd - 1))) contentEnd--;
232
+ const wanted = contentEnd === 0 ? '' : '\n';
233
+ if (s.slice(contentEnd) !== wanted) ending = { from: contentEnd, to: s.length, insert: wanted };
234
+ else contentEnd = s.length; // the end is as it should be: only lines are trimmed
235
+ }
236
+ if (trimTrailingWhitespace) {
237
+ const trailing = /[ \t]+$/gm;
238
+ for (let m = trailing.exec(s); m; m = trailing.exec(s)) {
239
+ if (ending && m.index >= ending.from) break; // what follows is replaced with the ending
240
+ changes.push({ from: m.index, to: m.index + m[0].length, insert: '' });
241
+ }
242
+ }
243
+ if (ending) changes.push(ending);
244
+ return changes;
245
+ }
246
+
247
+ /** `text` with the changes of saveFixes() (or any changes in document order) applied. */
248
+ export function applyChanges(text, changes) {
249
+ const s = String(text ?? '');
250
+ let out = '';
251
+ let at = 0;
252
+ for (const change of changes) {
253
+ out += s.slice(at, change.from) + change.insert;
254
+ at = change.to;
255
+ }
256
+ return out + s.slice(at);
257
+ }
@@ -0,0 +1,131 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Code editor: which lines differ from the last commit
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // The decisions behind the git markers (editor-git.js draws them): what the
6
+ // server said about a file's committed version, which lines of the document
7
+ // differ from it, and what the gutter shows for them. A line comparison by the
8
+ // O(ND) method, bounded: beyond its limit the stretch that differs is marked as
9
+ // one change instead of being taken apart. No DOM and no imports: loaded by
10
+ // `node --test` as it is.
11
+
12
+ /** Edits a comparison takes apart; a stretch that needs more is one "modified" block. */
13
+ export const DIFF_MAX_EDITS = 1500;
14
+
15
+ /**
16
+ * What GET /api/file-head answered.
17
+ * @param {{ status?: number, body?: object | null }} response
18
+ * @returns {{ tracked: boolean, text: string | null }} `text`: the committed version, line breaks as "\n"; null
19
+ * when git does not know the file, when what it holds is not text, or when the server could not say
20
+ */
21
+ export function readHeadResponse({ status = 0, body = null } = {}) {
22
+ if (status !== 200 || body?.ok !== true || body.tracked !== true) return { tracked: false, text: null };
23
+ if (typeof body.content !== 'string') return { tracked: true, text: null };
24
+ return { tracked: true, text: body.content.replace(/\r\n?/g, '\n') };
25
+ }
26
+
27
+ /**
28
+ * The stretches where two lists of lines differ.
29
+ * @param {string[]} before the committed lines
30
+ * @param {string[]} after the document's lines
31
+ * @param {{ maxEdits?: number }} [o]
32
+ * @returns {{ aFrom: number, aTo: number, bFrom: number, bTo: number }[]} in order; each replaces lines
33
+ * `aFrom`..`aTo` of `before` with lines `bFrom`..`bTo` of `after` (0-based, the end not included)
34
+ */
35
+ export function diffLines(before, after, { maxEdits = DIFF_MAX_EDITS } = {}) {
36
+ const a = before; const b = after;
37
+ let start = 0;
38
+ const shortest = Math.min(a.length, b.length);
39
+ while (start < shortest && a[start] === b[start]) start++;
40
+ let endA = a.length; let endB = b.length;
41
+ while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) { endA--; endB--; }
42
+ if (start === endA && start === endB) return [];
43
+ const whole = [{ aFrom: start, aTo: endA, bFrom: start, bTo: endB }];
44
+ if (start === endA || start === endB) return whole;
45
+
46
+ // The middle, each distinct line as a number: comparing lines is then comparing numbers.
47
+ const ids = new Map();
48
+ const intern = (line) => { let id = ids.get(line); if (id === undefined) { id = ids.size; ids.set(line, id); } return id; };
49
+ const n = endA - start; const m = endB - start;
50
+ const x0 = new Int32Array(n); const y0 = new Int32Array(m);
51
+ for (let i = 0; i < n; i++) x0[i] = intern(a[start + i]);
52
+ for (let i = 0; i < m; i++) y0[i] = intern(b[start + i]);
53
+
54
+ const max = Math.min(n + m, Math.max(1, maxEdits));
55
+ const offset = max + 1;
56
+ const v = new Int32Array(2 * max + 3);
57
+ const trace = [];
58
+ let found = -1;
59
+ for (let d = 0; d <= max && found < 0; d++) {
60
+ trace.push(v.slice(offset - d, offset + d + 1)); // the furthest points before this round, for the way back
61
+ for (let k = -d; k <= d; k += 2) {
62
+ let x = (k === -d || (k !== d && v[offset + k - 1] < v[offset + k + 1])) ? v[offset + k + 1] : v[offset + k - 1] + 1;
63
+ let y = x - k;
64
+ while (x < n && y < m && x0[x] === y0[y]) { x++; y++; }
65
+ v[offset + k] = x;
66
+ if (x >= n && y >= m) { found = d; break; }
67
+ }
68
+ }
69
+ if (found < 0) return whole;
70
+
71
+ // Back from the end: the lines both have are what separates one stretch from the next.
72
+ const hunks = [];
73
+ let open = null;
74
+ const edit = (x, y, dx, dy) => {
75
+ if (open && open.aFrom === x + dx && open.bFrom === y + dy) { open.aFrom = x; open.bFrom = y; return; }
76
+ open = { aFrom: x, aTo: x + dx, bFrom: y, bTo: y + dy };
77
+ hunks.push(open);
78
+ };
79
+ let x = n; let y = m;
80
+ for (let d = found; d > 0; d--) {
81
+ const prev = trace[d];
82
+ const at = (k) => prev[k + d];
83
+ const k = x - y;
84
+ const down = k === -d || (k !== d && at(k - 1) < at(k + 1));
85
+ const prevK = down ? k + 1 : k - 1;
86
+ const prevX = at(prevK);
87
+ const prevY = prevX - prevK;
88
+ if (down) edit(prevX, prevY, 0, 1); // a line of `after` that `before` does not have
89
+ else edit(prevX, prevY, 1, 0); // a line of `before` that is gone
90
+ x = prevX; y = prevY;
91
+ }
92
+ return hunks.reverse().map((hunk) => ({ aFrom: hunk.aFrom + start, aTo: hunk.aTo + start, bFrom: hunk.bFrom + start, bTo: hunk.bTo + start }));
93
+ }
94
+
95
+ /**
96
+ * What the gutter shows for the stretches diffLines() found.
97
+ * @param {{ aFrom: number, aTo: number, bFrom: number, bTo: number }[]} hunks
98
+ * @param {number} lineCount lines in the document
99
+ * @returns {{ from: number, to: number, kind: 'added' | 'modified' | 'deleted', after?: boolean }[]} `from`..`to`:
100
+ * lines of the document, 1-based, both included. `deleted` marks one line: the one committed lines were removed
101
+ * in front of, or (with `after`) the last line when they were removed behind it.
102
+ */
103
+ export function lineMarks(hunks, lineCount) {
104
+ const marks = [];
105
+ const last = Math.max(1, lineCount);
106
+ for (const hunk of hunks) {
107
+ if (hunk.bTo > hunk.bFrom) {
108
+ marks.push({ from: hunk.bFrom + 1, to: hunk.bTo, kind: hunk.aTo > hunk.aFrom ? 'modified' : 'added' });
109
+ } else if (hunk.aTo > hunk.aFrom) {
110
+ const after = hunk.bFrom >= last;
111
+ marks.push({ from: after ? last : hunk.bFrom + 1, to: after ? last : hunk.bFrom + 1, kind: 'deleted', ...(after ? { after: true } : {}) });
112
+ }
113
+ }
114
+ return marks;
115
+ }
116
+
117
+ /**
118
+ * The marks for a document against its committed version.
119
+ * @param {string[]} headLines the committed lines
120
+ * @param {string[]} lines the document's lines
121
+ */
122
+ export function gitMarks(headLines, lines, options) {
123
+ return lineMarks(diffLines(headLines, lines, options), lines.length);
124
+ }
125
+
126
+ /** How many lines each kind of mark covers: `{ added, modified, deleted }` (`deleted` counts places, not lines). */
127
+ export function countMarks(marks) {
128
+ const count = { added: 0, modified: 0, deleted: 0 };
129
+ for (const mark of marks) count[mark.kind] += mark.kind === 'deleted' ? 1 : mark.to - mark.from + 1;
130
+ return count;
131
+ }
@@ -0,0 +1,95 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Code editor: git markers
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // A thin column next to the fold arrows that says which lines differ from the
6
+ // last commit: added, modified, and where committed lines were removed. Which
7
+ // lines those are is decided in editor-git-logic.js; the panel fetches the
8
+ // committed text (GET /api/file-head), compares after edits and saves, and
9
+ // hands the result here as an effect. The marks are part of the document's
10
+ // state, so each tab keeps its own, and they move with the text until the next
11
+ // comparison. The Diff view of the file against the last commit is the inline
12
+ // comparison of editor-compare.js, fed the committed text.
13
+ // Takes the vendored bundle as an argument: no imports.
14
+
15
+ /** A comparison runs this long after the last edit. */
16
+ export const GIT_REFRESH_MS = 300;
17
+
18
+ /**
19
+ * @param {object} cm the vendored CodeMirror bundle (editor-loader.js)
20
+ */
21
+ export function editorGit(cm) {
22
+ const { GutterMarker, RangeSet, RangeSetBuilder, StateEffect, StateField, gutter } = cm;
23
+
24
+ class ChangeMarker extends GutterMarker {
25
+ constructor(kind, after = false) {
26
+ super();
27
+ this.kind = kind;
28
+ this.after = after;
29
+ this.elementClass = `cm-gitLine cm-gitLine-${kind}${after ? ' cm-gitLine-after' : ''}`;
30
+ }
31
+ eq(other) { return other.kind === this.kind && other.after === this.after; }
32
+ }
33
+ const markers = {
34
+ added: new ChangeMarker('added'),
35
+ modified: new ChangeMarker('modified'),
36
+ deleted: new ChangeMarker('deleted'),
37
+ deletedAfter: new ChangeMarker('deleted', true),
38
+ };
39
+
40
+ const setMarks = StateEffect.define();
41
+
42
+ function build(doc, marks) {
43
+ const builder = new RangeSetBuilder();
44
+ for (const mark of marks) {
45
+ const marker = mark.kind === 'deleted' ? (mark.after ? markers.deletedAfter : markers.deleted) : markers[mark.kind];
46
+ if (!marker) continue;
47
+ const last = Math.min(mark.to, doc.lines);
48
+ for (let n = Math.max(1, mark.from); n <= last; n++) {
49
+ const at = doc.line(n).from;
50
+ builder.add(at, at, marker);
51
+ }
52
+ }
53
+ return builder.finish();
54
+ }
55
+
56
+ const field = StateField.define({
57
+ create: () => RangeSet.empty,
58
+ update(value, tr) {
59
+ for (const effect of tr.effects) if (effect.is(setMarks)) return build(tr.state.doc, effect.value);
60
+ return tr.docChanged ? value.map(tr.changes) : value;
61
+ },
62
+ });
63
+
64
+ const column = gutter({ class: 'cm-gitGutter', markers: (view) => view.state.field(field) });
65
+
66
+ return {
67
+ /** The column and the marks it shows (none until an effect brings them). For the view's git compartment. */
68
+ extension: [field, column],
69
+
70
+ /**
71
+ * Show these marks.
72
+ * @param {{ from: number, to: number, kind: 'added' | 'modified' | 'deleted', after?: boolean }[]} marks
73
+ * lines of the document (editor-git-logic.js: gitMarks)
74
+ */
75
+ marks: (marks) => setMarks.of(marks || []),
76
+
77
+ /** Whether a state has the column. */
78
+ has: (state) => state.field(field, false) !== undefined,
79
+
80
+ /** The kinds of the marks a state shows, by line number: for a test, or a count. */
81
+ lines(state) {
82
+ const found = new Map();
83
+ const set = state.field(field, false);
84
+ if (!set) return found;
85
+ for (let cursor = set.iter(); cursor.value; cursor.next()) found.set(state.doc.lineAt(cursor.from).number, cursor.value.kind);
86
+ return found;
87
+ },
88
+
89
+ /** Call `use(position, kind)` for each mark between two document positions (the minimap draws them). */
90
+ between(state, from, to, use) {
91
+ const set = state.field(field, false);
92
+ if (set) set.between(from, to, (at, _end, marker) => { use(at, marker.kind); });
93
+ },
94
+ };
95
+ }
@@ -0,0 +1,156 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Code editor: which language a file is
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // File name → the language it is highlighted as, and the word the status bar
6
+ // shows. A language is named the way @codemirror/language-data names it; the
7
+ // view looks the name up in the vendored bundle and loads its chunk. This table
8
+ // only holds what language-data does not know by itself: dotfiles, lockfiles
9
+ // and a few extensions. For every other name the view asks language-data.
10
+ // No DOM and no imports: loaded by `node --test` as it is.
11
+
12
+ const PROPERTIES = 'Properties files';
13
+
14
+ // Exact file names, lower case.
15
+ const BY_NAME = {
16
+ // shell start-up files
17
+ '.bashrc': { language: 'Shell' },
18
+ '.bash_profile': { language: 'Shell' },
19
+ '.bash_aliases': { language: 'Shell' },
20
+ '.bash_logout': { language: 'Shell' },
21
+ '.profile': { language: 'Shell' },
22
+ '.zshrc': { language: 'Shell' },
23
+ '.zshenv': { language: 'Shell' },
24
+ '.zprofile': { language: 'Shell' },
25
+ '.zlogin': { language: 'Shell' },
26
+ '.envrc': { language: 'Shell' },
27
+ // key = value files
28
+ '.npmrc': { language: PROPERTIES, label: 'Properties' },
29
+ '.yarnrc': { language: PROPERTIES, label: 'Properties' },
30
+ '.editorconfig': { language: PROPERTIES, label: 'EditorConfig' },
31
+ '.gitconfig': { language: PROPERTIES, label: 'Git Config' },
32
+ '.gitmodules': { language: PROPERTIES, label: 'Git Config' },
33
+ '.netrc': { language: null, label: 'Plain Text' },
34
+ // JSON under another name
35
+ '.babelrc': { language: 'JSON' },
36
+ '.eslintrc': { language: 'JSON' },
37
+ '.prettierrc': { language: 'JSON' },
38
+ '.stylelintrc': { language: 'JSON' },
39
+ '.swcrc': { language: 'JSON' },
40
+ '.watchmanconfig': { language: 'JSON' },
41
+ // lockfiles
42
+ 'package-lock.json': { language: 'JSON' },
43
+ 'npm-shrinkwrap.json': { language: 'JSON' },
44
+ 'composer.lock': { language: 'JSON' },
45
+ 'pipfile.lock': { language: 'JSON' },
46
+ 'flake.lock': { language: 'JSON' },
47
+ 'bun.lock': { language: 'JSON' },
48
+ 'deno.lock': { language: 'JSON' },
49
+ 'yarn.lock': { language: 'YAML' },
50
+ 'pnpm-lock.yaml': { language: 'YAML' },
51
+ 'cargo.lock': { language: 'TOML' },
52
+ 'poetry.lock': { language: 'TOML' },
53
+ 'uv.lock': { language: 'TOML' },
54
+ 'pdm.lock': { language: 'TOML' },
55
+ 'gemfile.lock': { language: null, label: 'Plain Text' },
56
+ 'go.sum': { language: null, label: 'Plain Text' },
57
+ // build files
58
+ 'makefile': { language: 'Shell', label: 'Makefile' },
59
+ 'gnumakefile': { language: 'Shell', label: 'Makefile' },
60
+ 'containerfile': { language: 'Dockerfile' },
61
+ 'gemfile': { language: 'Ruby' },
62
+ 'rakefile': { language: 'Ruby' },
63
+ 'pipfile': { language: 'TOML' },
64
+ };
65
+
66
+ // File name patterns, first match wins.
67
+ const BY_PATTERN = [
68
+ { test: /^\.env(\.|$)/i, language: PROPERTIES, label: 'ENV' },
69
+ { test: /\.env$/i, language: PROPERTIES, label: 'ENV' },
70
+ { test: /^\.[\w-]*ignore$/i, language: 'Shell', label: 'Ignore' },
71
+ { test: /^dockerfile(\.|$)/i, language: 'Dockerfile' },
72
+ { test: /\.dockerfile$/i, language: 'Dockerfile' },
73
+ { test: /^\.(eslintrc|prettierrc|stylelintrc)\.json[c5]?$/i, language: 'JSON' },
74
+ ];
75
+
76
+ // Extensions language-data has no entry for, lower case.
77
+ const BY_EXTENSION = {
78
+ jsonc: { language: 'JSON' },
79
+ json5: { language: 'JSON' },
80
+ jsonl: { language: 'JSON' },
81
+ ndjson: { language: 'JSON' },
82
+ webmanifest: { language: 'JSON' },
83
+ mdx: { language: 'Markdown' },
84
+ mdc: { language: 'Markdown' },
85
+ svelte: { language: 'HTML', label: 'Svelte' },
86
+ astro: { language: 'HTML', label: 'Astro' },
87
+ xhtml: { language: 'HTML' },
88
+ plist: { language: 'XML' },
89
+ zsh: { language: 'Shell' },
90
+ fish: { language: 'Shell' },
91
+ command: { language: 'Shell' },
92
+ cfg: { language: PROPERTIES, label: 'Properties' },
93
+ conf: { language: PROPERTIES, label: 'Properties' },
94
+ ini: { language: PROPERTIES, label: 'INI' },
95
+ properties: { language: PROPERTIES, label: 'Properties' },
96
+ diff: { language: 'diff', label: 'Diff' },
97
+ patch: { language: 'diff', label: 'Diff' },
98
+ };
99
+
100
+ /** The last path segment of `path`, whichever separator it uses. */
101
+ export function baseName(path) {
102
+ const s = String(path ?? '');
103
+ const cut = Math.max(s.lastIndexOf('/'), s.lastIndexOf('\\'));
104
+ return cut === -1 ? s : s.slice(cut + 1);
105
+ }
106
+
107
+ /** The extension of a file name, lower case, without the dot. '' for a name that has none (a dotfile has none). */
108
+ export function extensionOf(fileName) {
109
+ const name = baseName(fileName);
110
+ const dot = name.lastIndexOf('.');
111
+ return dot > 0 ? name.slice(dot + 1).toLowerCase() : '';
112
+ }
113
+
114
+ /**
115
+ * What this table says about a file name.
116
+ * @returns {{ language: string | null, label: string | null, known: boolean }}
117
+ * `known: false` → the table has no entry: ask language-data by file name.
118
+ * `language: null` with `known: true` → plain text, on purpose.
119
+ * `label: null` → show the language's own name.
120
+ */
121
+ export function resolveLanguage(fileName) {
122
+ const name = baseName(fileName);
123
+ const lower = name.toLowerCase();
124
+ const hit = BY_NAME[lower]
125
+ || BY_PATTERN.find((p) => p.test.test(name))
126
+ || BY_EXTENSION[extensionOf(name)];
127
+ if (!hit) return { language: null, label: null, known: false };
128
+ return { language: hit.language ?? null, label: hit.label ?? null, known: true };
129
+ }
130
+
131
+ /**
132
+ * The word the status bar shows for a file.
133
+ * @param {string} fileName
134
+ * @param {string | null} languageName the name of the language that was found for it (language-data's), if any
135
+ * @returns {string | null} null → plain text: the caller shows its own translated words
136
+ */
137
+ export function languageLabel(fileName, languageName) {
138
+ const { label, known, language } = resolveLanguage(fileName);
139
+ if (label) return label === 'Plain Text' ? null : label;
140
+ if (languageName) return languageName;
141
+ if (known && language) return language;
142
+ const ext = extensionOf(fileName);
143
+ return ext ? ext.toUpperCase() : null;
144
+ }
145
+
146
+ /** Whether a file is Markdown (it gets the Raw / Preview switch). */
147
+ export function isMarkdownFile(fileName) {
148
+ const ext = extensionOf(fileName);
149
+ return ext === 'md' || ext === 'markdown' || ext === 'mkd' || ext === 'mdx' || ext === 'mdc';
150
+ }
151
+
152
+ /** Every language name this table can ask for (tests check each one exists in the bundle). */
153
+ export function namedLanguages() {
154
+ const all = [...Object.values(BY_NAME), ...BY_PATTERN, ...Object.values(BY_EXTENSION)];
155
+ return [...new Set(all.map((e) => e.language).filter(Boolean))].sort();
156
+ }