synabun 2.0.0 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/THIRD-PARTY-LICENSES.md +46 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +2 -2
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +20 -0
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/lib/start-launcher.js +88 -1
  14. package/mcp-server/package-lock.json +2 -2
  15. package/mcp-server/package.json +1 -3
  16. package/neural-interface/i18n/en.json +1500 -153
  17. package/neural-interface/i18n/pt-BR.json +1498 -152
  18. package/neural-interface/lib/assistant-api.js +62 -6
  19. package/neural-interface/lib/assistant-brains/claude.js +4 -0
  20. package/neural-interface/lib/assistant-catalog.js +179 -27
  21. package/neural-interface/lib/assistant-config.js +31 -2
  22. package/neural-interface/lib/assistant-dispatch.js +62 -13
  23. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  24. package/neural-interface/lib/assistant-memory.js +24 -10
  25. package/neural-interface/lib/assistant-persona.js +6 -2
  26. package/neural-interface/lib/assistant-pricing.js +21 -6
  27. package/neural-interface/lib/assistant-runtime.js +181 -12
  28. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  29. package/neural-interface/lib/assistant-test-density.js +142 -0
  30. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  31. package/neural-interface/lib/assistant-user-context.js +908 -0
  32. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  33. package/neural-interface/lib/assistant-user-learn.js +404 -0
  34. package/neural-interface/lib/assistant-user-threads.js +226 -0
  35. package/neural-interface/lib/assistant-user-writer.js +181 -0
  36. package/neural-interface/lib/browser-background-tab.js +134 -0
  37. package/neural-interface/lib/browser-screenshot.js +678 -61
  38. package/neural-interface/lib/claude-agent-bridge.js +33 -6
  39. package/neural-interface/lib/claude-model-catalog.js +30 -2
  40. package/neural-interface/lib/claude-temporary.js +46 -7
  41. package/neural-interface/lib/cloudflared/install.js +533 -0
  42. package/neural-interface/lib/cloudflared/manager.js +545 -0
  43. package/neural-interface/lib/cloudflared/pins.js +115 -0
  44. package/neural-interface/lib/cloudflared/pins.json +63 -0
  45. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  46. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  47. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  48. package/neural-interface/lib/editor-completion.js +714 -0
  49. package/neural-interface/lib/editor-config.js +149 -0
  50. package/neural-interface/lib/editor-files-api.js +268 -0
  51. package/neural-interface/lib/editor-git-api.js +134 -0
  52. package/neural-interface/lib/file-explorer-api.js +1039 -0
  53. package/neural-interface/lib/file-explorer-system.js +355 -0
  54. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  55. package/neural-interface/lib/file-explorer-watch.js +239 -0
  56. package/neural-interface/lib/git-diff-api.js +103 -0
  57. package/neural-interface/lib/git-read.js +202 -0
  58. package/neural-interface/lib/greeting/api.js +89 -0
  59. package/neural-interface/lib/greeting/budgets.js +90 -0
  60. package/neural-interface/lib/greeting/engine.js +461 -0
  61. package/neural-interface/lib/greeting/facts.js +320 -0
  62. package/neural-interface/lib/greeting/sources.js +252 -0
  63. package/neural-interface/lib/greeting/writer.js +139 -0
  64. package/neural-interface/lib/http-guards.js +189 -13
  65. package/neural-interface/lib/machine-names.js +88 -0
  66. package/neural-interface/lib/memory-api.js +31 -7
  67. package/neural-interface/lib/native-loop-providers.js +10 -5
  68. package/neural-interface/lib/plugin-installer.js +4 -2
  69. package/neural-interface/lib/project-storage.js +5 -7
  70. package/neural-interface/lib/request-gate.js +589 -0
  71. package/neural-interface/lib/ui-blob-api.js +108 -0
  72. package/neural-interface/lib/ui-blob-store.js +68 -0
  73. package/neural-interface/package-lock.json +701 -2
  74. package/neural-interface/package.json +13 -2
  75. package/neural-interface/public/install-app.html +9 -6
  76. package/neural-interface/public/offline.html +3 -2
  77. package/neural-interface/public/onboarding.html +12 -1
  78. package/neural-interface/public/shared/agent-runtime-options.js +22 -6
  79. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  80. package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
  81. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  82. package/neural-interface/public/shared/assistant/asst-mascot.js +18 -58
  83. package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
  84. package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
  85. package/neural-interface/public/shared/assistant/asst-render.js +162 -19
  86. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  87. package/neural-interface/public/shared/assistant/asst-route.js +23 -329
  88. package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
  89. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  90. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  91. package/neural-interface/public/shared/assistant/asst-state.js +185 -1
  92. package/neural-interface/public/shared/assistant/asst-styles.js +158 -4
  93. package/neural-interface/public/shared/blob-store.js +111 -22
  94. package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
  95. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  96. package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
  97. package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
  98. package/neural-interface/public/shared/constants.js +6 -1
  99. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  100. package/neural-interface/public/shared/cp/cp-styles.js +15 -0
  101. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  102. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  103. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  104. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  105. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  106. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  107. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  108. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  109. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  110. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  111. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  112. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  113. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  114. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  115. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  116. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  117. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  118. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  119. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  120. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  121. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  122. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  123. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  124. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  125. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  126. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  127. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  128. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  129. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  130. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  131. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  132. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  133. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  134. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  135. package/neural-interface/public/shared/file-links.js +339 -0
  136. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  137. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  138. package/neural-interface/public/shared/html-shell.js +160 -165
  139. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  140. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  141. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
  142. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  143. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
  144. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
  145. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  146. package/neural-interface/public/shared/pwa-install-page.js +29 -1
  147. package/neural-interface/public/shared/pwa-install.css +3 -3
  148. package/neural-interface/public/shared/pwa-install.js +100 -1
  149. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  150. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  151. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  152. package/neural-interface/public/shared/settings/settings-ia.js +18 -4
  153. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  154. package/neural-interface/public/shared/start-bridge.js +238 -19
  155. package/neural-interface/public/shared/styles.css +1414 -721
  156. package/neural-interface/public/shared/synabun-mascot.js +20 -56
  157. package/neural-interface/public/shared/tunnel-view.js +167 -0
  158. package/neural-interface/public/shared/ui-claude-panel.js +157 -16
  159. package/neural-interface/public/shared/ui-detail.js +15 -0
  160. package/neural-interface/public/shared/ui-explorer.js +22 -0
  161. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  162. package/neural-interface/public/shared/ui-file-links.js +103 -0
  163. package/neural-interface/public/shared/ui-help.js +26 -0
  164. package/neural-interface/public/shared/ui-invite.js +126 -116
  165. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  166. package/neural-interface/public/shared/ui-loading.js +159 -15
  167. package/neural-interface/public/shared/ui-menubar.js +12 -30
  168. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  169. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  170. package/neural-interface/public/shared/ui-navbar.js +45 -21
  171. package/neural-interface/public/shared/ui-settings.js +455 -488
  172. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  173. package/neural-interface/public/shared/ui-sync.js +16 -0
  174. package/neural-interface/public/shared/ui-terminal.js +23 -5
  175. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  176. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  177. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  178. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  179. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  180. package/neural-interface/public/shared/worker-end.js +251 -0
  181. package/neural-interface/public/sw.js +2 -2
  182. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  183. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  184. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  185. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  186. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  187. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  188. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  189. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  190. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  191. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  192. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  193. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  194. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  195. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  196. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  197. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  198. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  199. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  200. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  201. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  202. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  203. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  204. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  205. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  206. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  207. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  208. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  209. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  210. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  211. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  212. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  213. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  214. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  215. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  216. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  217. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  218. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  219. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  220. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  221. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  222. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  223. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  224. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  225. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  226. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  227. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  228. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  229. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  230. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  231. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  232. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  233. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  234. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  235. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  236. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  237. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  238. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  239. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  240. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  241. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  242. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  243. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  244. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  245. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  246. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  247. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  248. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  249. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  250. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  251. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  252. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  253. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  254. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  255. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  256. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  257. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  258. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  259. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  260. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  261. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  262. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  263. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  264. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  265. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  266. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  267. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  268. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  269. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  270. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  271. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  272. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  273. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  274. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  275. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  276. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  277. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  278. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  279. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  280. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  281. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  282. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  283. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  284. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  285. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  286. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  287. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  288. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  289. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  290. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  291. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  292. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  293. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  294. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  295. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  296. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  297. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  298. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  299. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  300. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  301. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  302. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  303. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  304. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  305. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  306. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  307. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  308. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  309. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  310. package/neural-interface/scripts/build-vendor.mjs +78 -2
  311. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  312. package/neural-interface/server.js +873 -1028
  313. package/neural-interface/templates/rulesets/README.md +1 -1
  314. package/package.json +2 -8
  315. package/setup.js +44 -7
  316. package/benchmarks/browser-live-results.json +0 -79
  317. package/benchmarks/browser-v2-results.json +0 -258
  318. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  319. package/neural-interface/public/category-logos/claude.svg +0 -1
  320. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  321. package/neural-interface/public/category-logos/openai.svg +0 -1
  322. package/neural-interface/public/category-logos/synabun.png +0 -0
  323. package/neural-interface/public/claude.svg +0 -1
  324. package/neural-interface/public/gallery.svg +0 -6
  325. package/neural-interface/public/leonardoai.svg +0 -1
  326. package/neural-interface/public/mcp.svg +0 -4
  327. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  328. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  329. package/neural-interface/public/sessionicon.svg +0 -8
  330. package/neural-interface/public/toolbar-album.svg +0 -8
  331. package/neural-interface/public/toolbar-question.svg +0 -6
  332. package/neural-interface/public/toolbar-sessions.svg +0 -6
  333. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  334. package/skills/synabun/synabunicon.png +0 -0
