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
@@ -10,7 +10,12 @@
10
10
  //
11
11
  // resolveClaudeLauncher what a terminal or a shell would start: the
12
12
  // user's configured command, else the first
13
- // `claude` on PATH that is not inside SynaBun
13
+ // `claude` on PATH that is not inside SynaBun.
14
+ // server.js asks lib/cli-resolver.js instead
15
+ // (Settings, PATH and the OS PATH, the known
16
+ // install folders, a --version that answers),
17
+ // which follows a Windows `claude.cmd` with
18
+ // windowsClaudeTargets below
14
19
  // resolveClaudeSdkExecutable what the SDK can start. It spawns with
15
20
  // shell:false, so an npm command file on Windows
16
21
  // is followed to the executable behind it
@@ -14,7 +14,7 @@ import { spawn } from 'node:child_process';
14
14
  import { dirname, join, sep } from 'node:path';
15
15
  import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
16
16
  import { createRequire } from 'node:module';
17
- import { getAugmentedPath } from './augmented-path.js';
17
+ import { withPathEnv, getAugmentedPath } from './augmented-path.js';
18
18
 
19
19
  // Effort ladder the Claude Code CLI accepts for --effort. Models advertise their
20
20
  // own subset via supportedEffortLevels; this is the superset used as a fallback
