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,87 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — File Explorer: names
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // What a file or folder may be called on the system the server runs on, how
6
+ // two names are ordered, and the names the explorer makes up itself (a copy's).
7
+ // No imports and no page: the server reads the same rules
8
+ // (lib/file-explorer-api.js), the way it reads the editor's preferences, so
9
+ // page and server cannot disagree about a name.
10
+
11
+ /** The longest name most filesystems take, in UTF-8 bytes. */
12
+ export const MAX_NAME_BYTES = 255;
13
+
14
+ // Device names Windows keeps for itself, with or without an extension.
15
+ const WINDOWS_RESERVED = /^(con|prn|aux|nul|com[0-9¹²³]|lpt[0-9¹²³])(\..*)?$/i;
16
+ const WINDOWS_CHARS = /[<>:"|?*]/;
17
+ // eslint-disable-next-line no-control-regex
18
+ const CONTROL = /[\u0000-\u001f\u007f]/;
19
+
20
+ const byteLength = (text) => new TextEncoder().encode(text).length;
21
+
22
+ /**
23
+ * Why a name cannot be used, or null when it can.
24
+ * @param {string} name one path segment, as it would be written to disk
25
+ * @param {{ platform?: string }} [o] `process.platform` of the machine that holds the files
26
+ * @returns {null | 'empty' | 'dots' | 'separator' | 'control' | 'reserved' | 'chars' | 'trailing' | 'long'}
27
+ */
28
+ export function validateName(name, { platform = 'posix' } = {}) {
29
+ if (typeof name !== 'string' || name.length === 0 || name.trim().length === 0) return 'empty';
30
+ if (name === '.' || name === '..') return 'dots';
31
+ if (/[\\/]/.test(name)) return 'separator';
32
+ if (CONTROL.test(name)) return 'control';
33
+ if (platform === 'win32') {
34
+ if (WINDOWS_CHARS.test(name)) return 'chars';
35
+ if (WINDOWS_RESERVED.test(name)) return 'reserved';
36
+ if (/[ .]$/.test(name)) return 'trailing';
37
+ }
38
+ if (byteLength(name) > MAX_NAME_BYTES) return 'long';
39
+ return null;
40
+ }
41
+
42
+ const collator = new Intl.Collator('en', { numeric: true, sensitivity: 'base' });
43
+
44
+ /** Natural, case-insensitive order: `file2` before `file10`, `a` next to `A`. Stable for names that only differ in case. */
45
+ export function compareNames(a, b) {
46
+ return collator.compare(a, b) || (a < b ? -1 : a > b ? 1 : 0);
47
+ }
48
+
49
+ /**
50
+ * A name as a stem and its extension. A dotfile has no extension (`.env`), and only the last one counts
51
+ * (`archive.tar.gz` → `archive.tar` + `.gz`).
52
+ * @returns {{ stem: string, ext: string }}
53
+ */
54
+ export function splitName(name) {
55
+ const text = String(name ?? '');
56
+ const dot = text.lastIndexOf('.');
57
+ if (dot <= 0) return { stem: text, ext: '' };
58
+ return { stem: text.slice(0, dot), ext: text.slice(dot) };
59
+ }
60
+
61
+ /** What a rename selects at first: the name without its extension (all of a folder's name). */
62
+ export function renameSelection(name, isDir = false) {
63
+ return [0, isDir ? String(name).length : splitName(name).stem.length];
64
+ }
65
+
66
+ /**
67
+ * The name of a copy that sits next to its original: `notes copy.md`, then `notes copy 2.md`, …
68
+ * @param {string} name
69
+ * @param {(candidate: string) => boolean} taken whether a name is in use in that folder
70
+ * @param {{ isDir?: boolean }} [o] a folder's name has no extension
71
+ */
72
+ export function copyName(name, taken, { isDir = false } = {}) {
73
+ const { stem, ext } = isDir ? { stem: String(name), ext: '' } : splitName(name);
74
+ // A copy of a copy counts on: `notes copy.md` → `notes copy 2.md`.
75
+ const again = /^(.*) copy(?: (\d+))?$/.exec(stem);
76
+ const base = again ? again[1] : stem;
77
+ for (let n = again ? Number(again[2] || 1) + 1 : 1; n < 10000; n++) {
78
+ const candidate = `${base} copy${n > 1 ? ` ${n}` : ''}${ext}`;
79
+ if (!taken(candidate)) return candidate;
80
+ }
81
+ return `${base} copy ${Date.now()}${ext}`;
82
+ }
83
+
84
+ /** Whether two names are one entry on a disk that does not tell case apart (macOS and Windows by default). */
85
+ export function sameNameIgnoringCase(a, b) {
86
+ return String(a).normalize('NFC').toLowerCase() === String(b).normalize('NFC').toLowerCase();
87
+ }
@@ -0,0 +1,370 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — File Explorer: what it does to files
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // New file, new folder, rename, duplicate, move, copy, paste, delete, files
6
+ // dropped from the computer, copy path, reveal. Each asks first where
7
+ // something could be lost, and says in the panel what happened: all of it, or
8
+ // how far it got, where it stopped and why, in plain words. It keeps the editor
9
+ // right: the tab of a renamed or moved file follows it as it is (text, caret,
10
+ // undo history), and unsaved text of a file that is about to be replaced is
11
+ // saved first.
12
+ // The controller (fx-main.js) hands in what this module needs of it.
13
+
14
+ import { renameSelection, sameNameIgnoringCase, validateName } from './fx-names.js';
15
+ import { baseName, canDropInto, dirName, failureMessage, relativeTo, stoppedMessage, topLevelOf } from './fx-logic.js';
16
+ import { confirmDialog } from './fx-popups.js';
17
+
18
+ export const MAX_UPLOAD_FILES = 2000;
19
+
20
+ /**
21
+ * @param {object} c
22
+ * @param {ReturnType<import('./fx-api.js').createExplorerApi>} c.api
23
+ * @param {(key: string, params?: object) => string} c.t @param {(key: string, count: number, params?: object) => string} c.tp
24
+ * @param {object} c.tree fx-tree.js @param {object} c.notice fx-popups.js createNotice
25
+ * @param {object} c.editor fx-links.js createEditorLink @param {(path: string, at?: object) => void} c.openFile
26
+ * @param {() => string | null} c.root the project's folder
27
+ * @param {(dir: string) => object[] | null} c.itemsIn what a folder holds, when it was read
28
+ * @param {(dir: string) => Promise<boolean>} c.openFolder open a folder in the tree and wait for its rows
29
+ * @param {(dirs: string[]) => Promise<void>} c.refresh read folders again and draw them
30
+ * @param {(pairs: { from: string, to: string }[]) => void} c.moved entries have another path now
31
+ * @param {{ get: () => object | null, set: (value: object | null) => void }} c.clipboard what was cut or copied
32
+ */
33
+ export function createOps({ api, t, tp, tree, notice, editor, openFile, root, itemsIn, openFolder, refresh, moved, clipboard }) {
34
+ const platform = () => api.abilities().platform || 'posix';
35
+ const trashWord = () => (platform() === 'win32' ? 'win32' : 'other');
36
+ const fail = (failure) => { const message = failureMessage(failure); notice.show(t(message.key, message.params), { tone: 'error' }); };
37
+ // A batch that did not go through: how far it got, where it stopped and why; and when a copy was begun and is
38
+ // not whole, that what is there of it is only a part.
39
+ const sayStopped = (what, { done, total, failure }) => {
40
+ const message = stoppedMessage(what, { done, total, failure, trash: trashWord() });
41
+ const said = t(message.key, message.why ? { ...message.params, why: t(message.why.key, message.why.params) } : message.params);
42
+ notice.show(message.partial ? `${said} ${t(message.partial.key, message.partial.params)}` : said, { tone: 'error' });
43
+ };
44
+ const folderName = (dir) => (dir === root() ? baseName(dir) : baseName(dir));
45
+ const cancelLabel = () => t('common.cancel');
46
+
47
+ /** Why a name cannot be used in a folder, as a sentence, or null. */
48
+ function nameProblem(name, dir, except = null) {
49
+ const why = validateName(name, { platform: platform() });
50
+ if (why) return t(failureMessage({ code: 'invalid-name', reason: why }).key, { name });
51
+ const taken = (itemsIn(dir) || []).some((item) => item.name !== except && (item.name === name || sameNameIgnoringCase(item.name, name)));
52
+ return taken ? t('fileExplorer.edit.taken', { name }) : null;
53
+ }
54
+
55
+ /**
56
+ * Ask before entries are replaced: where what is there goes, and that unsaved text in it is saved first (what
57
+ * goes to the Trash is then what was typed). A renamed or moved file needs no such question: its tab follows.
58
+ * @param {string[]} dests the paths that would be replaced @param {string} into the folder they are in
59
+ * @param {{ forGood?: boolean }} [o] asked again: the Trash cannot take them
60
+ * @returns {Promise<boolean>} false when it was given up, or something could not be saved
61
+ */
62
+ async function askReplace(dests, into, { forGood = false } = {}) {
63
+ const unsaved = editor.unsavedIn(dests);
64
+ const toTrash = !forGood && api.abilities().trash;
65
+ const answer = await confirmDialog({
66
+ title: t(forGood ? 'fileExplorer.delete.noTrashTitle' : 'fileExplorer.replace.title'),
67
+ lines: [
68
+ dests.length === 1 ? t('fileExplorer.replace.one', { name: baseName(dests[0]), folder: folderName(into) }) : t('fileExplorer.replace.many', { count: dests.length, folder: folderName(into) }),
69
+ t(toTrash ? `fileExplorer.replace.toTrash.${trashWord()}` : 'fileExplorer.replace.forGood'),
70
+ unsaved.length ? tp('fileExplorer.replace.unsaved', unsaved.length, { name: baseName(unsaved[0]) }) : '',
71
+ ],
72
+ confirmLabel: t('fileExplorer.replace.confirm'),
73
+ cancelLabel: cancelLabel(),
74
+ danger: true,
75
+ });
76
+ if (answer !== 'confirm') return false;
77
+ for (const file of unsaved) {
78
+ if (!(await editor.saveFile(file))) {
79
+ notice.show(t('fileExplorer.replace.notSaved', { name: baseName(file) }), { tone: 'error' });
80
+ return false;
81
+ }
82
+ }
83
+ return true;
84
+ }
85
+
86
+ /** Type the name of a new file or folder in `parent`, then make it. */
87
+ async function create(kind, parent) {
88
+ const dir = parent || root();
89
+ if (!dir || !(await openFolder(dir))) return;
90
+ tree.startEdit({
91
+ parent: dir,
92
+ kind,
93
+ label: t(kind === 'dir' ? 'fileExplorer.edit.newFolder' : 'fileExplorer.edit.newFile'),
94
+ validate: (name) => nameProblem(name, dir),
95
+ async commit(name) {
96
+ const made = await api.create(dir, name, kind);
97
+ if (!made.ok) { const message = failureMessage({ ...made, name }); return { ok: false, message: t(message.key, message.params) }; }
98
+ await refresh([dir]);
99
+ tree.select([made.path], { focus: made.path });
100
+ tree.focus();
101
+ if (kind === 'file') openFile(made.path, { pin: true });
102
+ return { ok: true };
103
+ },
104
+ });
105
+ }
106
+
107
+ /** Rename a row in place: the name without its extension starts selected. */
108
+ function rename(path) {
109
+ if (!path || path === root()) { fail({ code: 'root' }); return; }
110
+ if (!api.abilities().ops) { fail({ code: 'unsupported' }); return; }
111
+ const item = tree.itemOf(path);
112
+ if (!item) return;
113
+ const dir = dirName(path);
114
+ tree.startEdit({
115
+ path,
116
+ value: item.name,
117
+ select: renameSelection(item.name, item.type === 'dir'),
118
+ label: t('fileExplorer.edit.rename', { name: item.name }),
119
+ validate: (name) => nameProblem(name, dir, item.name),
120
+ async commit(name) {
121
+ const done = await api.rename(path, name);
122
+ if (!done.ok) { const message = failureMessage({ ...done, name }); return { ok: false, message: t(message.key, message.params) }; }
123
+ // The name was kept: the row it was typed in gives way to the row of the new name, and is not a file that went away.
124
+ tree.settleEdit();
125
+ const pairs = [{ from: path, to: done.path }];
126
+ moved(pairs);
127
+ editor.follow(pairs);
128
+ await refresh([dir]);
129
+ tree.select([done.path], { focus: done.path });
130
+ tree.focus();
131
+ return { ok: true };
132
+ },
133
+ });
134
+ }
135
+
136
+ /** Move or copy rows into a folder. A name that is taken is asked about; nothing is replaced unasked. */
137
+ async function transfer(paths, into, { copy = false } = {}) {
138
+ const list = topLevelOf(paths).filter((path) => path !== root());
139
+ if (!list.length || !into) return false;
140
+ if (!api.abilities().ops) { fail({ code: 'unsupported' }); return false; }
141
+ if (!canDropInto(list, into, { copy })) {
142
+ if (list.some((path) => into === path || relativeTo(path, into) !== null)) fail({ code: 'into-self' });
143
+ else notice.show(t('fileExplorer.move.same'));
144
+ return false;
145
+ }
146
+ const call = copy ? api.copy : api.move;
147
+ const pairs = [];
148
+ let left = list;
149
+ // What a request did, whether it went through or stopped midway: the tree and the editor's tabs follow at once.
150
+ const take = (answer) => {
151
+ const did = (copy ? answer.copied : answer.moved) || [];
152
+ if (!did.length) return;
153
+ pairs.push(...did);
154
+ left = left.filter((path) => !did.some((pair) => pair.from === path));
155
+ if (!copy) { moved(did); editor.follow(did); }
156
+ };
157
+ const destsOf = (names) => names.filter((name) => left.some((path) => baseName(path) === name)).map((name) => `${into}/${name}`);
158
+ // What may be replaced is said by name: the names that were asked about, and no other. A name that is taken by
159
+ // the time the server looks, and was not asked about, comes back as a conflict and is asked about too.
160
+ const replacing = () => ({ replace: [...taken], permanent: !api.abilities().trash });
161
+ // A name that is taken in a folder that was read is asked about before the server is: it refuses all the same
162
+ // (409) when one turns up that the page did not know of.
163
+ const there = itemsIn(into) || [];
164
+ let taken = list.filter((path) => dirName(path) !== into).map(baseName).filter((name) => there.some((item) => item.name === name));
165
+ let options = {};
166
+ if (taken.length) {
167
+ if (!(await askReplace(destsOf(taken), into))) return false;
168
+ options = replacing();
169
+ }
170
+ let done = await call(left, into, options);
171
+ take(done);
172
+ let givenUp = false;
173
+ // Each round asks about names nobody was asked about yet; a file that keeps turning up is not asked about for ever.
174
+ for (let round = 0; round < 5 && !done.ok && done.code === 'exists' && done.conflicts?.some((name) => !taken.includes(name)); round++) {
175
+ const fresh = done.conflicts.filter((name) => !taken.includes(name));
176
+ if (!(await askReplace(destsOf(fresh), into))) { givenUp = true; break; }
177
+ taken = [...taken, ...fresh];
178
+ options = replacing();
179
+ done = await call(left, into, options);
180
+ take(done);
181
+ }
182
+ if (!givenUp && !done.ok && done.code === 'no-trash' && !options.permanent) {
183
+ // The Trash cannot take what is in the way. Nothing of it was removed: asked again, in the words for that.
184
+ const still = destsOf(taken);
185
+ if (await askReplace(still.length ? still : [`${into}/${baseName(done.failed || left[0])}`], into, { forGood: true })) {
186
+ done = await call(left, into, { replace: [...taken], permanent: true });
187
+ take(done);
188
+ } else {
189
+ givenUp = true;
190
+ }
191
+ }
192
+ const dirs = [...new Set([into, ...list.map(dirName)])];
193
+ if (pairs.length) await openFolder(into);
194
+ await refresh(dirs);
195
+ const landed = pairs.map((pair) => pair.to);
196
+ if (landed.length) tree.select(landed, { focus: landed[0] });
197
+ if (!done.ok) {
198
+ // Given up before anything was done: nothing to say. Otherwise: how far it got, where it stopped and why.
199
+ if (!(givenUp && !pairs.length)) sayStopped(copy ? 'copy' : 'move', { done: pairs.length, total: list.length, failure: done });
200
+ return false;
201
+ }
202
+ if (pairs.length) notice.show(tp(copy ? 'fileExplorer.copy.done' : 'fileExplorer.move.done', pairs.length, { name: baseName(pairs[0].to), folder: folderName(into) }), { tone: 'ok' });
203
+ return true;
204
+ }
205
+
206
+ /** A copy of each row next to it. */
207
+ async function duplicate(paths) {
208
+ const list = topLevelOf(paths).filter((path) => path !== root());
209
+ if (!list.length) return;
210
+ if (!api.abilities().ops) { fail({ code: 'unsupported' }); return; }
211
+ const made = [];
212
+ let failure = null;
213
+ for (const dir of new Set(list.map(dirName))) {
214
+ const done = await api.copy(list.filter((path) => dirName(path) === dir), dir);
215
+ made.push(...(done.copied || []).map((pair) => pair.to));
216
+ if (!done.ok) { failure = done; break; }
217
+ }
218
+ await refresh([...new Set(list.map(dirName))]);
219
+ if (made.length) tree.select(made, { focus: made[0] });
220
+ if (failure) sayStopped('copy', { done: made.length, total: list.length, failure });
221
+ else if (made.length) notice.show(tp('fileExplorer.duplicate.done', made.length, { name: baseName(made[0]) }), { tone: 'ok' });
222
+ }
223
+
224
+ function cut(paths, mode = 'cut') {
225
+ const list = topLevelOf(paths).filter((path) => path !== root());
226
+ if (!list.length) return;
227
+ if (!api.abilities().ops) { fail({ code: 'unsupported' }); return; }
228
+ clipboard.set({ mode, paths: list });
229
+ notice.show(tp(mode === 'cut' ? 'fileExplorer.clipboard.cut' : 'fileExplorer.clipboard.copied', list.length, { name: baseName(list[0]) }));
230
+ }
231
+
232
+ /** Put what was cut or copied into a folder. */
233
+ async function paste(into) {
234
+ const held = clipboard.get();
235
+ if (!held) { notice.show(t('fileExplorer.menu.nothingCopied')); return; }
236
+ const target = into || root();
237
+ const done = await transfer(held.paths, target, { copy: held.mode === 'copy' });
238
+ if (held.mode !== 'cut') return;
239
+ // What was cut and has moved is no longer cut; after a move that stopped midway, the rest still is.
240
+ const now = clipboard.get();
241
+ const rest = done || !now ? [] : now.paths.filter((path) => dirName(path) !== target);
242
+ clipboard.set(rest.length ? { ...now, paths: rest } : null);
243
+ }
244
+
245
+ /** Delete rows: to the Trash where there is one, and only after the question says which it will be. */
246
+ async function remove(paths) {
247
+ const list = topLevelOf(paths).filter((path) => path !== root());
248
+ if (!list.length) { if (paths.includes(root())) fail({ code: 'root' }); return; }
249
+ const can = api.abilities();
250
+ const name = baseName(list[0]);
251
+ const unsaved = editor.unsavedIn(list);
252
+ const asked = await confirmDialog({
253
+ title: list.length === 1 ? t('fileExplorer.delete.titleOne', { name }) : t('fileExplorer.delete.titleMany', { count: list.length }),
254
+ lines: [
255
+ can.trash ? t(`fileExplorer.delete.toTrash.${trashWord()}`) : t(can.ops ? 'fileExplorer.delete.forGood' : 'fileExplorer.delete.forGoodLegacy'),
256
+ unsaved.length ? tp('fileExplorer.delete.unsaved', unsaved.length, { name: baseName(unsaved[0]) }) : '',
257
+ ],
258
+ confirmLabel: can.trash ? t(`fileExplorer.delete.moveToTrash.${trashWord()}`) : t('fileExplorer.delete.deleteForever'),
259
+ cancelLabel: cancelLabel(),
260
+ danger: true,
261
+ });
262
+ if (asked !== 'confirm') return;
263
+ const dirs = [...new Set(list.map(dirName))];
264
+ let done = await api.remove(list, { permanent: !can.trash });
265
+ let trashed = !!done.trashed;
266
+ let round = { what: can.trash ? 'trash' : 'delete', total: list.length };
267
+ if (!done.ok && done.code === 'no-trash') {
268
+ // That path has no Trash after all (another disk): it was not removed, and the question is asked again.
269
+ const left = list.filter((path) => !(done.deleted || []).includes(path));
270
+ await refresh(dirs);
271
+ const again = await confirmDialog({
272
+ title: t('fileExplorer.delete.noTrashTitle'),
273
+ lines: [left.length === 1 ? t('fileExplorer.delete.noTrashOne', { name: baseName(left[0]) }) : t('fileExplorer.delete.noTrashMany', { count: left.length })],
274
+ confirmLabel: t('fileExplorer.delete.deleteForever'),
275
+ cancelLabel: cancelLabel(),
276
+ danger: true,
277
+ });
278
+ if (again !== 'confirm') {
279
+ // What had gone to the Trash before it is said; the rest is where it was.
280
+ if (done.deleted?.length) sayStopped('trash', { done: done.deleted.length, total: list.length, failure: done });
281
+ return;
282
+ }
283
+ done = await api.remove(left, { permanent: true });
284
+ trashed = false;
285
+ round = { what: 'delete', total: left.length };
286
+ }
287
+ await refresh(dirs);
288
+ if (!done.ok) {
289
+ if (done.code === 'not-empty') notice.show(t('fileExplorer.delete.notEmpty'), { tone: 'error' });
290
+ else sayStopped(round.what, { done: (done.deleted || []).length, total: round.total, failure: done });
291
+ return;
292
+ }
293
+ const held = clipboard.get();
294
+ if (held && held.paths.some((path) => list.some((gone) => relativeTo(gone, path) !== null))) clipboard.set(null);
295
+ notice.show(tp(trashed ? `fileExplorer.delete.doneTrash.${trashWord()}` : 'fileExplorer.delete.done', list.length, { name }), { tone: 'ok' });
296
+ tree.focus();
297
+ }
298
+
299
+ // The files of a drop, read while the drop event is still there: a folder is walked, with its own folders.
300
+ function gather(transfer) {
301
+ const entries = [...(transfer.items || [])].filter((item) => item.kind === 'file').map((item) => item.webkitGetAsEntry?.() || item.getAsFile?.()).filter(Boolean);
302
+ if (!entries.length) return Promise.resolve([...(transfer.files || [])].map((file) => ({ file, rel: '' })));
303
+ const out = [];
304
+ const walk = async (entry, rel) => {
305
+ if (out.length > MAX_UPLOAD_FILES) return;
306
+ if (typeof File !== 'undefined' && entry instanceof File) { out.push({ file: entry, rel }); return; }
307
+ if (entry.isFile) { out.push({ file: await new Promise((done, failed) => entry.file(done, failed)), rel }); return; }
308
+ if (!entry.isDirectory) return;
309
+ const reader = entry.createReader();
310
+ const inside = rel ? `${rel}/${entry.name}` : entry.name;
311
+ for (;;) {
312
+ const batch = await new Promise((done, failed) => reader.readEntries(done, failed));
313
+ if (!batch.length) break;
314
+ for (const child of batch) await walk(child, inside);
315
+ }
316
+ };
317
+ return (async () => { for (const entry of entries) await walk(entry, ''); return out; })();
318
+ }
319
+
320
+ /** Files dropped from the computer are copied into a folder. */
321
+ async function upload(transfer, into) {
322
+ if (!api.abilities().ops) { fail({ code: 'unsupported' }); return; }
323
+ let files;
324
+ try { files = await gather(transfer); } catch { files = []; }
325
+ if (!files.length) { notice.show(t('fileExplorer.upload.none')); return; }
326
+ if (files.length > MAX_UPLOAD_FILES) { notice.show(t('fileExplorer.upload.tooMany', { max: MAX_UPLOAD_FILES }), { tone: 'error' }); return; }
327
+ const clashes = []; // a name that is taken: asked about
328
+ const noTrash = []; // the Trash could not take what was there: asked about again
329
+ let sent = 0; let failure = null;
330
+ const send = async (list, options = {}) => {
331
+ for (const entry of list) {
332
+ notice.show(t('fileExplorer.upload.progress', { done: sent + 1, total: files.length }), { ms: 0 });
333
+ const done = await api.upload(entry.file, into, { rel: entry.rel, ...options });
334
+ if (done.ok) { sent++; continue; }
335
+ if (done.code === 'exists' && !options.replace) { clashes.push(entry); continue; }
336
+ if (done.code === 'no-trash' && !options.permanent) { noTrash.push(entry); continue; }
337
+ failure = { ...done, name: entry.file.name };
338
+ return;
339
+ }
340
+ };
341
+ const destOf = (entry) => [into, entry.rel, entry.file.name].filter(Boolean).join('/');
342
+ await send(files);
343
+ // What is replaced goes to the Trash, as a move's does; only what the Trash cannot take is asked about again.
344
+ if (!failure && clashes.length && (await askReplace(clashes.map(destOf), into))) await send(clashes, { replace: true, permanent: !api.abilities().trash });
345
+ if (!failure && noTrash.length && (await askReplace(noTrash.map(destOf), into, { forGood: true }))) await send(noTrash, { replace: true, permanent: true });
346
+ await openFolder(into);
347
+ await refresh([into]);
348
+ if (failure) { sayStopped('copy', { done: sent, total: files.length, failure }); return; }
349
+ if (sent) notice.show(tp('fileExplorer.upload.done', sent, { name: files[0].file.name, folder: folderName(into) }), { tone: 'ok' });
350
+ else notice.clear();
351
+ }
352
+
353
+ async function write(text, said) {
354
+ try { await navigator.clipboard.writeText(text); notice.show(said, { tone: 'ok', ms: 2500 }); }
355
+ catch { notice.show(t('fileExplorer.copied.failed'), { tone: 'error' }); }
356
+ }
357
+ const copyPath = (paths, { relative = false } = {}) => {
358
+ if (!paths.length) return;
359
+ const lines = paths.map((path) => (relative ? relativeTo(root(), path) || '.' : path));
360
+ write(lines.join('\n'), tp('fileExplorer.copied.path', paths.length));
361
+ };
362
+ const copyName = (path) => write(baseName(path), t('fileExplorer.copied.name'));
363
+
364
+ async function reveal(path) {
365
+ const done = await api.reveal(path);
366
+ if (!done.ok) fail(done);
367
+ }
368
+
369
+ return { create, rename, transfer, duplicate, cut, copy: (paths) => cut(paths, 'copy'), paste, remove, upload, copyPath, copyName, reveal, nameProblem };
370
+ }