@@ -0,0 +1,328 @@
1
+ // ═══════════════════════════════════════════
2
+ // SETTINGS — the code editor's page, in words
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // What `GET /api/editor/config`, `GET /api/editor/capabilities` and the routes under `/api/editor/complete`
6
+ // (lib/editor-config.js, lib/editor-completion.js) say about Settings → Code editor: for "Editing", which
7
+ // preferences there are and what each is set to; for "AI autocomplete", whether the section can be used at all,
8
+ // what is stored, which models there are, and what a test found. Pure: no DOM and no import, so node runs it
9
+ // (tests/settings-code-editor-view.test.mjs).
10
+ //
11
+ // EDITING_FIELDS the editing preferences, each with its control
12
+ // readEditorEditingResponse({ status, body, error }) what the server answered → a reading
13
+ // describeEditorEditing(reading, { tx }) the "Editing" section as the pane draws it
14
+ // editingOptionLabel(key, value, { tx }) / editingChange(key, raw)
15
+ // readEditorAiResponse({ status, body, error, capabilities }) what the server answered → a reading
16
+ // describeEditorAi(reading, { tx }) the section as settings-code-editor-pane.js draws it
17
+ // describeEditorModels(answer, selected, { tx }) the model list
18
+ // describeEditorTest(result, { tx }) / describeEditorFastest(body, { tx })
19
+ // describeEditorUsage(today, { tx })
20
+ // describeEditorSaveFailure(input, { tx }) the sentence for a change that could not be saved
21
+ // parseExcludeList(text) / excludeListText(list) / isEndpointUrl(text)
22
+ //
23
+ // `tx(key, params)` is the caller's translate function over the settings.redesign.* strings (the kit's `tx`).
24
+ //
25
+ // The section never shows a healthy-looking control for something that is not happening: a server without the
26
+ // feature gets disabled controls and says why, and the key is only ever "set" or "not set".
27
+
28
+ const K = 'settings.redesign.code_editor';
29
+
30
+ const clean = (value, max = 200) => {
31
+ const text = String(value ?? '').replace(/\s+/g, ' ').trim();
32
+ return text.length > max ? `${text.slice(0, max - 1)}…` : text;
33
+ };
34
+
35
+ // ── Editing ──
36
+
37
+ /**
38
+ * The `editing` preferences (public/shared/editor/editor-prefs.js keeps their defaults and their limits), in the
39
+ * order the section shows them: the text, what is drawn around it, indentation, and what a save does.
40
+ * `id`: the control's inventory id; `kind`: a switch, or one choice among `values`.
41
+ */
42
+ export const EDITING_FIELDS = Object.freeze([
43
+ { key: 'fontSize', id: 'EDT014', kind: 'choice', values: Object.freeze([10, 11, 12, 13, 14, 15, 16, 17, 18]) },
44
+ { key: 'ligatures', id: 'EDT015', kind: 'toggle' },
45
+ { key: 'wordWrap', id: 'EDT016', kind: 'toggle' },
46
+ { key: 'minimap', id: 'EDT017', kind: 'toggle' },
47
+ { key: 'indentGuides', id: 'EDT018', kind: 'toggle' },
48
+ { key: 'renderWhitespace', id: 'EDT019', kind: 'choice', values: Object.freeze(['none', 'selection', 'all']) },
49
+ { key: 'indentWith', id: 'EDT020', kind: 'choice', values: Object.freeze(['auto', 'spaces', 'tabs']) },
50
+ { key: 'tabSize', id: 'EDT021', kind: 'choice', values: Object.freeze(['auto', 2, 4, 8]) },
51
+ { key: 'trimTrailingWhitespace', id: 'EDT022', kind: 'toggle' },
52
+ { key: 'insertFinalNewline', id: 'EDT023', kind: 'toggle' },
53
+ ]);
54
+
55
+ /**
56
+ * What GET /api/editor/config answered about the editing preferences.
57
+ *
58
+ * ready the preferences are there
59
+ * old-server 404: the running server was started before the editor kept its preferences
60
+ * unreadable anything else; `reason` says what
61
+ *
62
+ * @param {{ status?: number, body?: object|null, error?: string|null }} response
63
+ * @returns {{ state: 'ready'|'old-server'|'unreadable', editing: object|null, reason: string|null }}
64
+ */
65
+ export function readEditorEditingResponse({ status = 0, body = null, error = null } = {}) {
66
+ if (status === 404) return { state: 'old-server', editing: null, reason: null };
67
+ if (status === 200 && body?.ok === true && body.config?.editing && typeof body.config.editing === 'object' && !Array.isArray(body.config.editing)) {
68
+ return { state: 'ready', editing: body.config.editing, reason: null };
69
+ }
70
+ return { state: 'unreadable', editing: null, reason: clean(body?.error || error) || (status ? `HTTP ${status}` : null) };
71
+ }
72
+
73
+ /** What a choice is called in its list: "12 px", "Detect", "Spaces", "In the selection"… */
74
+ export function editingOptionLabel(key, value, { tx }) {
75
+ if (key === 'fontSize') return tx(`${K}.editing.fontSize`, { size: value });
76
+ if (key === 'renderWhitespace') return tx(`${K}.editing.whitespace.${value}`);
77
+ if (value === 'auto') return tx(`${K}.editing.detect`);
78
+ if (key === 'indentWith') return tx(`${K}.editing.${value}`);
79
+ return String(value);
80
+ }
81
+
82
+ /**
83
+ * The "Editing" section as it is drawn.
84
+ * → { state, ready, note, values: { [key]: value } }
85
+ * `reading`: what readEditorEditingResponse() returned, or null while the server has not answered yet.
86
+ * `values`: for each field what its control shows: a boolean for a switch, one of the field's values (as text)
87
+ * for a choice. Before the preferences are known, a switch is off and a choice shows its first value.
88
+ * `note`: one line above the controls when they cannot be used (and why), else null.
89
+ */
90
+ export function describeEditorEditing(reading, { tx }) {
91
+ const state = reading ? reading.state || 'unreadable' : 'loading';
92
+ const ready = state === 'ready';
93
+ const stored = ready && reading.editing ? reading.editing : {};
94
+ const values = {};
95
+ for (const field of EDITING_FIELDS) {
96
+ if (field.kind === 'toggle') values[field.key] = stored[field.key] === true;
97
+ else {
98
+ const found = field.values.find((value) => String(value) === String(stored[field.key]));
99
+ values[field.key] = String(found !== undefined ? found : field.values[0]);
100
+ }
101
+ }
102
+ let note = null;
103
+ // Until the preferences arrive the controls show first choices and switches that are off: say that they are
104
+ // being read, so those are not taken for what is stored.
105
+ if (state === 'loading') note = { text: tx('settings.redesign.common.checking'), tone: 'neutral' };
106
+ else if (state === 'old-server') note = { text: tx(`${K}.editing.oldServer`), tone: 'warn' };
107
+ else if (state === 'unreadable') {
108
+ const reason = clean(reading?.reason);
109
+ note = { text: reason ? tx(`${K}.state.unreadable`, { reason }) : tx(`${K}.state.unreadableNoReason`), tone: 'warn' };
110
+ }
111
+ return { state, ready, note, values };
112
+ }
113
+
114
+ /**
115
+ * What a control's new value changes in the stored preferences.
116
+ * @param {string} key a field's key
117
+ * @param {boolean | string} raw a switch's `checked`, or a list's `value`
118
+ * @returns {{ editing: object } | null} what is sent to PUT /api/editor/config; null for a value the field does not have
119
+ */
120
+ export function editingChange(key, raw) {
121
+ const field = EDITING_FIELDS.find((entry) => entry.key === key);
122
+ if (!field) return null;
123
+ if (field.kind === 'toggle') return typeof raw === 'boolean' ? { editing: { [key]: raw } } : null;
124
+ const value = field.values.find((entry) => String(entry) === String(raw));
125
+ return value === undefined ? null : { editing: { [key]: value } };
126
+ }
127
+
128
+ // ── AI autocomplete ──
129
+
130
+ /**
131
+ * What the server answered.
132
+ *
133
+ * ready the preferences are there and the server has the `complete` capability
134
+ * old-server 404, or preferences without the capability: the running server was started before AI
135
+ * autocomplete existed (public/ reloads at any time, lib/ only on a restart)
136
+ * unreadable anything else (no answer, a refusal, a body that is not the preferences); `reason` says what
137
+ *
138
+ * @param {{ status?: number, body?: object|null, error?: string|null, capabilities?: string[]|null }} response
139
+ * `status` and `body` of GET /api/editor/config; `capabilities` of GET /api/editor/capabilities (null: not answered)
140
+ * @returns {{ state: 'ready'|'old-server'|'unreadable', config: object|null, hasEndpointKey: boolean, reason: string|null }}
141
+ */
142
+ export function readEditorAiResponse({ status = 0, body = null, error = null, capabilities = null } = {}) {
143
+ const out = (state, reason = null) => ({ state, config: null, hasEndpointKey: false, reason });
144
+ if (status === 404) return out('old-server');
145
+ if (status === 200 && body?.ok === true && body.config?.ai && typeof body.config.ai === 'object') {
146
+ if (!Array.isArray(capabilities) || !capabilities.includes('complete')) return out('old-server');
147
+ return { state: 'ready', config: body.config, hasEndpointKey: body.hasEndpointKey === true, reason: null };
148
+ }
149
+ return out('unreadable', clean(body?.error || error) || (status ? `HTTP ${status}` : null));
150
+ }
151
+
152
+ /**
153
+ * The section as it is drawn.
154
+ * → { state, ready, note, pill: { text, state }, enabled, engine, model, endpoint: { kind, url, model, shape },
155
+ * hasKey, keyState, keyPlaceholder, urlPlaceholder, exclude (text), excludeCount }
156
+ * `reading`: what readEditorAiResponse() returned, or null while the server has not answered yet (the controls
157
+ * are then switched off without a word about why: nothing is known to be wrong).
158
+ * `note`: one line above the controls when they cannot be used (and why), else null.
159
+ * `pill.state`: 'ok' on and able to ask · 'warn' on but with nothing to ask yet · 'off'. An empty `pill.text`
160
+ * is not shown.
161
+ */
162
+ export function describeEditorAi(reading, { tx }) {
163
+ const state = reading ? reading.state || 'unreadable' : 'loading';
164
+ const ready = state === 'ready';
165
+ const view = {
166
+ state, ready, note: null, pill: { text: state === 'loading' ? '' : tx(`${K}.pill.unavailable`), state: 'off' },
167
+ enabled: false, engine: 'opencode', model: null, endpoint: { kind: 'ollama', url: '', model: '', shape: 'fim' },
168
+ hasKey: false, keyState: '', keyPlaceholder: tx(`${K}.key.placeholder`), urlPlaceholder: tx(`${K}.url.placeholderOllama`),
169
+ exclude: '', excludeCount: '',
170
+ };
171
+ if (state === 'loading') {
172
+ view.note = { text: tx('settings.redesign.common.checking'), tone: 'neutral' };
173
+ return view;
174
+ }
175
+ if (!ready) {
176
+ const reason = clean(reading?.reason);
177
+ if (state === 'old-server') view.note = { text: tx(`${K}.state.oldServer`), tone: 'warn' };
178
+ else view.note = { text: reason ? tx(`${K}.state.unreadable`, { reason }) : tx(`${K}.state.unreadableNoReason`), tone: 'warn' };
179
+ return view;
180
+ }
181
+ const ai = reading.config.ai;
182
+ const endpoint = ai.endpoint && typeof ai.endpoint === 'object' ? ai.endpoint : {};
183
+ view.enabled = ai.enabled === true;
184
+ view.engine = ai.engine === 'endpoint' ? 'endpoint' : 'opencode';
185
+ view.model = typeof ai.model === 'string' && ai.model ? ai.model : null;
186
+ view.endpoint = {
187
+ kind: endpoint.kind === 'openai' ? 'openai' : 'ollama', url: String(endpoint.url || ''), model: String(endpoint.model || ''),
188
+ shape: endpoint.shape === 'chat' ? 'chat' : 'fim',
189
+ };
190
+ view.hasKey = reading.hasEndpointKey === true;
191
+ view.keyState = tx(view.hasKey ? `${K}.key.set` : `${K}.key.notSet`);
192
+ view.keyPlaceholder = tx(view.hasKey ? `${K}.key.placeholderReplace` : `${K}.key.placeholder`);
193
+ view.urlPlaceholder = tx(view.endpoint.kind === 'openai' ? `${K}.url.placeholderOpenai` : `${K}.url.placeholderOllama`);
194
+ const list = Array.isArray(ai.exclude) ? ai.exclude.filter((glob) => typeof glob === 'string') : [];
195
+ view.exclude = excludeListText(list);
196
+ view.excludeCount = list.length ? tx(`${K}.exclude.count`, { n: list.length }) : tx(`${K}.exclude.empty`);
197
+ const able = view.engine === 'endpoint' ? !!(view.endpoint.url && view.endpoint.model) : !!view.model;
198
+ if (!view.enabled) view.pill = { text: tx(`${K}.pill.off`), state: 'off' };
199
+ else if (!able) view.pill = { text: tx(`${K}.pill.setup`), state: 'warn' };
200
+ else view.pill = { text: tx(`${K}.pill.on`), state: 'ok' };
201
+ return view;
202
+ }
203
+
204
+ /** A price per million tokens the way a model row writes it: 0.15, 0.60, 2.00, 0.003. */
205
+ export function formatPrice(value) {
206
+ const n = Number(value);
207
+ if (!Number.isFinite(n) || n < 0) return '';
208
+ return n > 0 && n < 0.01 ? n.toFixed(3) : n.toFixed(2);
209
+ }
210
+
211
+ /** A duration: 640 ms, 1.2 s. */
212
+ export function formatDuration(value) {
213
+ const ms = Math.max(0, Math.round(Number(value) || 0));
214
+ return ms < 1000 ? `${ms} ms` : `${(ms / 1000).toFixed(1)} s`;
215
+ }
216
+
217
+ /** A count: 950, 1.2k, 3.4M. */
218
+ export function formatAmount(value) {
219
+ const n = Math.max(0, Math.round(Number(value) || 0));
220
+ if (n < 1000) return String(n);
221
+ if (n < 1_000_000) return `${(n / 1000).toFixed(n < 10_000 ? 1 : 0).replace(/\.0$/, '')}k`;
222
+ return `${(n / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`;
223
+ }
224
+
225
+ /**
226
+ * The model list.
227
+ * @param {{ status?: number, body?: object|null } | null} answer what GET /api/editor/complete/models answered;
228
+ * null while it is being asked
229
+ * @param {string|null} selected `ai.model`
230
+ * @param {{ tx: Function, providersPage?: string }} deps `providersPage`: the name of the Settings page where a
231
+ * provider is signed in to
232
+ * @returns {{ state: 'loading'|'list'|'empty'|'unavailable', message: string, canStart: boolean,
233
+ * rows: { id: string, label: string, provider: string, meta: string, fast: boolean, selected: boolean, listed: boolean }[] }}
234
+ * `canStart`: the list could not be read because OpenCode is not running, and starting it would give one.
235
+ */
236
+ export function describeEditorModels(answer, selected, { tx, providersPage = '' }) {
237
+ const view = { state: 'loading', message: tx(`${K}.models.loading`), canStart: false, rows: [] };
238
+ const missing = () => (selected ? [{ id: selected, label: selected, provider: selected.split('/')[0], meta: tx(`${K}.models.notListed`), fast: false, selected: true, listed: false }] : []);
239
+ if (!answer) return view;
240
+ const body = answer.body;
241
+ if (answer.status !== 200 || body?.ok !== true || !Array.isArray(body.models)) {
242
+ return { state: 'unavailable', message: clean(body?.message || body?.error) || tx(`${K}.models.unavailable`), canStart: false, rows: missing() };
243
+ }
244
+ if (body.available === false) {
245
+ return { state: 'unavailable', message: clean(body.message) || tx(`${K}.models.unavailable`), canStart: true, rows: missing() };
246
+ }
247
+ const rows = body.models.filter((model) => model && typeof model.id === 'string').map((model) => {
248
+ const parts = [];
249
+ if (model.fast) parts.push(tx(`${K}.models.fast`));
250
+ if (model.free) parts.push(tx(`${K}.models.free`));
251
+ else if (model.price) parts.push(tx(`${K}.models.price`, { input: formatPrice(model.price.input), output: formatPrice(model.price.output) }));
252
+ else parts.push(tx(`${K}.models.noPrice`));
253
+ return { id: model.id, label: String(model.label || model.id), provider: String(model.provider || model.id.split('/')[0]), meta: parts.join(' · '), fast: model.fast === true, selected: model.id === selected, listed: true };
254
+ });
255
+ if (selected && !rows.some((row) => row.selected)) rows.unshift(...missing());
256
+ if (!rows.length) return { state: 'empty', message: tx(`${K}.models.none`, { page: providersPage }), canStart: false, rows };
257
+ return { state: 'list', message: '', canStart: false, rows };
258
+ }
259
+
260
+ /**
261
+ * One test's result as a sentence.
262
+ * @param {{ ok?: boolean, ms?: number, tokens?: { input: number, output: number }|null, sample?: string, error?: string }|null} result
263
+ * a row of POST /api/editor/complete/test; null when the request itself got no answer
264
+ * @returns {{ tone: 'ok'|'warn'|'err', text: string }}
265
+ */
266
+ export function describeEditorTest(result, { tx }) {
267
+ if (!result) return { tone: 'err', text: tx(`${K}.test.failed`, { reason: tx(`${K}.test.noAnswer`) }) };
268
+ if (!result.ok) return { tone: 'err', text: tx(`${K}.test.failed`, { reason: clean(result.error, 160) || '?' }) };
269
+ const time = formatDuration(result.ms);
270
+ const sample = clean(result.sample, 80);
271
+ if (!sample) return { tone: 'warn', text: tx(`${K}.test.empty`, { time }) };
272
+ if (result.tokens) return { tone: 'ok', text: tx(`${K}.test.ok`, { time, input: formatAmount(result.tokens.input), output: formatAmount(result.tokens.output), sample }) };
273
+ return { tone: 'ok', text: tx(`${K}.test.okNoTokens`, { time, sample }) };
274
+ }
275
+
276
+ /**
277
+ * What "Find fastest" found.
278
+ * @param {object|null} body the answer of POST /api/editor/complete/test { fastest: true }; null: no answer
279
+ * @returns {{ tone: 'ok'|'warn'|'err', text: string, model: string|null }} `model`: the one to select, or null
280
+ */
281
+ export function describeEditorFastest(body, { tx }) {
282
+ if (!body || body.ok !== true || !Array.isArray(body.results)) {
283
+ return { tone: 'err', text: tx(`${K}.test.failed`, { reason: clean(body?.error || body?.message, 160) || tx(`${K}.test.noAnswer`) }), model: null };
284
+ }
285
+ const best = body.fastest ? body.results.find((row) => row.model === body.fastest && row.ok) : null;
286
+ if (best) return { tone: 'ok', text: tx(`${K}.fastest.found`, { model: best.model, time: formatDuration(best.ms) }), model: best.model };
287
+ if (!body.results.length) return { tone: 'warn', text: clean(body.message, 160) || tx(`${K}.fastest.noCandidates`), model: null };
288
+ return { tone: 'warn', text: tx(`${K}.fastest.none`), model: null };
289
+ }
290
+
291
+ /** Today's use as one line. `today`: the `today` of GET /api/editor/complete/usage, or null. */
292
+ export function describeEditorUsage(today, { tx }) {
293
+ const n = (value) => Math.max(0, Math.round(Number(value) || 0));
294
+ if (!today || n(today.requests) === 0) return tx(`${K}.usage.none`);
295
+ return tx(`${K}.usage.today`, { requests: formatAmount(today.requests), input: formatAmount(today.tokensIn), output: formatAmount(today.tokensOut), accepted: n(today.accepted), shown: n(today.shown) });
296
+ }
297
+
298
+ /**
299
+ * The patterns a text holds: one per line (or between commas), no empty ones, none twice, no folders.
300
+ * The server normalises the same way (editor-prefs.js); this is what the page sends.
301
+ */
302
+ export function parseExcludeList(text) {
303
+ const out = [];
304
+ for (const piece of String(text ?? '').split(/[\n,]/)) {
305
+ const glob = piece.trim().slice(0, 200);
306
+ if (glob && !glob.includes('/') && !glob.includes('\\') && !out.includes(glob)) out.push(glob);
307
+ if (out.length >= 200) break;
308
+ }
309
+ return out;
310
+ }
311
+
312
+ /** A list of patterns as the text field shows it. */
313
+ export const excludeListText = (list) => (Array.isArray(list) ? list.join('\n') : '');
314
+
315
+ /** Whether a text is an address a request can be sent to. Empty is not an address, and not an error either. */
316
+ export const isEndpointUrl = (text) => /^https?:\/\/\S+$/i.test(String(text ?? '').trim());
317
+
318
+ /**
319
+ * A change that could not be saved: the control goes back to where it was, and this is the sentence that says so.
320
+ * @param {{ status?: number, body?: object|null, error?: string|null }} input
321
+ */
322
+ export function describeEditorSaveFailure({ status = 0, body = null, error = null } = {}, { tx }) {
323
+ let reason = clean(body?.error || body?.message, 160);
324
+ if (!reason && status === 404) reason = tx(`${K}.save.oldServer`);
325
+ if (!reason && status) reason = `HTTP ${status}`;
326
+ if (!reason) reason = clean(error, 160) || tx(`${K}.save.noAnswer`);
327
+ return tx(`${K}.save.failed`, { reason });
328
+ }
@@ -0,0 +1,133 @@
1
+ // ═══════════════════════════════════════════
2
+ // SETTINGS — the greeting, in words
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // What `GET /api/greeting` and `POST /api/greeting/preview` (lib/greeting/api.js, the engine in
6
+ // lib/greeting/engine.js) say about Settings → Automations → Greeting: whether the one switch can be
7
+ // used at all, what it is set to, what stands in the way, and what a new session would be greeted
8
+ // with right now. Pure: no DOM (it imports only the greeting's own DOM-free view), so node runs it
9
+ // (tests/settings-greeting-view.test.mjs).
10
+ //
11
+ // readGreetingStatus({ status, body, error }) what the server answered → a reading
12
+ // describeGreeting(reading, preview, { tx, t }) the section as ui-settings.js draws it
13
+ // describeGreetingSaveFailure(input, { tx }) the sentence for a switch that could not be saved
14
+ //
15
+ // `tx(key, params)` is the caller's translate function over the settings.redesign.* strings (the
16
+ // kit's `tx`); `t` is the app's translator, for the elapsed time the greeting card also formats.
17
+ //
18
+ // The section never shows a healthy-looking control for something that is not happening: a server
19
+ // without the engine gets a disabled switch and says why, and the preview says in so many words
20
+ // whether a session opened now would actually be greeted.
21
+
22
+ import { formatAway } from '../greeting/greeting-view.js';
23
+
24
+ const K = 'settings.redesign.hooks.greet';
25
+
26
+ const clean = (value, max = 200) => {
27
+ const text = String(value ?? '').replace(/\s+/g, ' ').trim();
28
+ return text.length > max ? `${text.slice(0, max - 1)}…` : text;
29
+ };
30
+
31
+ /**
32
+ * What the server answered.
33
+ *
34
+ * ready the engine is there: `payload` is { settings, enabled, killed, writer, spend, gapMs, last }
35
+ * old-server 404: the running server was started before the engine existed (public/ reloads at any
36
+ * time, lib/ only on a restart)
37
+ * failed 503 GREETING_UNAVAILABLE: the engine did not start; `reason` is the error
38
+ * unreadable anything else (no answer, a refusal, a body that is not the payload); `reason` says what
39
+ *
40
+ * @param {{ status?: number, body?: object|null, error?: string|null }} response
41
+ * @returns {{ state: 'ready'|'old-server'|'failed'|'unreadable', payload: object|null, reason: string|null }}
42
+ */
43
+ export function readGreetingStatus({ status = 0, body = null, error = null } = {}) {
44
+ if (status === 200 && body?.ok === true && body.settings && typeof body.settings === 'object') return { state: 'ready', payload: body, reason: null };
45
+ if (status === 404) return { state: 'old-server', payload: null, reason: null };
46
+ if (status === 503 && body?.code === 'GREETING_UNAVAILABLE') return { state: 'failed', payload: null, reason: clean(body.reason) || null };
47
+ return { state: 'unreadable', payload: null, reason: clean(body?.error || error) || (status ? `HTTP ${status}` : null) };
48
+ }
49
+
50
+ /** "$0.0006" · "<$0.0001" · '' */
51
+ function usd(value) {
52
+ const amount = Number(value);
53
+ if (!Number.isFinite(amount) || amount <= 0) return '';
54
+ if (amount < 0.0001) return '<$0.0001';
55
+ return `$${amount < 0.01 ? amount.toFixed(4) : amount.toFixed(2)}`;
56
+ }
57
+
58
+ /**
59
+ * The section as it is drawn.
60
+ * `preview`: null (not asked yet) | { loading: true } | { failed } (the request itself failed) | the engine's
61
+ * preview payload (whose own `error` only says why no model wrote the sentence: 'not-prepared', 'no-writer' …).
62
+ * → { state, ready, on, disabled, note, notes[], preview: { shown, state, project, lead, greeting, empty, spend, canWrite } }
63
+ * preview.state: 'hidden' | 'loading' | 'greeting' | 'empty' | 'error'
64
+ * preview.greeting: what the shared card draws (public/shared/greeting/greeting-card.js), or null
65
+ */
66
+ export function describeGreeting(reading, preview, { tx, t = null, now = Date.now() } = {}) {
67
+ const state = reading?.state || 'unreadable';
68
+ const ready = state === 'ready';
69
+ const payload = ready ? reading.payload : null;
70
+ const on = ready && payload.settings.enabled !== false;
71
+ const killed = ready && payload.killed === true;
72
+ let note = null;
73
+ if (state === 'old-server') note = { tone: 'info', text: tx(`${K}.state.oldServer`) };
74
+ else if (state === 'failed') note = { tone: 'warn', text: reading.reason ? tx(`${K}.state.failed`, { reason: reading.reason }) : tx(`${K}.state.failedUnknown`) };
75
+ else if (state === 'unreadable') note = { tone: 'warn', text: reading?.reason ? tx(`${K}.state.unreadable`, { reason: reading.reason }) : tx(`${K}.state.unreadableUnknown`) };
76
+ else if (killed) note = { tone: 'warn', text: tx(`${K}.state.killed`) };
77
+
78
+ const notes = [];
79
+ if (ready && on && !killed && payload.writer === false) notes.push({ tone: 'info', text: tx(`${K}.note.noWriter`) });
80
+
81
+ const view = { shown: ready, state: 'hidden', project: '', lead: '', greeting: null, empty: '', spend: '', canWrite: false };
82
+ if (ready) {
83
+ if (!preview || preview.loading) { view.state = 'loading'; view.lead = tx(`${K}.preview.loading`); }
84
+ else if (preview.failed || preview.reason === 'error' || preview.reason === 'unavailable') {
85
+ view.state = 'error';
86
+ view.project = clean(preview.label || preview.project || '', 60);
87
+ view.empty = tx(`${K}.preview.error`, { reason: clean(preview.failed || preview.reason || '', 120) || '?' });
88
+ } else {
89
+ const project = clean(preview.label || preview.project || '', 60);
90
+ view.project = project;
91
+ const text = typeof preview.text === 'string' ? preview.text.trim() : '';
92
+ const since = Date.parse(preview.lastHereAt ?? '');
93
+ const away = Number.isFinite(since) ? formatAway(Math.max(0, now - since), t) : '';
94
+ if (!text) {
95
+ view.state = 'empty';
96
+ view.empty = tx(preview.reason === 'first-session' || !Number.isFinite(since) ? `${K}.preview.firstSession` : `${K}.preview.nothingToSay`, { project });
97
+ } else {
98
+ view.state = 'greeting';
99
+ const reason = !on || killed ? 'off' : preview.reason;
100
+ if (reason === 'off') view.lead = tx(`${K}.preview.off`, { project });
101
+ else if (reason === 'recent') view.lead = tx(`${K}.preview.recent`, { project, time: away || tx(`${K}.preview.moment`), gap: formatAway(Number(preview.gapMs) || 3_600_000, t) });
102
+ else if (reason === 'nothing-new') view.lead = tx(`${K}.preview.nothingNew`, { project });
103
+ else view.lead = tx(`${K}.preview.now`, { project });
104
+ view.greeting = {
105
+ id: null, text, source: preview.source === 'writer' ? 'writer' : 'fallback', error: preview.error || null, model: preview.model || null,
106
+ tokens: preview.tokens || null, costUsd: preview.costUsd ?? null, preparedAt: preview.preparedAt || null,
107
+ basis: Array.isArray(preview.basis) ? preview.basis : [], awaySince: null,
108
+ };
109
+ }
110
+ const spend = preview.spend || payload.spend || null;
111
+ if (spend) {
112
+ const calls = Math.max(0, Math.round(Number(spend.calls) || 0));
113
+ view.spend = calls > 0
114
+ ? tx(`${K}.preview.spend`, { calls, cap: Math.round(Number(spend.cap) || 0), cost: usd(spend.costUsd) || '$0' })
115
+ : tx(`${K}.preview.spendNone`);
116
+ }
117
+ view.canWrite = payload.writer !== false && view.state !== 'empty';
118
+ }
119
+ }
120
+ return { state, ready, on, disabled: !ready || killed, note, notes, preview: view };
121
+ }
122
+
123
+ /**
124
+ * Why a switch could not be saved, as one sentence. `status` / `body`: what the server answered;
125
+ * `error`: the fetch failure when there was no answer.
126
+ */
127
+ export function describeGreetingSaveFailure({ status = 0, body = null, error = null } = {}, { tx }) {
128
+ if (status === 404) return tx(`${K}.save.oldServer`);
129
+ if (status === 503) return tx(`${K}.save.failed`, { reason: clean(body?.reason || body?.error) || 'HTTP 503' });
130
+ if (status === 403) return tx(`${K}.save.forbidden`);
131
+ const reason = clean(body?.error || error) || (status ? `HTTP ${status}` : '');
132
+ return reason ? tx(`${K}.save.failed`, { reason }) : tx(`${K}.save.noAnswer`);
133
+ }
@@ -118,6 +118,14 @@ export const SETTINGS_PAGES = [
118
118
  { id: 'stg-sec-tool-permissions', key: 'tool_permissions', state: 'open' },
119
119
  ],
