synabun 2.0.5 → 2.0.9

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 (527) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +2 -1
  3. package/THIRD-PARTY-LICENSES.md +13 -0
  4. package/docs/whatsapp.md +151 -16
  5. package/hooks/claude-code/post-plan.mjs +6 -30
  6. package/hooks/claude-code/session-start.mjs +1 -4
  7. package/hooks/claude-code/shared.mjs +112 -75
  8. package/lib/child-path.js +303 -0
  9. package/lib/claude-hooks.js +2 -1
  10. package/lib/data-home-migration.js +2 -0
  11. package/lib/desktop-app.js +265 -0
  12. package/lib/external-tools.js +7 -3
  13. package/lib/log-redact.js +102 -0
  14. package/lib/package-processes.js +670 -0
  15. package/lib/packaged-runtime.js +138 -11
  16. package/lib/runtime-dependencies.js +225 -0
  17. package/mcp-server/dist/http.d.ts +3 -1
  18. package/mcp-server/dist/http.d.ts.map +1 -1
  19. package/mcp-server/dist/http.js +15 -4
  20. package/mcp-server/dist/http.js.map +1 -1
  21. package/mcp-server/dist/index.d.ts.map +1 -1
  22. package/mcp-server/dist/index.js +9 -6
  23. package/mcp-server/dist/index.js.map +1 -1
  24. package/mcp-server/dist/services/browser-bench.d.ts +1 -1
  25. package/mcp-server/dist/services/browser-bench.d.ts.map +1 -1
  26. package/mcp-server/dist/services/browser-bench.js +1 -1
  27. package/mcp-server/dist/services/browser-bench.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/categories.d.ts +35 -2
  33. package/mcp-server/dist/services/categories.d.ts.map +1 -1
  34. package/mcp-server/dist/services/categories.js +77 -33
  35. package/mcp-server/dist/services/categories.js.map +1 -1
  36. package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
  37. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  38. package/mcp-server/dist/services/codex-config-heal.js +41 -4
  39. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  40. package/mcp-server/dist/services/desktop-bench.d.ts +7 -7
  41. package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -1
  42. package/mcp-server/dist/services/desktop-bench.js +1 -1
  43. package/mcp-server/dist/services/desktop-bench.js.map +1 -1
  44. package/mcp-server/dist/services/identity.d.ts +11 -0
  45. package/mcp-server/dist/services/identity.d.ts.map +1 -1
  46. package/mcp-server/dist/services/identity.js +24 -0
  47. package/mcp-server/dist/services/identity.js.map +1 -1
  48. package/mcp-server/dist/services/neural-interface.d.ts +13 -0
  49. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  50. package/mcp-server/dist/services/neural-interface.js +34 -2
  51. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  52. package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
  53. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
  54. package/mcp-server/dist/services/typesafe-config.js +4 -1
  55. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  56. package/mcp-server/dist/tools/agents.d.ts +11 -8
  57. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  58. package/mcp-server/dist/tools/agents.js +35 -14
  59. package/mcp-server/dist/tools/agents.js.map +1 -1
  60. package/mcp-server/dist/tools/bluesky.js +1 -1
  61. package/mcp-server/dist/tools/bluesky.js.map +1 -1
  62. package/mcp-server/dist/tools/browser-advanced.d.ts +1 -1
  63. package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -1
  64. package/mcp-server/dist/tools/browser-advanced.js +1 -1
  65. package/mcp-server/dist/tools/browser-advanced.js.map +1 -1
  66. package/mcp-server/dist/tools/browser-batch.d.ts +1 -1
  67. package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -1
  68. package/mcp-server/dist/tools/browser-batch.js +1 -1
  69. package/mcp-server/dist/tools/browser-batch.js.map +1 -1
  70. package/mcp-server/dist/tools/browser-cheatsheet.d.ts +1 -1
  71. package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -1
  72. package/mcp-server/dist/tools/browser-cheatsheet.js +1 -1
  73. package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -1
  74. package/mcp-server/dist/tools/browser-interact.d.ts +1 -1
  75. package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -1
  76. package/mcp-server/dist/tools/browser-interact.js +1 -1
  77. package/mcp-server/dist/tools/browser-interact.js.map +1 -1
  78. package/mcp-server/dist/tools/browser-navigate.d.ts +1 -1
  79. package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -1
  80. package/mcp-server/dist/tools/browser-navigate.js +1 -1
  81. package/mcp-server/dist/tools/browser-navigate.js.map +1 -1
  82. package/mcp-server/dist/tools/browser-observe.d.ts +1 -1
  83. package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -1
  84. package/mcp-server/dist/tools/browser-observe.js +1 -1
  85. package/mcp-server/dist/tools/browser-observe.js.map +1 -1
  86. package/mcp-server/dist/tools/card-tools.d.ts +1 -1
  87. package/mcp-server/dist/tools/card-tools.d.ts.map +1 -1
  88. package/mcp-server/dist/tools/card-tools.js +1 -1
  89. package/mcp-server/dist/tools/card-tools.js.map +1 -1
  90. package/mcp-server/dist/tools/category.d.ts +1 -1
  91. package/mcp-server/dist/tools/category.d.ts.map +1 -1
  92. package/mcp-server/dist/tools/category.js +5 -1
  93. package/mcp-server/dist/tools/category.js.map +1 -1
  94. package/mcp-server/dist/tools/choice.d.ts +1 -1
  95. package/mcp-server/dist/tools/choice.d.ts.map +1 -1
  96. package/mcp-server/dist/tools/choice.js +1 -1
  97. package/mcp-server/dist/tools/choice.js.map +1 -1
  98. package/mcp-server/dist/tools/computer.d.ts +1 -1
  99. package/mcp-server/dist/tools/computer.d.ts.map +1 -1
  100. package/mcp-server/dist/tools/computer.js +1 -1
  101. package/mcp-server/dist/tools/computer.js.map +1 -1
  102. package/mcp-server/dist/tools/discord-channel.d.ts +1 -1
  103. package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -1
  104. package/mcp-server/dist/tools/discord-channel.js +1 -1
  105. package/mcp-server/dist/tools/discord-channel.js.map +1 -1
  106. package/mcp-server/dist/tools/discord-guild.d.ts +1 -1
  107. package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -1
  108. package/mcp-server/dist/tools/discord-guild.js +1 -1
  109. package/mcp-server/dist/tools/discord-guild.js.map +1 -1
  110. package/mcp-server/dist/tools/discord-member.d.ts +1 -1
  111. package/mcp-server/dist/tools/discord-member.d.ts.map +1 -1
  112. package/mcp-server/dist/tools/discord-member.js +1 -1
  113. package/mcp-server/dist/tools/discord-member.js.map +1 -1
  114. package/mcp-server/dist/tools/discord-message.d.ts +1 -1
  115. package/mcp-server/dist/tools/discord-message.d.ts.map +1 -1
  116. package/mcp-server/dist/tools/discord-message.js +1 -1
  117. package/mcp-server/dist/tools/discord-message.js.map +1 -1
  118. package/mcp-server/dist/tools/discord-onboarding.d.ts +1 -1
  119. package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -1
  120. package/mcp-server/dist/tools/discord-onboarding.js +1 -1
  121. package/mcp-server/dist/tools/discord-onboarding.js.map +1 -1
  122. package/mcp-server/dist/tools/discord-role.d.ts +1 -1
  123. package/mcp-server/dist/tools/discord-role.d.ts.map +1 -1
  124. package/mcp-server/dist/tools/discord-role.js +1 -1
  125. package/mcp-server/dist/tools/discord-role.js.map +1 -1
  126. package/mcp-server/dist/tools/discord-thread.d.ts +1 -1
  127. package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -1
  128. package/mcp-server/dist/tools/discord-thread.js +1 -1
  129. package/mcp-server/dist/tools/discord-thread.js.map +1 -1
  130. package/mcp-server/dist/tools/discord-webhook.d.ts +1 -1
  131. package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -1
  132. package/mcp-server/dist/tools/discord-webhook.js +1 -1
  133. package/mcp-server/dist/tools/discord-webhook.js.map +1 -1
  134. package/mcp-server/dist/tools/fb-groups.d.ts +1 -1
  135. package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -1
  136. package/mcp-server/dist/tools/fb-groups.js +1 -1
  137. package/mcp-server/dist/tools/fb-groups.js.map +1 -1
  138. package/mcp-server/dist/tools/forget.d.ts +1 -1
  139. package/mcp-server/dist/tools/forget.d.ts.map +1 -1
  140. package/mcp-server/dist/tools/forget.js +1 -1
  141. package/mcp-server/dist/tools/forget.js.map +1 -1
  142. package/mcp-server/dist/tools/git-tools.d.ts +1 -1
  143. package/mcp-server/dist/tools/git-tools.d.ts.map +1 -1
  144. package/mcp-server/dist/tools/git-tools.js +1 -1
  145. package/mcp-server/dist/tools/git-tools.js.map +1 -1
  146. package/mcp-server/dist/tools/gsc/experience.d.ts +1 -1
  147. package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -1
  148. package/mcp-server/dist/tools/gsc/experience.js +1 -1
  149. package/mcp-server/dist/tools/gsc/experience.js.map +1 -1
  150. package/mcp-server/dist/tools/gsc/indexing.d.ts +1 -1
  151. package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -1
  152. package/mcp-server/dist/tools/gsc/indexing.js +1 -1
  153. package/mcp-server/dist/tools/gsc/indexing.js.map +1 -1
  154. package/mcp-server/dist/tools/gsc/inspect.d.ts +1 -1
  155. package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -1
  156. package/mcp-server/dist/tools/gsc/inspect.js +1 -1
  157. package/mcp-server/dist/tools/gsc/inspect.js.map +1 -1
  158. package/mcp-server/dist/tools/gsc/navigate.d.ts +1 -1
  159. package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -1
  160. package/mcp-server/dist/tools/gsc/navigate.js +1 -1
  161. package/mcp-server/dist/tools/gsc/navigate.js.map +1 -1
  162. package/mcp-server/dist/tools/gsc/performance.d.ts +1 -1
  163. package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -1
  164. package/mcp-server/dist/tools/gsc/performance.js +1 -1
  165. package/mcp-server/dist/tools/gsc/performance.js.map +1 -1
  166. package/mcp-server/dist/tools/gsc/settings.d.ts +1 -1
  167. package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -1
  168. package/mcp-server/dist/tools/gsc/settings.js +1 -1
  169. package/mcp-server/dist/tools/gsc/settings.js.map +1 -1
  170. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  171. package/mcp-server/dist/tools/image-tools.d.ts.map +1 -1
  172. package/mcp-server/dist/tools/image-tools.js +1 -1
  173. package/mcp-server/dist/tools/image-tools.js.map +1 -1
  174. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +1 -1
  175. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -1
  176. package/mcp-server/dist/tools/leonardo-browser-tools.js +1 -1
  177. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -1
  178. package/mcp-server/dist/tools/loop.d.ts +1 -1
  179. package/mcp-server/dist/tools/loop.d.ts.map +1 -1
  180. package/mcp-server/dist/tools/loop.js +1 -1
  181. package/mcp-server/dist/tools/loop.js.map +1 -1
  182. package/mcp-server/dist/tools/memories.d.ts +1 -1
  183. package/mcp-server/dist/tools/memories.d.ts.map +1 -1
  184. package/mcp-server/dist/tools/memories.js +1 -1
  185. package/mcp-server/dist/tools/memories.js.map +1 -1
  186. package/mcp-server/dist/tools/morelogin.d.ts +1 -1
  187. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
  188. package/mcp-server/dist/tools/morelogin.js +1 -1
  189. package/mcp-server/dist/tools/morelogin.js.map +1 -1
  190. package/mcp-server/dist/tools/profile.d.ts +1 -1
  191. package/mcp-server/dist/tools/profile.d.ts.map +1 -1
  192. package/mcp-server/dist/tools/profile.js +1 -1
  193. package/mcp-server/dist/tools/profile.js.map +1 -1
  194. package/mcp-server/dist/tools/recall.d.ts +1 -1
  195. package/mcp-server/dist/tools/recall.d.ts.map +1 -1
  196. package/mcp-server/dist/tools/recall.js +1 -1
  197. package/mcp-server/dist/tools/recall.js.map +1 -1
  198. package/mcp-server/dist/tools/reflect.d.ts +1 -1
  199. package/mcp-server/dist/tools/reflect.d.ts.map +1 -1
  200. package/mcp-server/dist/tools/reflect.js +1 -1
  201. package/mcp-server/dist/tools/reflect.js.map +1 -1
  202. package/mcp-server/dist/tools/remember.d.ts +1 -1
  203. package/mcp-server/dist/tools/remember.d.ts.map +1 -1
  204. package/mcp-server/dist/tools/remember.js +13 -11
  205. package/mcp-server/dist/tools/remember.js.map +1 -1
  206. package/mcp-server/dist/tools/restore.d.ts +1 -1
  207. package/mcp-server/dist/tools/restore.d.ts.map +1 -1
  208. package/mcp-server/dist/tools/restore.js +1 -1
  209. package/mcp-server/dist/tools/restore.js.map +1 -1
  210. package/mcp-server/dist/tools/style-guide-tools.d.ts +1 -1
  211. package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -1
  212. package/mcp-server/dist/tools/style-guide-tools.js +1 -1
  213. package/mcp-server/dist/tools/style-guide-tools.js.map +1 -1
  214. package/mcp-server/dist/tools/sync.d.ts +1 -1
  215. package/mcp-server/dist/tools/sync.d.ts.map +1 -1
  216. package/mcp-server/dist/tools/sync.js +1 -1
  217. package/mcp-server/dist/tools/sync.js.map +1 -1
  218. package/mcp-server/dist/tools/tictactoe-tools.d.ts +1 -1
  219. package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -1
  220. package/mcp-server/dist/tools/tictactoe-tools.js +1 -1
  221. package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -1
  222. package/mcp-server/dist/tools/utils.d.ts +1 -1
  223. package/mcp-server/dist/tools/utils.d.ts.map +1 -1
  224. package/mcp-server/dist/tools/utils.js +1 -1
  225. package/mcp-server/dist/tools/utils.js.map +1 -1
  226. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
  227. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
  228. package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
  229. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
  230. package/mcp-server/dist/tools/whiteboard-tools.d.ts +3 -3
  231. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  232. package/mcp-server/dist/tools/whiteboard-tools.js +1 -1
  233. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  234. package/mcp-server/dist/tools/youtube/analytics.d.ts +1 -1
  235. package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -1
  236. package/mcp-server/dist/tools/youtube/analytics.js +1 -1
  237. package/mcp-server/dist/tools/youtube/analytics.js.map +1 -1
  238. package/mcp-server/dist/tools/youtube/download.d.ts +1 -1
  239. package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -1
  240. package/mcp-server/dist/tools/youtube/download.js +1 -1
  241. package/mcp-server/dist/tools/youtube/download.js.map +1 -1
  242. package/mcp-server/dist/tools/youtube/media.d.ts +1 -1
  243. package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -1
  244. package/mcp-server/dist/tools/youtube/media.js +1 -1
  245. package/mcp-server/dist/tools/youtube/media.js.map +1 -1
  246. package/mcp-server/dist/tools/youtube/metadata.d.ts +1 -1
  247. package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -1
  248. package/mcp-server/dist/tools/youtube/metadata.js +1 -1
  249. package/mcp-server/dist/tools/youtube/metadata.js.map +1 -1
  250. package/mcp-server/dist/tools/youtube/navigate.d.ts +1 -1
  251. package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -1
  252. package/mcp-server/dist/tools/youtube/navigate.js +1 -1
  253. package/mcp-server/dist/tools/youtube/navigate.js.map +1 -1
  254. package/mcp-server/dist/tools/youtube/playlist.d.ts +1 -1
  255. package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -1
  256. package/mcp-server/dist/tools/youtube/playlist.js +1 -1
  257. package/mcp-server/dist/tools/youtube/playlist.js.map +1 -1
  258. package/mcp-server/dist/tools/youtube/schedule.d.ts +1 -1
  259. package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -1
  260. package/mcp-server/dist/tools/youtube/schedule.js +1 -1
  261. package/mcp-server/dist/tools/youtube/schedule.js.map +1 -1
  262. package/mcp-server/dist/tools/youtube/source.d.ts +1 -1
  263. package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -1
  264. package/mcp-server/dist/tools/youtube/source.js +1 -1
  265. package/mcp-server/dist/tools/youtube/source.js.map +1 -1
  266. package/mcp-server/dist/tools/youtube/upload.d.ts +1 -1
  267. package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -1
  268. package/mcp-server/dist/tools/youtube/upload.js +4 -9
  269. package/mcp-server/dist/tools/youtube/upload.js.map +1 -1
  270. package/mcp-server/dist/tui.js +18 -5
  271. package/mcp-server/dist/tui.js.map +1 -1
  272. package/mcp-server/dist/version.d.ts +5 -0
  273. package/mcp-server/dist/version.d.ts.map +1 -0
  274. package/mcp-server/dist/version.js +21 -0
  275. package/mcp-server/dist/version.js.map +1 -0
  276. package/mcp-server/package-lock.json +3 -3
  277. package/mcp-server/package.json +3 -3
  278. package/mcp-server/run.mjs +15 -9
  279. package/mcp-server/src/http.ts +18 -3
  280. package/mcp-server/src/index.ts +9 -6
  281. package/mcp-server/src/services/browser-bench.ts +1 -1
  282. package/mcp-server/src/services/budget-judgments.ts +183 -0
  283. package/mcp-server/src/services/categories.ts +77 -32
  284. package/mcp-server/src/services/codex-config-heal.ts +35 -4
  285. package/mcp-server/src/services/desktop-bench.ts +1 -1
  286. package/mcp-server/src/services/identity.ts +27 -0
  287. package/mcp-server/src/services/neural-interface.ts +34 -2
  288. package/mcp-server/src/services/typesafe-config.ts +4 -1
  289. package/mcp-server/src/tools/agents.ts +34 -15
  290. package/mcp-server/src/tools/bluesky.ts +1 -1
  291. package/mcp-server/src/tools/browser-advanced.ts +1 -1
  292. package/mcp-server/src/tools/browser-batch.ts +1 -1
  293. package/mcp-server/src/tools/browser-cheatsheet.ts +1 -1
  294. package/mcp-server/src/tools/browser-interact.ts +1 -1
  295. package/mcp-server/src/tools/browser-navigate.ts +1 -1
  296. package/mcp-server/src/tools/browser-observe.ts +1 -1
  297. package/mcp-server/src/tools/card-tools.ts +1 -1
  298. package/mcp-server/src/tools/category.ts +6 -1
  299. package/mcp-server/src/tools/choice.ts +1 -1
  300. package/mcp-server/src/tools/computer.ts +1 -1
  301. package/mcp-server/src/tools/discord-channel.ts +1 -1
  302. package/mcp-server/src/tools/discord-guild.ts +1 -1
  303. package/mcp-server/src/tools/discord-member.ts +1 -1
  304. package/mcp-server/src/tools/discord-message.ts +1 -1
  305. package/mcp-server/src/tools/discord-onboarding.ts +1 -1
  306. package/mcp-server/src/tools/discord-role.ts +1 -1
  307. package/mcp-server/src/tools/discord-thread.ts +1 -1
  308. package/mcp-server/src/tools/discord-webhook.ts +1 -1
  309. package/mcp-server/src/tools/fb-groups.ts +1 -1
  310. package/mcp-server/src/tools/forget.ts +1 -1
  311. package/mcp-server/src/tools/git-tools.ts +1 -1
  312. package/mcp-server/src/tools/gsc/experience.ts +1 -1
  313. package/mcp-server/src/tools/gsc/indexing.ts +1 -1
  314. package/mcp-server/src/tools/gsc/inspect.ts +1 -1
  315. package/mcp-server/src/tools/gsc/navigate.ts +1 -1
  316. package/mcp-server/src/tools/gsc/performance.ts +1 -1
  317. package/mcp-server/src/tools/gsc/settings.ts +1 -1
  318. package/mcp-server/src/tools/image-tools.ts +1 -1
  319. package/mcp-server/src/tools/leonardo-browser-tools.ts +1 -1
  320. package/mcp-server/src/tools/loop.ts +1 -1
  321. package/mcp-server/src/tools/memories.ts +1 -1
  322. package/mcp-server/src/tools/morelogin.ts +1 -1
  323. package/mcp-server/src/tools/profile.ts +1 -1
  324. package/mcp-server/src/tools/recall.ts +1 -1
  325. package/mcp-server/src/tools/reflect.ts +1 -1
  326. package/mcp-server/src/tools/remember.ts +13 -12
  327. package/mcp-server/src/tools/restore.ts +1 -1
  328. package/mcp-server/src/tools/style-guide-tools.ts +1 -1
  329. package/mcp-server/src/tools/sync.ts +1 -1
  330. package/mcp-server/src/tools/tictactoe-tools.ts +1 -1
  331. package/mcp-server/src/tools/utils.ts +1 -1
  332. package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
  333. package/mcp-server/src/tools/whiteboard-tools.ts +1 -1
  334. package/mcp-server/src/tools/youtube/analytics.ts +1 -1
  335. package/mcp-server/src/tools/youtube/download.ts +1 -1
  336. package/mcp-server/src/tools/youtube/media.ts +1 -1
  337. package/mcp-server/src/tools/youtube/metadata.ts +1 -1
  338. package/mcp-server/src/tools/youtube/navigate.ts +1 -1
  339. package/mcp-server/src/tools/youtube/playlist.ts +1 -1
  340. package/mcp-server/src/tools/youtube/schedule.ts +1 -1
  341. package/mcp-server/src/tools/youtube/source.ts +1 -1
  342. package/mcp-server/src/tools/youtube/upload.ts +4 -12
  343. package/mcp-server/src/tui.ts +24 -10
  344. package/mcp-server/src/version.ts +22 -0
  345. package/neural-interface/i18n/en.json +645 -57
  346. package/neural-interface/i18n/pt-BR.json +644 -56
  347. package/neural-interface/lib/account-usage.js +163 -0
  348. package/neural-interface/lib/agent-sandbox-elevation.js +102 -0
  349. package/neural-interface/lib/agent-sandbox-setting.js +57 -0
  350. package/neural-interface/lib/assistant-api.js +274 -52
  351. package/neural-interface/lib/assistant-brains/claude.js +15 -2
  352. package/neural-interface/lib/assistant-brains/codex.js +3 -0
  353. package/neural-interface/lib/assistant-brains/opencode.js +17 -4
  354. package/neural-interface/lib/assistant-budget-planner.js +390 -0
  355. package/neural-interface/lib/assistant-budget-split.js +408 -0
  356. package/neural-interface/lib/assistant-budget.js +94 -95
  357. package/neural-interface/lib/assistant-config.js +62 -14
  358. package/neural-interface/lib/assistant-dispatch.js +432 -125
  359. package/neural-interface/lib/assistant-jev.js +26 -3
  360. package/neural-interface/lib/assistant-persona.js +68 -11
  361. package/neural-interface/lib/assistant-plan-permissions.js +8 -1
  362. package/neural-interface/lib/assistant-route-accounts.js +14 -0
  363. package/neural-interface/lib/assistant-route-gate-codex.js +3 -1
  364. package/neural-interface/lib/assistant-route-gate.js +19 -3
  365. package/neural-interface/lib/assistant-router.js +373 -42
  366. package/neural-interface/lib/assistant-runtime.js +312 -48
  367. package/neural-interface/lib/assistant-user-writer.js +4 -2
  368. package/neural-interface/lib/augmented-path.js +317 -53
  369. package/neural-interface/lib/claude-accounts.js +342 -31
  370. package/neural-interface/lib/claude-agent-bridge.js +90 -3
  371. package/neural-interface/lib/claude-executable.js +6 -1
  372. package/neural-interface/lib/claude-model-catalog.js +3 -3
  373. package/neural-interface/lib/claude-panel-auth.js +221 -0
  374. package/neural-interface/lib/claude-profile-api-key.js +62 -0
  375. package/neural-interface/lib/cli-install-api.js +137 -0
  376. package/neural-interface/lib/cli-install.js +797 -0
  377. package/neural-interface/lib/cli-path.js +130 -0
  378. package/neural-interface/lib/cli-resolver.js +744 -0
  379. package/neural-interface/lib/cli-update-planner.js +143 -7
  380. package/neural-interface/lib/codex-account-auth.js +180 -0
  381. package/neural-interface/lib/codex-accounts.js +502 -0
  382. package/neural-interface/lib/codex-capabilities.js +2 -1
  383. package/neural-interface/lib/codex-compat-rpc.js +8 -2
  384. package/neural-interface/lib/codex-mcp-approvals.js +5 -1
  385. package/neural-interface/lib/codex-panel-auth.js +62 -0
  386. package/neural-interface/lib/codex-session-settings.js +1 -1
  387. package/neural-interface/lib/codex-settings-schema.js +122 -0
  388. package/neural-interface/lib/desktop/config.js +3 -0
  389. package/neural-interface/lib/exec-loop-command.js +3 -1
  390. package/neural-interface/lib/git-bash/bzip2.js +339 -0
  391. package/neural-interface/lib/git-bash/claude-settings.js +73 -0
  392. package/neural-interface/lib/git-bash/extract-worker.js +34 -0
  393. package/neural-interface/lib/git-bash/install.js +529 -0
  394. package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
  395. package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
  396. package/neural-interface/lib/git-bash/manager.js +268 -0
  397. package/neural-interface/lib/git-bash/pins.js +206 -0
  398. package/neural-interface/lib/git-bash/pins.json +34 -0
  399. package/neural-interface/lib/git-bash/resolver.js +194 -0
  400. package/neural-interface/lib/git-read.js +34 -2
  401. package/neural-interface/lib/install-details.js +163 -0
  402. package/neural-interface/lib/mcp-installer.js +14 -2
  403. package/neural-interface/lib/native-loop-providers.js +22 -4
  404. package/neural-interface/lib/native-loop-runtime.js +9 -4
  405. package/neural-interface/lib/os-path.js +319 -0
  406. package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
  407. package/neural-interface/lib/provider-panel-terminal-auth.js +96 -0
  408. package/neural-interface/lib/pty-host/core.js +2 -1
  409. package/neural-interface/lib/pty-host/manager.js +27 -2
  410. package/neural-interface/lib/remote-policy.js +20 -11
  411. package/neural-interface/lib/session-indexer.js +3 -1
  412. package/neural-interface/lib/session-title-generator.js +6 -3
  413. package/neural-interface/lib/system-about.js +340 -0
  414. package/neural-interface/lib/system-activity.js +66 -0
  415. package/neural-interface/lib/terminal-env.js +11 -3
  416. package/neural-interface/lib/terminal-path.js +55 -0
  417. package/neural-interface/lib/virtual-socket.js +12 -1
  418. package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
  419. package/neural-interface/lib/whatsapp/bridge.js +78 -34
  420. package/neural-interface/lib/whatsapp/commands.js +13 -14
  421. package/neural-interface/lib/whatsapp/config.js +134 -20
  422. package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
  423. package/neural-interface/lib/whatsapp/host-core.js +168 -35
  424. package/neural-interface/lib/whatsapp/inbound.js +5 -3
  425. package/neural-interface/lib/whatsapp/local-file.js +524 -0
  426. package/neural-interface/lib/whatsapp/manager.js +92 -32
  427. package/neural-interface/lib/whatsapp/paths.js +11 -1
  428. package/neural-interface/lib/whatsapp/protocol.js +65 -5
  429. package/neural-interface/lib/whatsapp/redact.js +63 -0
  430. package/neural-interface/lib/whatsapp/service.js +343 -91
  431. package/neural-interface/lib/whatsapp/transcription.js +463 -0
  432. package/neural-interface/package-lock.json +2 -2
  433. package/neural-interface/package.json +1 -1
  434. package/neural-interface/public/index.html +2 -0
  435. package/neural-interface/public/index2d.html +2 -0
  436. package/neural-interface/public/offline.html +16 -1
  437. package/neural-interface/public/onboarding.html +96 -2
  438. package/neural-interface/public/shared/account-i18n.js +7 -0
  439. package/neural-interface/public/shared/account-menu.js +542 -0
  440. package/neural-interface/public/shared/account-usage.js +79 -0
  441. package/neural-interface/public/shared/account-view.js +336 -0
  442. package/neural-interface/public/shared/agent-runtime-options.js +28 -8
  443. package/neural-interface/public/shared/assistant/asst-account-menu.js +298 -0
  444. package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
  445. package/neural-interface/public/shared/assistant/asst-api.js +39 -2
  446. package/neural-interface/public/shared/assistant/asst-brain-picker.js +288 -32
  447. package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
  448. package/neural-interface/public/shared/assistant/asst-mascot.js +126 -7
  449. package/neural-interface/public/shared/assistant/asst-menu.js +219 -80
  450. package/neural-interface/public/shared/assistant/asst-model-chip.js +135 -0
  451. package/neural-interface/public/shared/assistant/asst-panel.js +87 -16
  452. package/neural-interface/public/shared/assistant/asst-render.js +38 -7
  453. package/neural-interface/public/shared/assistant/asst-route.js +72 -14
  454. package/neural-interface/public/shared/assistant/asst-routes-editor.js +45 -6
  455. package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
  456. package/neural-interface/public/shared/assistant/asst-state.js +1 -1
  457. package/neural-interface/public/shared/assistant/asst-styles.js +160 -29
  458. package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
  459. package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
  460. package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
  461. package/neural-interface/public/shared/cdx/cdx-panel.js +49 -108
  462. package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
  463. package/neural-interface/public/shared/cdx/cdx-styles.js +172 -111
  464. package/neural-interface/public/shared/cdx/cdx-tabs.js +264 -664
  465. package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
  466. package/neural-interface/public/shared/claude-account-view.js +3 -0
  467. package/neural-interface/public/shared/cli-install-view.js +392 -0
  468. package/neural-interface/public/shared/cli-status.js +282 -22
  469. package/neural-interface/public/shared/cp/cp-events.js +2 -1
  470. package/neural-interface/public/shared/cp/cp-styles.js +46 -0
  471. package/neural-interface/public/shared/desktop-bridge.js +39 -0
  472. package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
  473. package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
  474. package/neural-interface/public/shared/desktop-titlebar.js +153 -0
  475. package/neural-interface/public/shared/desktop-update-view.js +462 -0
  476. package/neural-interface/public/shared/editor/editor-panel.js +1 -1
  477. package/neural-interface/public/shared/explorer/fx-main.js +1 -1
  478. package/neural-interface/public/shared/git-bash-view.js +145 -0
  479. package/neural-interface/public/shared/html-shell.js +1 -0
  480. package/neural-interface/public/shared/i18n.js +19 -1
  481. package/neural-interface/public/shared/install-details-view.js +86 -0
  482. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -1
  483. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +36 -1
  484. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +17 -0
  485. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +169 -2
  486. package/neural-interface/public/shared/pwa-install-page.js +3 -1
  487. package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
  488. package/neural-interface/public/shared/settings/settings-about.js +254 -0
  489. package/neural-interface/public/shared/settings/settings-ia.js +15 -0
  490. package/neural-interface/public/shared/showcase-privacy.js +490 -0
  491. package/neural-interface/public/shared/storage.js +30 -3
  492. package/neural-interface/public/shared/styles.css +281 -32
  493. package/neural-interface/public/shared/synabun-mascot.js +48 -2
  494. package/neural-interface/public/shared/ui-claude-panel.js +262 -217
  495. package/neural-interface/public/shared/ui-cli-banner.js +355 -0
  496. package/neural-interface/public/shared/ui-explorer.js +2 -0
  497. package/neural-interface/public/shared/ui-git-bash.js +124 -0
  498. package/neural-interface/public/shared/ui-judgments.js +10 -1
  499. package/neural-interface/public/shared/ui-loading.js +30 -6
  500. package/neural-interface/public/shared/ui-menubar.js +13 -3
  501. package/neural-interface/public/shared/ui-navbar.js +3 -1
  502. package/neural-interface/public/shared/ui-provider-auth.js +104 -0
  503. package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
  504. package/neural-interface/public/shared/ui-sessions.js +9 -3
  505. package/neural-interface/public/shared/ui-settings.js +55 -3
  506. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
  507. package/neural-interface/public/shared/ui-sync.js +8 -0
  508. package/neural-interface/public/shared/ui-terminal.js +17 -0
  509. package/neural-interface/public/shared/ui-tool-updates.js +5 -0
  510. package/neural-interface/public/shared/ui-update.js +598 -7
  511. package/neural-interface/public/shared/ui-whatsapp.js +21 -6
  512. package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
  513. package/neural-interface/public/shared/worker-end.js +2 -0
  514. package/neural-interface/public/sw.js +1 -1
  515. package/neural-interface/public/variant/2d/main.js +10 -1
  516. package/neural-interface/public/variant/3d/main.js +10 -1
  517. package/neural-interface/scripts/rebuild-pty.js +14 -2
  518. package/neural-interface/server.js +1384 -679
  519. package/npm-shrinkwrap.json +4424 -0
  520. package/package.json +57 -2
  521. package/postinstall.js +61 -47
  522. package/preuninstall.js +8 -35
  523. package/setup.js +80 -47
  524. package/skills/synabun/modules/idea.md +1 -1
  525. package/skills/synabun/modules/schedule.md +1 -1
  526. package/updater.cmd +2 -2
  527. package/updater.mjs +97 -3