@@ -240,7 +240,7 @@ export function queryClaudeCliModels(claudeBin, { cwd, timeoutMs = DISCOVERY_TIM
240
240
  try {
241
241
  child = spawnImpl(shellCommand(bin, useShell), args, {
242
242
  cwd: cwd || process.cwd(),
243
- env: { ...process.env, PATH: getAugmentedPath(), CLAUDE_CODE_ENTRYPOINT: 'sdk-cli' },
243
+ env: { ...withPathEnv(process.env, getAugmentedPath()), CLAUDE_CODE_ENTRYPOINT: 'sdk-cli' },
244
244
  stdio: ['pipe', 'pipe', 'ignore'],
245
245
  shell: useShell,
246
246
  });
@@ -400,7 +400,7 @@ export function queryClaudeCliVersion(claudeBin, { timeoutMs = 10000 } = {}) {
400
400
 
401
401
  try {
402
402
  child = spawn(shellCommand(bin, useShell), args, {
403
- env: { ...process.env, PATH: getAugmentedPath() },
403
+ env: withPathEnv(process.env, getAugmentedPath()),
404
404
  stdio: ['ignore', 'pipe', 'ignore'],
405
405
  shell: useShell,
406
406
  });
@@ -0,0 +1,221 @@
1
+ // Claude owns OAuth credentials and refresh. SynaBun owns only pasted keys
2
+ // in a separate private profile file; this adapter exposes safe auth status.
3
+ import { spawn } from 'node:child_process';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
6
+ import { tmpdir } from 'node:os';
7
+ import { join } from 'node:path';
8
+ import { verifyClaudeApiKey } from './claude-profile-api-key.js';
9
+ import { validateProviderApiKey, redactProviderSecrets } from './provider-panel-api-key-auth.js';
10
+
11
+ export function claudeAuthEnv(env, accountEnv) {
12
+ const clean = { ...env };
13
+ for (const key of Object.keys(clean)) {
14
+ if (/^(CLAUDECODE|CLAUDE_CONFIG_DIR|CLAUDE_CODE_OAUTH_TOKEN|CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR|ANTHROPIC_AUTH_TOKEN|ANTHROPIC_API_KEY|ANTHROPIC_API_KEY_FILE_DESCRIPTOR|CLAUDE_CODE_USE_BEDROCK|CLAUDE_CODE_USE_VERTEX|CLAUDE_CODE_USE_FOUNDRY)$/.test(key)) delete clean[key];
15
+ }
16
+ return { ...clean, ...accountEnv, NO_COLOR: '1', FORCE_COLOR: '0' };
17
+ }
18
+
19
+ export function claudeAuthorizationUrl(text) {
20
+ for (const match of text.matchAll(/https:\/\/[^\s<>"\x1b]+/g)) {
21
+ try {
22
+ const url = new URL(match[0]);
23
+ if (['claude.ai', 'console.anthropic.com', 'platform.claude.com'].includes(url.hostname)
24
+ && !url.username && !url.password && url.searchParams.has('client_id') && url.searchParams.has('code_challenge')) return url.href;
25
+ } catch {}
26
+ }
27
+ return '';
28
+ }
29
+
30
+ export function createClaudePanelAuth({ accounts, executable, env = () => process.env, spawnProcess = spawn, timeoutMs = 10 * 60_000, statusTimeoutMs = 15_000, verifyKey = verifyClaudeApiKey, changeCredentials = async (_id, change) => change() } = {}) {
31
+ const operations = new Map();
32
+ function account(id = 'default') {
33
+ if (typeof id !== 'string' || !/^[a-z0-9-]{1,64}$/.test(id)) throw Object.assign(new Error('Unknown Claude account'), { status: 404 });
34
+ const row = accounts.profileFor ? accounts.profileFor(id) : accounts.get(id);
35
+ if (!row || row.id !== id) throw Object.assign(new Error('Unknown Claude account'), { status: 404 });
36
+ return row;
37
+ }
38
+ function launch(id, args, extraEnv = {}) {
39
+ account(id);
40
+ const found = executable();
41
+ if (!found?.path) throw new Error(found?.reason || 'Claude CLI not found. Install Claude Code to sign in.');
42
+ const script = /\.[cm]?js$/i.test(found.path);
43
+ const proc = spawnProcess(script ? process.execPath : found.path, script ? [found.path, ...args] : args, {
44
+ env: { ...claudeAuthEnv(env(), accounts.envFor(id)), ...extraEnv }, stdio: ['pipe', 'pipe', 'pipe'], shell: false, windowsHide: true,
45
+ });
46
+ proc.stdin?.on('error', () => {}); // A native exit can race a manual-code write.
47
+ return proc;
48
+ }
49
+ // Claude Code's own answer about the account's login (never a credential).
50
+ function nativeStatus(id = 'default') {
51
+ account(id);
52
+ return new Promise((resolve, reject) => {
53
+ let proc;
54
+ try { proc = launch(id, ['auth', 'status', '--json']); } catch (error) { reject(error); return; }
55
+ let output = '', settled = false;
56
+ const finish = (error, result) => { if (settled) return; settled = true; clearTimeout(timer); error ? reject(error) : resolve(result); };
57
+ const timer = setTimeout(() => { proc.kill(); finish(new Error('Claude authentication status timed out. Try Refresh.')); }, statusTimeoutMs);
58
+ proc.stdout.on('data', chunk => { output = (output + chunk).slice(-65536); });
59
+ proc.stderr.on('data', () => {}); // Never forward CLI output or credential-bearing errors.
60
+ proc.on('error', () => finish(new Error('Could not start Claude Code. Check its installation.')));
61
+ proc.on('close', () => {
62
+ try {
63
+ const data = JSON.parse(output);
64
+ if (typeof data.loggedIn !== 'boolean') throw new Error();
65
+ finish(null, {
66
+ loggedIn: data.loggedIn, email: redactProviderSecrets(data.email || null), orgName: redactProviderSecrets(data.orgName || null),
67
+ authMethod: redactProviderSecrets(data.authMethod || null), subscriptionType: redactProviderSecrets(data.subscriptionType || null),
68
+ });
69
+ } catch { finish(new Error('Could not read Claude authentication. Update Claude Code to a version supporting auth status --json.')); }
70
+ });
71
+ });
72
+ }
73
+ // The account as every surface shows it (lib/claude-accounts.js describe()),
74
+ // after asking Claude Code about its subscription login. A failed check of an
75
+ // account that still has a usable API key is not an error: the key is used.
76
+ async function read(id = 'default') {
77
+ account(id);
78
+ // An account running on its API key is signed in whatever its subscription
79
+ // says: no process is started for the answer. The subscription half is
80
+ // asked in the background, so the account list can show it later.
81
+ const current = accounts.describeForClient?.(id);
82
+ if (current?.mode === 'apiKey' && current.signedIn === true) {
83
+ Promise.resolve(accounts.refreshStatus?.([id])).catch(() => {});
84
+ return { accountId: id, account: current, state: snapshot(id) };
85
+ }
86
+ let status = null, failure = null;
87
+ try { status = await nativeStatus(id); } catch (error) { failure = error; }
88
+ account(id); // A profile removed meanwhile is gone, whatever the CLI said.
89
+ if (status) accounts.noteNativeStatus?.(id, status);
90
+ const row = accounts.describeForClient ? accounts.describeForClient(id) : null;
91
+ if (failure && !(row?.mode === 'apiKey' && row.signedIn)) throw failure;
92
+ return { accountId: id, account: row || legacyAccount(id, status), state: snapshot(id) };
93
+ }
94
+ // A host without the account model (an older caller, a test double).
95
+ function legacyAccount(id, status) {
96
+ if (accounts.readApiKey?.(id)) return { loggedIn: true, email: null, authMethod: 'apiKey', subscriptionType: null };
97
+ return { loggedIn: status.loggedIn, email: status.email, authMethod: status.authMethod, subscriptionType: status.subscriptionType };
98
+ }
99
+ const keyPending = new Set();
100
+ async function signInApiKey(id, key) {
101
+ account(id); key = validateProviderApiKey('claude', key);
102
+ if (keyPending.has(id) || ['starting', 'pending'].includes(snapshot(id).status)) throw Object.assign(new Error('Sign-in already pending'), { status: 409 });
103
+ keyPending.add(id);
104
+ try {
105
+ await verifyKey(key);
106
+ account(id); // A removed profile must never be recreated by a late reply.
107
+ await changeCredentials(id, () => { account(id); accounts.saveApiKey(id, key); });
108
+ operations.set(id, { status: 'success', authUrl: '' });
109
+ } finally { keyPending.delete(id); key = null; }
110
+ }
111
+ // `credential`: 'apiKey' removes the key SynaBun keeps, 'subscription' runs
112
+ // Claude Code's own logout in the account's config directory, 'all' both.
113
+ async function signOut(id, credential = 'all') {
114
+ account(id);
115
+ if (!['all', 'apiKey', 'subscription'].includes(credential)) throw Object.assign(new Error('Choose which sign-in to remove.'), { status: 400 });
116
+ if (keyPending.has(id)) throw Object.assign(new Error('Sign-in already pending'), { status: 409 });
117
+ const op = operations.get(id);
118
+ if (op && ['starting', 'pending'].includes(op.status) && credential !== 'apiKey') cancel(id, op.operationId);
119
+ keyPending.add(id);
120
+ try {
121
+ await changeCredentials(id, async () => {
122
+ if (credential !== 'subscription') accounts.clearApiKey(id);
123
+ if (credential === 'apiKey') return;
124
+ operations.delete(id);
125
+ await new Promise((resolve, reject) => {
126
+ let proc, timer;
127
+ try { proc = launch(id, ['auth', 'logout']); } catch { reject(new Error('Could not sign out of Claude. Try again.')); return; }
128
+ let settled = false;
129
+ const finish = ok => { if (settled) return; settled = true; clearTimeout(timer); ok ? resolve() : reject(new Error('Could not sign out of Claude. Try again.')); };
130
+ proc.stdout.on('data', () => {}); proc.stderr.on('data', () => {});
131
+ proc.on('error', () => finish(false)); proc.on('close', code => finish(code === 0));
132
+ timer = setTimeout(() => { proc.kill(); finish(false); }, statusTimeoutMs);
133
+ });
134
+ accounts.noteNativeStatus?.(id, { loggedIn: false });
135
+ });
136
+ } finally { keyPending.delete(id); }
137
+ }
138
+ // What the account's sessions use from now on; its idle panel processes end
139
+ // so the next message starts under the new credential.
140
+ async function setMode(id, mode) {
141
+ account(id);
142
+ if (keyPending.has(id) || ['starting', 'pending'].includes(snapshot(id).status)) throw Object.assign(new Error('Finish sign-in first.'), { status: 409 });
143
+ return changeCredentials(id, () => accounts.setMode(id, mode));
144
+ }
145
+ function snapshot(id) {
146
+ const op = operations.get(id);
147
+ return op ? { accountId: id, operationId: op.operationId, status: op.status, authUrl: op.authUrl, error: op.error || '' } : { accountId: id, status: 'idle' };
148
+ }
149
+ function start(id = 'default') {
150
+ account(id);
151
+ if (keyPending.has(id)) throw Object.assign(new Error('Sign-in already pending'), { status: 409 });
152
+ const existing = operations.get(id);
153
+ if (existing && ['starting', 'pending'].includes(existing.status)) return snapshot(id);
154
+ // BROWSER is a supported Claude Code handoff. Suppress its OS browser launch:
155
+ // the panel opens the URL printed by the native command instead.
156
+ const dir = mkdtempSync(join(tmpdir(), 'synabun-claude-oauth-'));
157
+ const browser = join(dir, process.platform === 'win32' ? 'browser.cmd' : 'browser');
158
+ writeFileSync(browser, process.platform === 'win32' ? '@exit /b 0\r\n' : '#!/bin/sh\nexit 0\n', { mode: 0o700 });
159
+ const op = { operationId: randomUUID(), status: 'starting', authUrl: '', process: null };
160
+ operations.set(id, op);
161
+ let timer, forceTimer, output = '';
162
+ const cleanup = () => { clearTimeout(timer); clearTimeout(forceTimer); rmSync(dir, { recursive: true, force: true }); };
163
+ const finish = (status, error = '') => {
164
+ if (!['starting', 'pending'].includes(op.status)) return;
165
+ op.status = status; op.error = error; op.authUrl = '';
166
+ clearTimeout(timer);
167
+ };
168
+ op.stop = (status, error) => {
169
+ finish(status, error);
170
+ op.process?.kill();
171
+ forceTimer = setTimeout(() => op.process?.kill('SIGKILL'), 2000); forceTimer.unref?.();
172
+ };
173
+ try {
174
+ op.process = launch(id, ['auth', 'login', '--claudeai'], { BROWSER: browser });
175
+ const collect = chunk => {
176
+ output = (output + chunk).slice(-65536);
177
+ if (['starting', 'pending'].includes(op.status)) {
178
+ const url = claudeAuthorizationUrl(output.slice(0, output.lastIndexOf('\n') + 1));
179
+ if (url) { op.authUrl = url; op.status = 'pending'; }
180
+ }
181
+ };
182
+ op.process.stdout.on('data', collect); op.process.stderr.on('data', collect);
183
+ op.process.on('error', () => { finish('error', 'Could not start Claude Code. Check its installation.'); cleanup(); });
184
+ op.process.on('close', async code => {
185
+ cleanup();
186
+ if (!['starting', 'pending'].includes(op.status)) return;
187
+ if (code !== 0) { finish('error', 'Claude sign-in failed. Retry, or update Claude Code if auth login is unavailable.'); return; }
188
+ try {
189
+ const status = await nativeStatus(id);
190
+ if (status.loggedIn) {
191
+ // A subscription just signed into is what the account uses from now on.
192
+ if (accounts.noteSubscriptionSignIn) accounts.noteSubscriptionSignIn(id, status);
193
+ else accounts.recordIdentity?.(id);
194
+ finish('success');
195
+ } else finish('error', 'Claude Code finished without confirming sign-in. Try again.');
196
+ } catch (error) { finish('error', error.message); }
197
+ });
198
+ timer = setTimeout(() => op.stop('error', 'Claude sign-in timed out. Try again.'), timeoutMs); timer.unref?.();
199
+ } catch (error) { finish('error', error.message); cleanup(); }
200
+ return snapshot(id);
201
+ }
202
+ function cancel(id, operationId) {
203
+ account(id);
204
+ const op = operations.get(id);
205
+ if (!op || op.operationId !== operationId) throw Object.assign(new Error('This sign-in attempt is no longer active. Refresh its status.'), { status: 409 });
206
+ if (['starting', 'pending'].includes(op.status)) op.stop('cancelled', '');
207
+ return snapshot(id);
208
+ }
209
+ function submitCode(id, operationId, code) {
210
+ account(id);
211
+ const op = operations.get(id);
212
+ if (!op || op.operationId !== operationId || op.status !== 'pending') throw Object.assign(new Error('This sign-in attempt is no longer active. Refresh its status.'), { status: 409 });
213
+ if (typeof code !== 'string' || !code.trim() || code.length > 4096 || /[\r\n\0]/.test(code)) throw Object.assign(new Error('Paste a single sign-in code from Claude.'), { status: 400 });
214
+ if (!op.process?.stdin?.writable) throw new Error('Claude Code is no longer accepting a sign-in code. Retry sign-in.');
215
+ op.process.stdin.write(`${code.trim()}\n`, error => {
216
+ if (error && op.status === 'pending') op.stop('error', 'Claude Code stopped accepting the sign-in code. Retry sign-in.');
217
+ });
218
+ return snapshot(id);
219
+ }
220
+ return { start, read, nativeStatus, cancel, submitCode, snapshot, signInApiKey, signOut, setMode, close: () => { for (const [id, op] of operations) if (['starting', 'pending'].includes(op.status)) cancel(id, op.operationId); } };
221
+ }
@@ -0,0 +1,62 @@
1
+ // SynaBun's own file, never Claude's native credential store. Atomic 0600 writes
2
+ // work on every supported platform without a Keychain prompt or service naming.
3
+ import { constants, openSync, closeSync, fstatSync, readFileSync, writeFileSync, mkdirSync, renameSync, rmSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { validateProviderApiKey, providerApiKeyError } from './provider-panel-api-key-auth.js';
7
+ export const CLAUDE_API_KEY_FILE = '.synabun-api-key';
8
+ // The same file holds an account's key for either provider (Claude: an
9
+ // Anthropic key in its CLAUDE_CONFIG_DIR; Codex: an OpenAI key in its
10
+ // CODEX_HOME, lib/codex-accounts.js), checked against that provider's format.
11
+ export function readProfileApiKey(home, provider = 'claude') {
12
+ let fd;
13
+ try {
14
+ fd = openSync(join(home, CLAUDE_API_KEY_FILE), constants.O_RDONLY | (constants.O_NOFOLLOW || 0));
15
+ const stat = fstatSync(fd);
16
+ if (!stat.isFile() || (process.platform !== 'win32' && (stat.mode & 0o077))) throw new Error('Unsafe credential permissions');
17
+ return validateProviderApiKey(provider, readFileSync(fd, 'utf8'));
18
+ } catch (error) {
19
+ if (error.code === 'ENOENT') return null;
20
+ throw new Error('Could not read the saved API key. Sign in again.');
21
+ } finally { if (fd !== undefined) closeSync(fd); }
22
+ }
23
+ export function readClaudeProfileApiKey(home) { return readProfileApiKey(home, 'claude'); }
24
+ export function saveProfileApiKey(home, key, provider = 'claude') {
25
+ key = validateProviderApiKey(provider, key);
26
+ mkdirSync(home, { recursive: true, mode: 0o700 });
27
+ const target = join(home, CLAUDE_API_KEY_FILE), temp = `${target}.${randomUUID()}.tmp`;
28
+ try { writeFileSync(temp, key, { mode: 0o600, flag: 'wx' }); renameSync(temp, target); }
29
+ finally { rmSync(temp, { force: true }); }
30
+ }
31
+ export function saveClaudeProfileApiKey(home, key) { saveProfileApiKey(home, key, 'claude'); }
32
+ export function clearProfileApiKey(home) { rmSync(join(home, CLAUDE_API_KEY_FILE), { force: true }); }
33
+ export function clearClaudeProfileApiKey(home) { clearProfileApiKey(home); }
34
+ export async function verifyClaudeApiKey(key, fetchRequest = fetch) {
35
+ let response;
36
+ try {
37
+ response = await fetchRequest('https://api.anthropic.com/v1/models?limit=1', {
38
+ headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01' }, signal: AbortSignal.timeout(15000), redirect: 'error',
39
+ });
40
+ } catch { throw providerApiKeyError('verification_unreachable'); }
41
+ finally { key = null; }
42
+ // Discard the body without letting a cleanup failure mask the HTTP result.
43
+ // Neither provider response bodies nor exception text are surfaced or logged.
44
+ try { await response.body?.cancel(); } catch {}
45
+ if ([401, 403].includes(response.status)) throw providerApiKeyError('key_rejected');
46
+ if (!response.ok) throw providerApiKeyError('verification_unavailable');
47
+ }
48
+
49
+ // OpenAI's read-only model list answers whether a key is accepted. The key goes
50
+ // in the Authorization header only; the body and any error text are dropped.
51
+ export async function verifyOpenAiApiKey(key, fetchRequest = fetch) {
52
+ let response;
53
+ try {
54
+ response = await fetchRequest('https://api.openai.com/v1/models', {
55
+ headers: { authorization: `Bearer ${key}` }, signal: AbortSignal.timeout(15000), redirect: 'error',
56
+ });
57
+ } catch { throw providerApiKeyError('verification_unreachable', 'codex'); }
58
+ finally { key = null; }
59
+ try { await response.body?.cancel(); } catch {}
60
+ if ([401, 403].includes(response.status)) throw providerApiKeyError('key_rejected', 'codex');
61
+ if (!response.ok) throw providerApiKeyError('verification_unavailable', 'codex');
62
+ }
@@ -0,0 +1,137 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — /api/cli/install: the side panels' Install button
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // GET /api/cli/install { ok, jobs: { [tool]: { state, step, code, error, log, … } } }
6
+ // POST /api/cli/install/:tool NDJSON: start, step, progress, log … done (lib/cli-install.js)
7
+ // GET /api/cli/git-bash Git Bash for Claude Code on Windows (lib/git-bash/), and
8
+ // POST /api/cli/git-bash/install|recheck its download and a new look (createGitBashApi below)
9
+ //
10
+ // Installing software is the owner's own act on this computer, so these routes
11
+ // take the guards of SynaBun's other local mutating endpoints (lib/http-guards.js,
12
+ // as /api/whatsapp): a loopback socket and no proxy header, a localhost Host,
13
+ // no invite guest; a POST also its own Origin, a JSON body, X-SynaBun-UI: 1 and
14
+ // no agent header. (`/api/cli` is admin-only in lib/request-gate.js too.) A
15
+ // request names a tool and nothing else: what runs is fixed by
16
+ // lib/cli-install.js for this system.
17
+ //
18
+ // Refusals before anything starts are JSON: 400 UNKNOWN_TOOL, 409 BUSY (that
19
+ // tool is being installed), 422 UNSUPPORTED (no one-click method here). A tab
20
+ // that goes away does not stop an install; its result is in GET and in the next
21
+ // /api/system/tool-versions.
22
+
23
+ import { Router } from 'express';
24
+ import { guestForbidden, noStore, requireLocal, requireLocalHost, requireSameOriginJson, sendGuardError, uiOnly } from './http-guards.js';
25
+ import { openNdjsonStream } from './whatsapp/api.js';
26
+
27
+ export const INSTALLABLE_TOOLS = Object.freeze(['claude-code', 'codex', 'opencode']);
28
+
29
+ /**
30
+ * @param {object} deps
31
+ * @param {ReturnType<import('./cli-install.js').createCliInstaller>} deps.installer
32
+ * @param {(req) => boolean} [deps.isGuestRequest]
33
+ * @param {number|(() => number)} [deps.port] the port the page is served from (Host / Origin checks)
34
+ */
35
+ function localCliRouter({ isGuestRequest = () => false, port = 3344 } = {}) {
36
+ const router = Router();
37
+ const portOf = () => Number(typeof port === 'function' ? port() : port);
38
+ const built = new Map();
39
+ const perPort = (name, factory) => (req, res, next) => {
40
+ const key = `${name}:${portOf()}`;
41
+ let guard = built.get(key);
42
+ if (!guard) {
43
+ try { guard = factory(portOf()); } catch { return sendGuardError(res, 500, 'BAD_PORT', 'The install API has no valid port.'); }
44
+ built.set(key, guard);
45
+ }
46
+ return guard(req, res, next);
47
+ };
48
+
49
+ router.use(noStore, guestForbidden(isGuestRequest), requireLocal, perPort('host', requireLocalHost));
50
+ const mutating = [perPort('origin', requireSameOriginJson), uiOnly];
51
+ return { router, mutating };
52
+ }
53
+
54
+ export function createCliInstallApi({ installer, isGuestRequest = () => false, port = 3344, log = () => {} } = {}) {
55
+ if (!installer) throw new Error('createCliInstallApi needs the installer');
56
+ const { router, mutating } = localCliRouter({ isGuestRequest, port });
57
+
58
+ router.get('/', (req, res) => {
59
+ res.json({ ok: true, jobs: installer.jobs() });
60
+ });
61
+
62
+ router.post('/:tool', ...mutating, (req, res) => {
63
+ const tool = String(req.params.tool || '');
64
+ if (!INSTALLABLE_TOOLS.includes(tool)) {
65
+ return res.status(400).json({ ok: false, code: 'UNKNOWN_TOOL', error: `Unknown CLI: ${tool.slice(0, 40)}` });
66
+ }
67
+ let stream = null;
68
+ const onEvent = (event) => {
69
+ if (!stream) stream = openNdjsonStream(res);
70
+ stream.write(event);
71
+ if (event?.type === 'done') stream.end();
72
+ };
73
+ let started;
74
+ try {
75
+ started = installer.start(tool, { onEvent });
76
+ } catch (error) {
77
+ const code = error?.code === 'busy' ? 'BUSY' : error?.code === 'unsupported' ? 'UNSUPPORTED' : 'INTERNAL';
78
+ if (code === 'INTERNAL') log(`[cli-install] ${tool} could not start: ${error?.message || error}`);
79
+ if (stream) return stream.end({ type: 'done', ok: false, code: 'internal', error: String(error?.message || error) });
80
+ return res.status(code === 'BUSY' ? 409 : code === 'UNSUPPORTED' ? 422 : 500).json({ ok: false, code, error: String(error?.message || error) });
81
+ }
82
+ if (!stream) stream = openNdjsonStream(res);
83
+ stream.onAbort(() => started.unsubscribe());
84
+ });
85
+
86
+ return router;
87
+ }
88
+
89
+ /**
90
+ * /api/cli/git-bash: the Git Bash every Claude Code SynaBun starts gets on Windows (lib/git-bash/manager.js),
91
+ * beside the CLIs' install and PATH, with the same guards.
92
+ *
93
+ * GET / { ok, state: unsupported | system | ready | missing | downloading | failed, … }
94
+ * POST /install start the download of SynaBun's copy now (Settings: Download, Try again); answers at once,
95
+ * the page follows it with GET. 422 UNSUPPORTED where there is no Windows Git to fetch
96
+ * POST /recheck look again (a Git for Windows installed meanwhile), then the status
97
+ *
98
+ * A request names nothing: what is downloaded is the pinned release (lib/git-bash/pins.json), and a body is refused.
99
+ */
100
+ export function createGitBashApi({ manager, onRecheck = async () => {}, isGuestRequest, port } = {}) {
101
+ if (!manager) throw new Error('createGitBashApi needs the Git Bash manager');
102
+ const { router, mutating } = localCliRouter({ isGuestRequest, port });
103
+ const noBody = (req, res, next) => (Object.keys(req.body || {}).length ? res.status(400).json({ ok: false, code: 'UNEXPECTED_BODY' }) : next());
104
+ router.get('/', (req, res) => { res.json({ ok: true, ...manager.status() }); });
105
+ router.post('/install', ...mutating, noBody, (req, res) => {
106
+ const before = manager.status();
107
+ if (before.state === 'unsupported') return res.status(422).json({ ok: false, code: 'UNSUPPORTED', ...before });
108
+ manager.ensure({ reason: 'settings' });
109
+ res.json({ ok: true, ...manager.status() });
110
+ });
111
+ router.post('/recheck', ...mutating, noBody, async (req, res) => {
112
+ manager.refresh();
113
+ try { await onRecheck(); } catch { /* the status says what is known */ }
114
+ res.json({ ok: true, ...manager.status() });
115
+ });
116
+ return router;
117
+ }
118
+
119
+ /** The same owner/localhost/Origin/UI guards as Install. Never accepts a folder. */
120
+ export function createCliPathApi({ pathManager, resolver, diagnostics = async () => ({}), isGuestRequest, port } = {}) {
121
+ const { router, mutating } = localCliRouter({ isGuestRequest, port });
122
+ router.get('/', async (req, res) => {
123
+ try { res.json({ ok: true, ...await diagnostics() }); }
124
+ catch (error) { res.status(500).json({ ok: false, error: error.message }); }
125
+ });
126
+ router.post('/:tool', ...mutating, async (req, res) => {
127
+ if (!INSTALLABLE_TOOLS.includes(req.params.tool)) return res.status(400).json({ ok: false, code: 'UNKNOWN_TOOL' });
128
+ if (Object.keys(req.body || {}).length) return res.status(400).json({ ok: false, code: 'UNEXPECTED_BODY' });
129
+ try {
130
+ const status = await resolver.resolve(req.params.tool, { force: true });
131
+ const result = await pathManager.add(status);
132
+ if (!result.changed) return res.status(409).json({ ok: false, code: 'NOT_NEEDED', ...result });
133
+ res.json({ ok: true, ...result });
134
+ } catch (error) { res.status(error.status || 500).json({ ok: false, error: error.message }); }
135
+ });
136
+ return router;
137
+ }