120
120
  },
121
+ {
122
+ id: 'code-editor', key: 'code_editor', group: 'tools', icon: 'editor',
123
+ panes: [{ id: 'editor' }],
124
+ sections: [
125
+ { id: 'stg-sec-editor-editing', key: 'editing', state: 'open' },
126
+ { id: 'stg-sec-editor-ai', key: 'ai_autocomplete', state: 'open' },
127
+ ],
128
+ },
121
129
  {
122
130
  id: 'automations', key: 'automations', group: 'automations', icon: 'hooks',
123
131
  panes: [{ id: 'hooks' }, { id: 'social' }, { id: 'youtube', titleKey: k('pane.youtube') }],
@@ -243,8 +251,10 @@ export const SETTINGS_CONTROLS = {
243
251
  'stg-sec-ml-server': 'ML012 ML013 ML014',
244
252
  'stg-sec-skills': 'SKL004 SKL001',
245
253
  'stg-sec-tool-permissions': 'PER012 PER001 PER002 PER003 PER004 PER005 PER006 PER007 PER008 PER009 PER010 PER011',
246
- 'stg-sec-greetings': 'AUT001 AUT002 AUT003 AUT047 AUT004 AUT005 AUT009 AUT010 AUT011 AUT012 AUT013 AUT014 AUT015 AUT016 AUT017 AUT018 AUT019 AUT020 AUT021 AUT022 AUT026 AUT027',
247
- 'stg-sec-automation-behavior': 'AUT046 AUT036 AUT037 AUT038 AUT039 AUT040 AUT041 AUT028 AUT029 AUT030 AUT031 AUT032 AUT033 AUT034 AUT035',
254
+ 'stg-sec-editor-editing': 'EDT014 EDT015 EDT016 EDT017 EDT018 EDT019 EDT020 EDT021 EDT022 EDT023',
255
+ 'stg-sec-editor-ai': 'EDT001 EDT002 EDT003 EDT004 EDT005 EDT006 EDT007 EDT008 EDT009 EDT010 EDT011 EDT012 EDT013',
256
+ 'stg-sec-greetings': 'AUT058 AUT059 AUT060',
257
+ 'stg-sec-automation-behavior': 'AUT053 AUT054 AUT055 AUT056 AUT057 AUT046 AUT036 AUT037 AUT038 AUT039 AUT040 AUT041 AUT028 AUT029 AUT030 AUT031 AUT032 AUT033 AUT034 AUT035',
248
258
  'stg-sec-online-access': 'AUT048 AUT042 AUT043 AUT044 AUT045 AUT049 AUT050 AUT051 AUT052',
249
259
  'stg-sec-social': 'SOC003 SOC001 SOC002',
250
260
  'stg-sec-youtube': 'YT017 YT001 YT002 YT003 YT004 YT005 YT006 YT007 YT008 YT009 YT010 YT011 YT012 YT013 YT014 YT015 YT016 YT018',
@@ -326,9 +336,13 @@ export const SETTINGS_TAB_ALIASES = {
326
336
 
327
337
  // Old `expand` / `highlight` / `scrollTo` ids whose element was merged into another
328
338
  // section. Ids that still exist in the markup (setup-claude, jv-sec-log, wa-sec-setup,
329
- // recall-controls, cc-greeting-config, bridge-openclaw, project-storage-manager,
330
- // cc-cli-paths …) need no entry: they resolve to themselves.
339
+ // recall-controls, bridge-openclaw, project-storage-manager, cc-cli-paths …) need
340
+ // no entry: they resolve to themselves.
331
341
  export const SETTINGS_SECTION_ALIASES = {
342
+ // The three greeting editors became the one Greeting section (lib/greeting/).
343
+ 'cc-greeting-config': 'stg-sec-greetings',
344
+ 'codex-greeting-config': 'stg-sec-greetings',
345
+ 'opencode-greeting-config': 'stg-sec-greetings',
332
346
  'notif-sources-section': 'stg-sec-when',
333
347
  'notif-triggers-section': 'stg-sec-when',
334
348
  'notif-sound-section': 'stg-sec-sounds',