@@ -0,0 +1,355 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — the CLI banner of the side panels (page side)
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // One banner for the Claude Code, Codex and OpenCode panels, above their
6
+ // transcript, whenever their CLI is not usable: not installed (with Install,
7
+ // which runs the vendor's official installer on the server, the command to run
8
+ // by hand and the install guide), being installed (its steps and the
9
+ // installer's last lines), failed to install, or found but failed to start
10
+ // (with the error it gave). Each panel keeps its own look through a class
11
+ // prefix: cp-cli-banner* (Claude), cxp-cli-banner* (Codex), ocp-cli-banner*
12
+ // (OpenCode); the panel element gets `<prefix>-cli-blocked` while sending is
13
+ // off.
14
+ //
15
+ // What it says is cli-install-view.js (DOM-free, tested under node); the data
16
+ // and the install stream are cli-status.js. A panel mounts one controller:
17
+ //
18
+ // const cli = mountCliBanner({ toolKey: 'codex', prefix: 'cxp',
19
+ // panel: () => panelEl, anchor: () => messagesContainer,
20
+ // onBlockedChange: (blocked) => syncSendButton() });
21
+ // cli.refresh(); when the panel is shown
22
+ // cli.blocked(); before a send
23
+ // cli.nudge(); a send that was held back
24
+ // cli.flagFailure(text); a session could not start the CLI
25
+
26
+ import { subscribeCliStatus, recheckCliStatus, getCliDocUrl, getCliInstallCommand, getCliLabel, installCli, followCliInstall, getCliInstallJobs, addCliToPath } from './cli-status.js';
27
+ import { cliBannerView, cliInstallText, cliStateOf, reduceInstallJob, setCliInstallTranslator, INSTALLABLE_CLIS } from './cli-install-view.js';
28
+ import { t } from './i18n.js';
29
+
30
+ setCliInstallTranslator((key, params) => t(`cliInstall.${key}`, params));
31
+
32
+ /** How long the note about where an install put the CLI stays, when it has one. */
33
+ const DONE_NOTICE_MS = 12000;
34
+ const NUDGE_MS = 700;
35
+
36
+ /**
37
+ * @param {object} o
38
+ * @param {'claude-code' | 'codex' | 'opencode'} o.toolKey
39
+ * @param {'cp' | 'cxp' | 'ocp'} o.prefix the panel's class prefix
40
+ * @param {() => HTMLElement | null} o.panel the panel element (gets `<prefix>-cli-blocked`)
41
+ * @param {() => HTMLElement | null} o.anchor the banner goes right before this element (the transcript's container)
42
+ * @param {(blocked: boolean, view: object) => void} [o.onBlockedChange] sending was held back / let go
43
+ * @param {number} [o.doneNoticeMs]
44
+ */
45
+ export function mountCliBanner({ toolKey, prefix, panel, anchor, onBlockedChange = () => {}, doneNoticeMs = DONE_NOTICE_MS } = {}) {
46
+ const p = String(prefix || 'cp');
47
+ let info = null; // this CLI in the latest answer of /api/system/tool-versions (null: none yet)
48
+ let forced = null; // { reason }: a session could not start it, and the server was not asked again yet
49
+ let job = null; // the install under way or just ended (cli-install-view.js reduceInstallJob)
50
+ let installing = false;
51
+ let addingPath = false;
52
+ let pathError = '';
53
+ let looked = false; // asked whether the server is already installing it since it went missing
54
+ let el = null;
55
+ let doneTimer = null;
56
+ let nudgeTimer = null;
57
+ let lastBlocked = null;
58
+ let destroyed = false;
59
+
60
+ const view = () => cliBannerView({
61
+ toolKey,
62
+ label: getCliLabel(toolKey),
63
+ info,
64
+ forced,
65
+ job,
66
+ addingPath, pathError,
67
+ guideUrl: getCliDocUrl(toolKey),
68
+ installCommand: getCliInstallCommand(toolKey),
69
+ }, { t: cliInstallText });
70
+
71
+ const cls = (name) => `${p}-cli-banner-${name}`;
72
+
73
+ function build() {
74
+ const root = document.createElement('div');
75
+ root.className = `${p}-cli-banner`;
76
+ root.dataset.cliTool = toolKey;
77
+ root.innerHTML = `
78
+ <div class="${cls('icon')}" aria-hidden="true"></div>
79
+ <div class="${cls('text')}">
80
+ <div class="${cls('title')}"></div>
81
+ <div class="${cls('body')}"></div>
82
+ <div class="${cls('command')}" hidden><code></code><button class="${cls('copy')}" type="button"></button></div>
83
+ <div class="${cls('error')}" hidden><code></code></div>
84
+ <div class="${cls('note')}" hidden></div>
85
+ <div class="${cls('progress')}" hidden><div class="${cls('step')}"></div><div class="${cls('bar')}" hidden><span></span></div></div>
86
+ <pre class="${cls('log')}" hidden></pre>
87
+ <div class="${cls('actions')}">
88
+ <button class="${cls('install')}" type="button" hidden></button>
89
+ <a class="${cls('link')}" target="_blank" rel="noopener noreferrer"></a>
90
+ <button class="${cls('recheck')}" type="button"></button>
91
+ <button class="${cls('path')}" type="button" hidden></button>
92
+ <button class="${cls('dismiss')}" type="button" hidden></button>
93
+ </div>
94
+ </div>
95
+ `;
96
+ root.querySelector(`.${cls('install')}`).addEventListener('click', () => { startInstall(); });
97
+ root.querySelector(`.${cls('recheck')}`).addEventListener('click', () => { recheck(); });
98
+ root.querySelector(`.${cls('path')}`).addEventListener('click', () => { addPath(); });
99
+ root.querySelector(`.${cls('dismiss')}`).addEventListener('click', () => { clearDone(); });
100
+ root.querySelector(`.${cls('copy')}`).addEventListener('click', async (event) => {
101
+ const button = event.currentTarget;
102
+ const command = root.querySelector(`.${cls('command')} code`)?.textContent || '';
103
+ if (!command) return;
104
+ try { await navigator.clipboard.writeText(command); } catch { return; }
105
+ button.textContent = cliInstallText('copied');
106
+ setTimeout(() => { if (button.isConnected) button.textContent = cliInstallText('copy'); }, 1500);
107
+ });
108
+ return root;
109
+ }
110
+
111
+ function place() {
112
+ const before = anchor?.();
113
+ if (!before || !before.parentNode) return false;
114
+ if (!el) el = build();
115
+ if (el.parentNode !== before.parentNode || el.nextSibling !== before) before.parentNode.insertBefore(el, before);
116
+ return true;
117
+ }
118
+
119
+ function paint(v) {
120
+ el.className = `${p}-cli-banner ${p}-cli-banner--${v.kind}${nudgeTimer ? ` ${p}-cli-banner--nudge` : ''}`;
121
+ el.setAttribute('role', v.live === 'assertive' ? 'alert' : 'status');
122
+ el.setAttribute('aria-live', v.live);
123
+ el.querySelector(`.${cls('icon')}`).textContent = v.icon;
124
+ el.querySelector(`.${cls('title')}`).textContent = v.title;
125
+ const body = el.querySelector(`.${cls('body')}`);
126
+ body.textContent = v.text;
127
+ body.hidden = !v.text;
128
+
129
+ const command = el.querySelector(`.${cls('command')}`);
130
+ command.hidden = !v.command;
131
+ command.querySelector('code').textContent = v.command || '';
132
+ const copy = command.querySelector(`.${cls('copy')}`);
133
+ if (copy.textContent !== cliInstallText('copied')) copy.textContent = cliInstallText('copy');
134
+
135
+ const error = el.querySelector(`.${cls('error')}`);
136
+ error.hidden = !v.error;
137
+ error.querySelector('code').textContent = v.error || '';
138
+
139
+ const note = el.querySelector(`.${cls('note')}`);
140
+ note.hidden = !v.note;
141
+ note.textContent = v.note || '';
142
+ // The command to run by hand is announced after the note that introduces it. (Moved only when it is
143
+ // elsewhere: a node that moves loses the focus of what is in it.)
144
+ const before = v.kind === 'failed' && v.note && v.command ? note : body;
145
+ if (command.previousElementSibling !== before) before.after(command);
146
+
147
+ const progress = el.querySelector(`.${cls('progress')}`);
148
+ progress.hidden = !v.progress;
149
+ el.querySelector(`.${cls('step')}`).textContent = v.progress?.text || '';
150
+ const bar = el.querySelector(`.${cls('bar')}`);
151
+ const percent = v.progress?.percent;
152
+ bar.hidden = !(typeof percent === 'number');
153
+ if (typeof percent === 'number') {
154
+ bar.setAttribute('role', 'progressbar');
155
+ bar.setAttribute('aria-valuemin', '0');
156
+ bar.setAttribute('aria-valuemax', '100');
157
+ bar.setAttribute('aria-valuenow', String(percent));
158
+ bar.firstElementChild.style.width = `${percent}%`;
159
+ }
160
+
161
+ const log = el.querySelector(`.${cls('log')}`);
162
+ log.hidden = !v.log.length;
163
+ log.setAttribute('aria-label', cliInstallText('logLabel'));
164
+ const logText = v.log.join('\n');
165
+ if (log.textContent !== logText) {
166
+ log.textContent = logText;
167
+ log.scrollTop = log.scrollHeight;
168
+ }
169
+
170
+ const install = el.querySelector(`.${cls('install')}`);
171
+ install.hidden = !v.install;
172
+ if (v.install) {
173
+ install.textContent = v.install.label;
174
+ install.disabled = !!v.install.disabled;
175
+ install.setAttribute('aria-busy', v.kind === 'installing' ? 'true' : 'false');
176
+ }
177
+ const link = el.querySelector(`.${cls('link')}`);
178
+ link.hidden = !v.guide;
179
+ if (v.guide) { link.href = v.guide; link.textContent = cliInstallText('guide'); }
180
+ else link.removeAttribute('href');
181
+ const recheckButton = el.querySelector(`.${cls('recheck')}`);
182
+ recheckButton.hidden = !v.recheck;
183
+ if (!recheckButton.disabled) recheckButton.textContent = cliInstallText('recheck');
184
+ const pathButton = el.querySelector(`.${cls('path')}`);
185
+ pathButton.hidden = !v.addPath;
186
+ pathButton.textContent = v.addPath?.label || '';
187
+ pathButton.disabled = !!v.addPath?.disabled;
188
+ const dismiss = el.querySelector(`.${cls('dismiss')}`);
189
+ dismiss.hidden = !v.dismiss;
190
+ dismiss.textContent = cliInstallText('dismiss');
191
+ }
192
+
193
+ function render() {
194
+ if (destroyed) return;
195
+ const v = view();
196
+ const panelEl = panel?.();
197
+ if (panelEl) panelEl.classList.toggle(`${p}-cli-blocked`, v.blocked);
198
+ if (v.show && place()) paint(v);
199
+ else if (!v.show && el) el.remove();
200
+ if (v.blocked !== lastBlocked) {
201
+ lastBlocked = v.blocked;
202
+ try { onBlockedChange(v.blocked, v); } catch (error) { console.error('[cli-banner] onBlockedChange:', error); }
203
+ }
204
+ }
205
+
206
+ function clearDone() {
207
+ clearTimeout(doneTimer);
208
+ doneTimer = null;
209
+ if (job?.phase === 'done') job = null;
210
+ render();
211
+ }
212
+
213
+ function apply(event) {
214
+ if (!job && event?.type !== 'start') job = reduceInstallJob(null, { type: 'start' });
215
+ job = reduceInstallJob(job, event);
216
+ if (event?.type === 'done' && event.ok && event.status && typeof event.status === 'object') {
217
+ info = event.status;
218
+ forced = null;
219
+ }
220
+ render();
221
+ }
222
+
223
+ function settle(final) {
224
+ if (job?.phase === 'running' && final) job = reduceInstallJob(job, final);
225
+ if (job?.phase === 'done') {
226
+ clearTimeout(doneTimer);
227
+ const where = job.path;
228
+ if (where && (where.onPath === false || where.added === true)) doneTimer = setTimeout(clearDone, doneNoticeMs);
229
+ else job = null;
230
+ }
231
+ render();
232
+ }
233
+
234
+ async function startInstall() {
235
+ if (installing || !INSTALLABLE_CLIS.includes(toolKey)) return;
236
+ installing = true;
237
+ clearTimeout(doneTimer);
238
+ job = reduceInstallJob(null, { type: 'start' });
239
+ render();
240
+ let final = null;
241
+ try {
242
+ final = await installCli(toolKey, { onEvent: apply });
243
+ } catch (error) {
244
+ final = { type: 'done', ok: false, code: 'internal', error: error?.message || null };
245
+ } finally {
246
+ installing = false;
247
+ settle(final);
248
+ }
249
+ }
250
+
251
+ async function addPath() {
252
+ if (addingPath) return;
253
+ addingPath = true; pathError = ''; render();
254
+ try {
255
+ const result = await addCliToPath(toolKey);
256
+ info = { ...info, pathStatus: { dir: result.dir, onPath: true, canAdd: false } };
257
+ job = reduceInstallJob(null, { type: 'done', ok: true, status: info, path: { ...result, added: true } });
258
+ settle();
259
+ } catch (error) { pathError = error.message; }
260
+ finally { addingPath = false; render(); }
261
+ }
262
+
263
+ // An install the server is already running (another window started it, or this page was reloaded during
264
+ // one): followed here as if this banner had started it.
265
+ async function followRunning() {
266
+ if (installing) return;
267
+ const jobs = await getCliInstallJobs();
268
+ if (destroyed || installing || jobs?.[toolKey]?.state !== 'running') return;
269
+ installing = true;
270
+ job = reduceInstallJob(null, { type: 'start' });
271
+ render();
272
+ let final = null;
273
+ try {
274
+ final = await followCliInstall(toolKey, { onEvent: apply });
275
+ } catch (error) {
276
+ final = { type: 'done', ok: false, code: 'connection', error: error?.message || null };
277
+ } finally {
278
+ installing = false;
279
+ settle(final);
280
+ }
281
+ }
282
+
283
+ async function recheck() {
284
+ const button = el?.querySelector(`.${cls('recheck')}`);
285
+ if (button) { button.disabled = true; button.textContent = cliInstallText('checking'); }
286
+ // Asking again starts over: an earlier failure is not repeated over a fresh answer.
287
+ if (job?.phase === 'failed') job = null;
288
+ try {
289
+ const next = await recheckCliStatus(toolKey);
290
+ info = next || info;
291
+ if (cliStateOf(info) === 'installed' || cliStateOf(info) === 'broken') forced = null;
292
+ } catch { /* the banner stays as it is */ }
293
+ finally {
294
+ if (button?.isConnected) { button.disabled = false; button.textContent = cliInstallText('recheck'); }
295
+ render();
296
+ }
297
+ }
298
+
299
+ const unsubscribe = subscribeCliStatus(toolKey, (next) => {
300
+ info = next || null;
301
+ const state = cliStateOf(info);
302
+ if (state === 'installed') {
303
+ forced = null;
304
+ if (job?.phase === 'failed') job = null;
305
+ }
306
+ if (state === 'missing') {
307
+ if (!looked) { looked = true; followRunning().catch(() => {}); }
308
+ } else {
309
+ looked = false;
310
+ }
311
+ render();
312
+ });
313
+
314
+ return {
315
+ /** Draw again (the panel was shown, or rebuilt). */
316
+ refresh: render,
317
+ /** Sending is held back: the CLI is missing, broken, failed to install, or being installed. */
318
+ blocked: () => view().blocked,
319
+ /** What the banner shows now (cli-install-view.js cliBannerView). */
320
+ view,
321
+ /** A send was held back: draw attention to the banner for a moment. */
322
+ nudge() {
323
+ if (!view().show) return;
324
+ clearTimeout(nudgeTimer);
325
+ nudgeTimer = setTimeout(() => { nudgeTimer = null; render(); }, NUDGE_MS);
326
+ render();
327
+ },
328
+ /**
329
+ * A session could not start the CLI ("Claude CLI not found", a spawn error): show "failed to start" with its
330
+ * words at once, then ask the server again; its answer decides what stays.
331
+ */
332
+ flagFailure(reason = null) {
333
+ forced = { reason: reason ? String(reason).slice(0, 300) : null };
334
+ render();
335
+ recheckCliStatus(toolKey)
336
+ .then((next) => {
337
+ info = next || info;
338
+ const state = cliStateOf(info);
339
+ if (state === 'installed' || state === 'broken') forced = null;
340
+ render();
341
+ })
342
+ .catch(() => {});
343
+ },
344
+ /** Install from the banner's own button (for a panel's own entry point). */
345
+ install: startInstall,
346
+ destroy() {
347
+ destroyed = true;
348
+ unsubscribe();
349
+ clearTimeout(doneTimer);
350
+ clearTimeout(nudgeTimer);
351
+ el?.remove();
352
+ el = null;
353
+ },
354
+ };
355
+ }
@@ -1441,6 +1441,8 @@ export function initExplorer() {
1441
1441
  if (selectAllBtn) {
1442
1442
  selectAllBtn.addEventListener('click', () => {
1443
1443
  Object.keys(state.categoryMetadata).forEach(c => state.activeCategories.add(c));
1444
+ // and the categories memories use without a registered one ("uncategorized" included), as the sidebar does
1445
+ (state.allNodes || []).forEach(n => { if (n.payload?.category) state.activeCategories.add(n.payload.category); });
1444
1446
  cancelScheduledRemoval();
1445
1447
  emit('graph:refresh');
1446
1448
  emit('categories-changed');
@@ -0,0 +1,124 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — the Git Bash row of Settings
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // The page side of git-bash-view.js: the row of Settings → AI connections →
6
+ // Executable paths that shows the Git Bash every Claude Code SynaBun starts
7
+ // gets on Windows, with Download / Try again and Re-check. It asks
8
+ // GET /api/cli/git-bash, follows a download while it runs (a few times a
9
+ // second, until the row has left the page), and posts what the buttons ask
10
+ // with the guards of SynaBun's other local routes (a JSON body, X-SynaBun-UI).
11
+ // A server that has no such route (an older one), and every macOS or Linux
12
+ // server, leaves the row hidden.
13
+
14
+ import { gitBashText, gitBashView } from './git-bash-view.js';
15
+
16
+ const POST = Object.freeze({ method: 'POST', headers: { 'Content-Type': 'application/json', 'X-SynaBun-UI': '1' }, body: '{}' });
17
+
18
+ const ICON = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 4h18a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1zm1 2v12h16V6H4zm2.3 2.3 3.7 3.7-3.7 3.7-1.4-1.4L7.2 12 4.9 9.7l1.4-1.4zM11 15h6v2h-6v-2z"/></svg>';
19
+
20
+ const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[char]));
21
+
22
+ /** The row's markup: hidden until the server says there is something to show (Windows). */
23
+ export function gitBashRowHtml() {
24
+ return `
25
+ <div class="cli-path-row" id="cli-git-bash" data-git-bash hidden>
26
+ <div class="cli-path-icon" style="color:var(--t-secondary)">${ICON}</div>
27
+ <div class="cli-path-field">
28
+ <span class="cli-path-label" id="cli-git-bash-label">${escapeHtml(gitBashText('label'))}</span>
29
+ <div class="cli-path-input-row">
30
+ <span class="stg-help" data-git-bash-line role="status" aria-live="polite" aria-labelledby="cli-git-bash-label" style="flex:1;min-width:0;margin:0"></span>
31
+ <button type="button" class="cli-detect-btn" data-git-bash-action hidden></button>
32
+ <button type="button" class="cli-detect-btn" data-git-bash-recheck hidden>${escapeHtml(gitBashText('recheck'))}</button>
33
+ <span class="cli-path-status" data-git-bash-badge></span>
34
+ </div>
35
+ <div class="stg-help" data-git-bash-note hidden></div>
36
+ <div class="stg-help">${escapeHtml(gitBashText('help'))}</div>
37
+ </div>
38
+ </div>`;
39
+ }
40
+
41
+ /**
42
+ * Fill the row and keep it current. `fetchImpl`, `schedule` and `cancel` are the page's own unless a test gives
43
+ * others. Returns `{ refresh, stop }`; the row stops following a download by itself once it is off the page.
44
+ */
45
+ export function wireGitBashRow(row, { fetchImpl = (...args) => fetch(...args), pollMs = 1500, schedule = (fn, ms) => setTimeout(fn, ms), cancel = (id) => clearTimeout(id) } = {}) {
46
+ if (!row) return { refresh: async () => null, stop: () => {} };
47
+ const badge = row.querySelector('[data-git-bash-badge]');
48
+ const line = row.querySelector('[data-git-bash-line]');
49
+ const note = row.querySelector('[data-git-bash-note]');
50
+ const action = row.querySelector('[data-git-bash-action]');
51
+ const recheck = row.querySelector('[data-git-bash-recheck]');
52
+ let timer = null;
53
+ let stopped = false;
54
+
55
+ const clearPoll = () => { if (timer !== null) { cancel(timer); timer = null; } };
56
+ const stop = () => { stopped = true; clearPoll(); };
57
+
58
+ function draw(status) {
59
+ const view = gitBashView(status);
60
+ row.hidden = !view.visible;
61
+ clearPoll();
62
+ if (!view.visible) return view;
63
+ if (badge) {
64
+ badge.textContent = view.badge?.text || '';
65
+ badge.className = `cli-path-status${view.badge?.tone ? ` ${view.badge.tone}` : ''}`;
66
+ }
67
+ if (line) line.textContent = view.line;
68
+ if (note) { note.textContent = view.note || ''; note.hidden = !view.note; }
69
+ if (action) {
70
+ action.hidden = !view.action;
71
+ action.disabled = false;
72
+ action.textContent = view.action ? gitBashText(view.action) : '';
73
+ }
74
+ if (recheck) {
75
+ recheck.hidden = !view.recheck;
76
+ recheck.disabled = false;
77
+ recheck.textContent = gitBashText('recheck');
78
+ }
79
+ if (view.busy && !stopped) {
80
+ timer = schedule(() => {
81
+ timer = null;
82
+ if (row.isConnected === false) { stop(); return; }
83
+ refresh();
84
+ }, pollMs);
85
+ }
86
+ return view;
87
+ }
88
+
89
+ async function ask(path, init) {
90
+ const res = await fetchImpl(path, init);
91
+ let body = null;
92
+ try { body = await res.json(); } catch { body = null; }
93
+ // A refusal that still says how things are (422: nothing to fetch here) is drawn as it is.
94
+ if (!res.ok && !(body && typeof body.state === 'string')) throw new Error(body?.error || body?.code || `HTTP ${res.status}`);
95
+ return body;
96
+ }
97
+
98
+ const said = (error) => { if (line) line.textContent = gitBashText('errorRequest', { message: error?.message || String(error) }); };
99
+
100
+ async function refresh() {
101
+ try { return draw(await ask('/api/cli/git-bash')); } catch {
102
+ // An older server, or none: there is no row to show.
103
+ row.hidden = true;
104
+ return null;
105
+ }
106
+ }
107
+
108
+ action?.addEventListener('click', async () => {
109
+ action.disabled = true;
110
+ try { draw(await ask('/api/cli/git-bash/install', POST)); } catch (error) { said(error); action.disabled = false; }
111
+ });
112
+ recheck?.addEventListener('click', async () => {
113
+ recheck.disabled = true;
114
+ recheck.textContent = gitBashText('checking');
115
+ try { draw(await ask('/api/cli/git-bash/recheck', POST)); } catch (error) {
116
+ said(error);
117
+ recheck.disabled = false;
118
+ recheck.textContent = gitBashText('recheck');
119
+ }
120
+ });
121
+
122
+ refresh();
123
+ return { refresh, stop };
124
+ }
@@ -862,7 +862,7 @@ export function wireJudgmentsTab(overlay, { toast = () => {}, initial = null } =
862
862
  const outcome = row.outcome && typeof row.outcome === 'object' ? row.outcome : null;
863
863
  const summary = `${clock(row.created_at)} · <code>${escapeHtml(row.surface)}</code>${riders.map(x => ` <span style="${CHIP}">+${escapeHtml(x)}</span>`).join('')} ${kit.th('settings.redesign.judgments.qMs', { origin: escapeHtml(row.origin), model: escapeHtml(row.model || ''), num: num(row.question_count), num2: num(row.latency_ms), num3: num(row.input_tokens) })}${num(row.output_tokens)}${row.cached ? ' · <span style="color:var(--accent)">' + kit.te('settings.redesign.judgments.cached') + '</span>' : ''}${row.error ? ` · <span style="color:#f66">${escapeHtml(row.error)}</span>` : ''}${row.entity_id ? ` · ${memLink(row.entity_id)}` : ''}${row.session_id ? ` · <a href="#" class="jv-session-link" data-session="${attr(row.session_id)}" title="${attr(row.session_id)}${kit.te('settings.redesign.judgments.thisSessionSJudgments')}" style="${LINK};font-family:monospace">${kit.te('settings.redesign.judgments.session')} ${escapeHtml(shortId(row.session_id))}</a>` : ''}${row.project ? ` · ${escapeHtml(row.project)}` : ''}`;
864
864
  return `<details class="jv-log-row" data-id="${attr(row.id)}"${state.openLogIds.has(String(row.id)) ? ' open' : ''} style="font-size:12px;border:1px solid var(--s-medium);border-radius:6px;padding:4px 8px;background:var(--s-darker)">
865
- <summary style="cursor:pointer;list-style:none" title="${attr(row.created_at)}">${summary}</summary>
865
+ <summary style="cursor:pointer;list-style:none" title="${attr(row.created_at)}">${summary}${row.surface === 'budget-split' && budgetSplitLine(row, row.answers || {}).line ? ' · ' + budgetSplitLine(row, row.answers || {}).line : ''}</summary>
866
866
  <div class="settings-hint" style="margin:6px 0 2px">${kit.te('settings.redesign.judgments.state')}</div><pre style="margin:0;white-space:pre-wrap;font-size:11px;max-height:160px;overflow:auto">${escapeHtml(row.state_preview || '')}</pre>
867
867
  <div class="settings-hint" style="margin:6px 0 2px">${kit.te('settings.redesign.judgments.answers')}</div><pre style="margin:0;white-space:pre-wrap;font-size:11px;max-height:200px;overflow:auto">${escapeHtml(answers || '—')}</pre>
868
868
  ${outcome ? `<div class="settings-hint" style="margin:6px 0 2px">${kit.te('settings.redesign.judgments.outcome')}</div><pre style="${PRE}">${escapeHtml(JSON.stringify(outcome, null, 2))}</pre>` : ''}
@@ -1029,7 +1029,16 @@ export function wireJudgmentsTab(overlay, { toast = () => {}, initial = null } =
1029
1029
  const p = prob(a.goal_met);
1030
1030
  return { line: p == null ? '' : flagged(kit.tx('settings.redesign.judgments.pGoalMet', { pct: pct(p) }), p >= knob('loop-goal', 'minConfidence', 0.8)) };
1031
1031
  }
1032
+ function budgetSplitLine(row, answers) {
1033
+ const names = { share_claude_code: 'Claude Code', share_codex: 'Codex', share_opencode: 'OpenCode' };
1034
+ const parts = Object.entries(names).filter(([key]) => answers[key]?.type === 'score').map(([key, label]) => {
1035
+ const a = answers[key];
1036
+ return escapeHtml(kit.tx('settings.redesign.judgments.budgetShareScore', { provider: label, score: num(a.score ?? a.value), confidence: pct(a.confidence) }));
1037
+ });
1038
+ return { line: parts.join(' · ') };
1039
+ }
1032
1040
  const TIMELINE = {
1041
+ 'budget-split': budgetSplitLine,
1033
1042
  'prompt-urgency': urgencyLine, 'rerank': rankLine, 'brief-rank': rankLine,
1034
1043
  'agent-message': stopLine, 'turn-worth': stopLine, 'claim-check': stopLine,
1035
1044
  'edit-stale': staleLine, 'stale-check': staleLine, 'plan-conflict': planLine, 'compact-digest': digestLine, 'loop-goal': loopLine,
@@ -14,6 +14,7 @@ import {
14
14
  // Imported with the app, not when it is needed: once the server is down
15
15
  // nothing more can be fetched, and that is exactly when the overlay wants it.
16
16
  import { createMascot } from './synabun-mascot.js';
17
+ import { isDesktopApp } from './desktop-bridge.js';
17
18
 
18
19
  const $ = (id) => document.getElementById(id);
19
20
 
@@ -48,6 +49,22 @@ function stored(key) {
48
49
  try { return localStorage.getItem(key); } catch { return null; }
49
50
  }
50
51
 
52
+ // Inside the desktop app the app starts and restarts its server itself: no
53
+ // Start button, no command to type. The sentences of a start that point at
54
+ // them say what to do in the app instead (desktop.offline.* in i18n).
55
+ const DESKTOP_LINES = { stalled: 'stalled', restartStalled: 'stalled', gaveUp: 'stalled', failedHint: 'failedHint' };
56
+
57
+ function desktopLine(line) {
58
+ const key = isDesktopApp() ? DESKTOP_LINES[line.key] : null;
59
+ return key ? t(`desktop.offline.${key}`) : '';
60
+ }
61
+
62
+ /** The command box under the overlay: a terminal command, which the desktop app has no use for. */
63
+ function showStartCommand(show) {
64
+ const $box = $('loading-server-cmd-box');
65
+ if ($box) $box.style.display = show ? '' : 'none';
66
+ }
67
+
51
68
  // ═══════════════════════════════════════════
52
69
  // PUBLIC API
53
70
  // ═══════════════════════════════════════════
@@ -76,8 +93,10 @@ export function showLoadingError(title, sub, canStart, serverOffline) {
76
93
  if (serverOffline) _offlineTitle = title;
77
94
 
78
95
  // Offline, the button asks the OS to start the server (synabun://start); it
79
- // is left out only when that launcher is known not to be registered.
80
- const offlineStart = !!serverOffline && startButtonUsable(stored(START_STORAGE.launcher));
96
+ // is left out only when that launcher is known not to be registered, and in
97
+ // the desktop app, which starts its server itself.
98
+ const desktopApp = isDesktopApp();
99
+ const offlineStart = !!serverOffline && !desktopApp && startButtonUsable(stored(START_STORAGE.launcher));
81
100
  const $action = $('loading-action');
82
101
  const $btn = $('loading-action-btn');
83
102
  if (canStart || offlineStart) {
@@ -94,7 +113,8 @@ export function showLoadingError(title, sub, canStart, serverOffline) {
94
113
 
95
114
  if (serverOffline) {
96
115
  const $hint = $('loading-server-cmd-hint');
97
- if ($hint) $hint.textContent = offlineStart ? t('loading.runCommandOr') : t('loading.runCommandSetup');
116
+ if ($hint) $hint.textContent = desktopApp ? t('desktop.offline.hint') : offlineStart ? t('loading.runCommandOr') : t('loading.runCommandSetup');
117
+ if (desktopApp) showStartCommand(false);
98
118
  watchForServer();
99
119
  } else {
100
120
  stopWatchingForServer();
@@ -275,7 +295,7 @@ function poseMascot(state) {
275
295
  function renderStart(snap) {
276
296
  const platform = isWindowsPlatform(navigator) ? 'windows' : isMacPlatform(navigator) ? 'mac' : 'linux';
277
297
  const view = startStatusView(snap, { platform });
278
- const say = (line) => (line ? t(`loading.start.${line.key}`, line.params) : '');
298
+ const say = (line) => (line ? desktopLine(line) || t(`loading.start.${line.key}`, line.params) : '');
279
299
  // A restart this page asked for or was told of, while it runs.
280
300
  const restarting = !!snap.restart && (view.busy || view.state === 'online');
281
301
 
@@ -315,7 +335,8 @@ function renderStart(snap) {
315
335
  // No dead button: when a restart did not bring the server back, Start is
316
336
  // offered only if its launcher is there (the command below always is).
317
337
  const startOffered = view.button === 'start' || view.button === 'retry';
318
- btn.style.display = _restartShown && startOffered && !startButtonUsable(stored(START_STORAGE.launcher)) ? 'none' : '';
338
+ // The desktop app keeps only the way back from a refused restart.
339
+ btn.style.display = (isDesktopApp() ? view.button !== 'back' : _restartShown && startOffered && !startButtonUsable(stored(START_STORAGE.launcher))) ? 'none' : '';
319
340
  }
320
341
  if ($label) {
321
342
  $label.textContent = view.button === 'starting' ? t('loading.starting')
@@ -465,7 +486,10 @@ function beginRestart({ request = null, announced = false, boot = '' } = {}) {
465
486
  const $action = $('loading-action');
466
487
  if ($action) $action.style.display = 'block';
467
488
  const $hint = $('loading-server-cmd-hint');
468
- if ($hint) $hint.textContent = startButtonUsable(stored(START_STORAGE.launcher)) ? t('loading.runCommandOr') : t('loading.runCommandSetup');
489
+ if (isDesktopApp()) {
490
+ if ($hint) $hint.textContent = t('desktop.offline.hint');
491
+ showStartCommand(false);
492
+ } else if ($hint) $hint.textContent = startButtonUsable(stored(START_STORAGE.launcher)) ? t('loading.runCommandOr') : t('loading.runCommandSetup');
469
493
  const $retry = $('loading-retry-status');
470
494
  if ($retry) $retry.textContent = '';
471
495
  clearStartStatus();
@@ -14,8 +14,10 @@ import { registerAction } from './ui-keybinds.js';
14
14
  import { isGuest, hasPermission } from './ui-sync.js';
15
15
  import { sendToPanel } from './ui-claude-panel.js';
16
16
  import { forceCheckToolUpdates, showUpdateToast, TOOL_LABELS } from './ui-tool-updates.js';
17
- import { forceCheckSynabunUpdate, openSynabunUpdateModal } from './ui-update.js';
17
+ import { forceCheckSynabunUpdate, openSynabunUpdateModal, synabunUpdateSourceLabel } from './ui-update.js';
18
18
  import { requestServerRestart } from './ui-loading.js';
19
+ import { desktopBridge, isDesktopApp } from './desktop-bridge.js';
20
+ import { t } from './i18n.js';
19
21
 
20
22
  const $ = (id) => document.getElementById(id);
21
23
 
@@ -391,8 +393,7 @@ function wireTerminalMenu() {
391
393
  }
392
394
  }
393
395
  if (syn?.updateAvailable) {
394
- const srcLabel = syn.source === 'both' ? 'npm + GitHub' : syn.source === 'github' ? 'GitHub' : 'npm';
395
- updates.push({ label: `SynaBun v${syn.current} → v${syn.latest}`, source: srcLabel });
396
+ updates.push({ label: `SynaBun v${syn.current} → v${syn.latest}`, source: synabunUpdateSourceLabel(syn) });
396
397
  }
397
398
 
398
399
  const errors = [];
@@ -426,6 +427,9 @@ function wireTerminalMenu() {
426
427
  // when the server is back (ui-loading.js requestServerRestart).
427
428
  'menu-restart-server': () => { requestServerRestart(); },
428
429
  'menu-stop-server': async () => {
430
+ // Inside the desktop app the server is the app's: stopping it is quitting SynaBun.
431
+ const desktop = desktopBridge();
432
+ if (desktop) { await desktop.quit(); return; }
429
433
  const previousTitle = document.title;
430
434
  try {
431
435
  document.title = 'Stopping...';
@@ -451,6 +455,12 @@ function wireTerminalMenu() {
451
455
  registerAction('toggle-terminal', () => emit('terminal:toggle'));
452
456
  registerAction('toggle-terminal-alt', () => emit('terminal:toggle'));
453
457
 
458
+ // The desktop app is SynaBun's own window: no browser installs it, and Stop Server is Quit SynaBun there.
459
+ if (isDesktopApp()) {
460
+ const quitLabel = [...($('menu-stop-server')?.querySelector('.menu-text')?.childNodes || [])].filter(node => node.nodeType === 3).pop();
461
+ if (quitLabel) quitLabel.textContent = ` ${t('desktop.menu.quit')}`;
462
+ return;
463
+ }
454
464
  initPwaInstall({ changed: value => {
455
465
  const el = $('menu-install-app');
456
466
  if (el) el.style.display = value === 'installed' ? 'none' : '';