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,13 +10,24 @@
10
10
  // resources are symlinked back to ~/.claude so skills/commands/settings stay in
11
11
  // sync; identity-bearing files stay isolated.
12
12
  //
13
- // The `default` account is the ambient ~/.claude and never gets an env override.
13
+ // Default follows CLAUDE_CONFIG_DIR when set, otherwise ~/.claude.
14
+ //
15
+ // One account, two credentials, one mode (docs/claude-sidepanel.md → Claude
16
+ // accounts). Each account can hold a Claude subscription login (Claude Code's
17
+ // own OAuth, inside that account's config directory) and an API key (SynaBun's
18
+ // `.synabun-api-key` file in the same directory). Its mode, "subscription" or
19
+ // "apiKey", decides what its sessions use; when only one credential exists,
20
+ // that one is the mode. describe() is the one reading of an account's state:
21
+ // the account list, the panel's sign-in screens, its composer picker, the
22
+ // Assistant's brain picker, the catalog and worker dispatch all show it.
23
+ // launchFor() is the one place a Claude Code process gets its credential.
14
24
 
15
25
  import {
16
- copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, symlinkSync, writeFileSync,
26
+ existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync,
17
27
  } from 'node:fs';
18
28
  import { dirname, resolve } from 'node:path';
19
29
  import { homedir } from 'node:os';
30
+ import { readClaudeProfileApiKey, saveClaudeProfileApiKey, clearClaudeProfileApiKey } from './claude-profile-api-key.js';
20
31
 
21
32
  export const DEFAULT_ACCOUNT_ID = 'default';
22
33
  // `rules` carries SynaBun's rules file (rules/synabun.md) to accounts created
@@ -26,6 +37,18 @@ export const SHARED_LINKS = ['settings.json', 'CLAUDE.md', 'rules', 'commands',
26
37
  export const ISOLATED_ITEMS = ['.credentials.json', '.claude.json', 'projects', 'todos', 'history.jsonl', 'statsig', 'shell-snapshots', 'plans'];
27
38
  const CLAUDE_JSON_WHITELIST = ['mcpServers', 'theme', 'editorMode', 'preferredNotifChannel', 'autoUpdates', 'verbose'];
28
39
 
40
+ export const CLAUDE_AUTH_MODES = Object.freeze(['subscription', 'apiKey']);
41
+ // Everything inherited from the server's own environment that would decide a
42
+ // Claude Code process's credential or backend. Removed from every account
43
+ // launch: the account and its mode decide, never what SynaBun was started with.
44
+ export const CLAUDE_CREDENTIAL_ENV = Object.freeze([
45
+ 'ANTHROPIC_API_KEY', 'ANTHROPIC_API_KEY_FILE_DESCRIPTOR', 'ANTHROPIC_AUTH_TOKEN',
46
+ 'CLAUDE_CODE_OAUTH_TOKEN', 'CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR',
47
+ 'CLAUDE_CODE_USE_BEDROCK', 'CLAUDE_CODE_USE_VERTEX', 'CLAUDE_CODE_USE_FOUNDRY',
48
+ ]);
49
+ // A subscription check older than this is asked again when the list is read.
50
+ const STATUS_MAX_AGE_MS = 5 * 60_000;
51
+
29
52
  export class ClaudeAccountError extends Error {
30
53
  constructor(code, message, status = 400) {
31
54
  super(message);
@@ -49,11 +72,22 @@ function sanitizeLabel(value, fallback = 'Claude account') {
49
72
  const label = String(value || '').replace(/[\r\n\t]/g, ' ').trim().slice(0, 80);
50
73
  return label || fallback;
51
74
  }
75
+ const text = (value, max = 200) => (typeof value === 'string' && value.trim() ? value.trim().slice(0, max) : null);
76
+
77
+ /**
78
+ * Claude Code's global config file (`.claude.json`, where `oauthAccount` lives)
79
+ * for a config directory. An explicit CLAUDE_CONFIG_DIR keeps it inside that
80
+ * directory; the ambient default keeps it in the home directory, next to
81
+ * ~/.claude rather than inside it (the CLI's own layout).
82
+ */
83
+ export function claudeGlobalConfigFile(configDir, { ambient = false, homeDir = homedir() } = {}) {
84
+ return ambient ? resolve(homeDir, '.claude.json') : resolve(configDir, '.claude.json');
85
+ }
52
86
 
53
87
  /** Read the OAuth identity Claude Code records after login. */
54
- export function readClaudeIdentity(configDir) {
55
- if (!configDir) return null;
56
- const data = readJson(resolve(configDir, '.claude.json'), null);
88
+ export function readClaudeIdentity(configDir, file = null) {
89
+ if (!configDir && !file) return null;
90
+ const data = readJson(file || resolve(configDir, '.claude.json'), null);
57
91
  const account = data?.oauthAccount;
58
92
  if (!account || typeof account !== 'object') return null;
59
93
  return {
@@ -63,58 +97,312 @@ export function readClaudeIdentity(configDir) {
63
97
  };
64
98
  }
65
99
 
100
+ /** What an API key is shown as: never the key, never its prefix. */
101
+ export function apiKeyHint(key) {
102
+ return typeof key === 'string' && key.length >= 8 ? `…${key.slice(-4)}` : null;
103
+ }
104
+
105
+ /**
106
+ * The mode an account's sessions use, from its stored choice and the
107
+ * credentials it holds. `subscription` is true (signed in), false (confirmed
108
+ * signed out) or null (not known yet). A stored key with an unconfirmed
109
+ * subscription keeps the key unless the user chose the subscription; both
110
+ * present with no choice keeps the key, which is what SynaBun did before modes.
111
+ */
112
+ export function resolveClaudeAuth({ preferredMode = null, hasKey = false, subscription = null } = {}) {
113
+ const preferred = CLAUDE_AUTH_MODES.includes(preferredMode) ? preferredMode : null;
114
+ let mode = null;
115
+ if (hasKey && subscription === true) mode = preferred || 'apiKey';
116
+ else if (hasKey) mode = preferred === 'subscription' && subscription !== false ? 'subscription' : 'apiKey';
117
+ else if (subscription !== false) mode = 'subscription';
118
+ const signedIn = mode === 'apiKey' ? true : mode === 'subscription' ? subscription : false;
119
+ return { mode, signedIn, switchable: hasKey && subscription === true };
120
+ }
121
+
122
+ /** A launch environment applied to a base: inherited credentials out, the account's in. */
123
+ export function applyClaudeLaunchEnv(base, launch) {
124
+ const env = { ...(base || {}) };
125
+ for (const key of launch?.unset || CLAUDE_CREDENTIAL_ENV) delete env[key];
126
+ return Object.assign(env, launch?.env || {});
127
+ }
128
+
66
129
  export function createClaudeAccounts({
67
130
  dataHome,
68
131
  homeDir = homedir(),
132
+ env = process.env,
69
133
  accountsRoot = null,
70
134
  log = () => {},
71
135
  now = Date.now,
72
136
  pollIntervalMs = 2000,
137
+ // async (id) => Claude Code's own `auth status --json` for that account
138
+ // ({ loggedIn, email, orgName, subscriptionType, authMethod }), or null.
139
+ readNativeStatus = null,
140
+ statusTimeoutMs = 20_000,
73
141
  } = {}) {
74
142
  if (!dataHome) throw new Error('createClaudeAccounts requires dataHome');
75
143
  const registryPath = resolve(dataHome, 'data', 'claude-accounts.json');
76
- const defaultHome = resolve(homeDir, '.claude');
144
+ const configDir = env.CLAUDE_CONFIG_DIR;
145
+ const defaultHome = resolve(configDir || resolve(homeDir, '.claude'));
146
+ const defaultConfigFile = claudeGlobalConfigFile(defaultHome, { ambient: !configDir, homeDir });
77
147
  const root = accountsRoot || resolve(homeDir, '.claude-accounts');
148
+ // id → the last answer of Claude Code's own status check (never a credential).
149
+ const nativeStatus = new Map();
150
+ const refreshing = new Map();
151
+ const listeners = new Set();
152
+ let nativeReader = typeof readNativeStatus === 'function' ? readNativeStatus : null;
78
153
 
154
+ function configFileOf(id, home) { return id === DEFAULT_ACCOUNT_ID ? defaultConfigFile : resolve(home, '.claude.json'); }
79
155
  function defaultAccount() {
80
- const identity = readClaudeIdentity(defaultHome);
156
+ const identity = readClaudeIdentity(defaultHome, defaultConfigFile);
81
157
  return { id: DEFAULT_ACCOUNT_ID, label: 'Default', email: identity?.email || null, organization: identity?.organization || null, home: defaultHome, builtin: true, createdAt: null };
82
158
  }
83
159
  function load() {
84
160
  const data = readJson(registryPath, null);
85
161
  const accounts = Array.isArray(data?.accounts) ? data.accounts.filter((row) => row?.id && row.id !== DEFAULT_ACCOUNT_ID) : [];
86
- return { accounts, defaultId: DEFAULT_ACCOUNT_ID };
162
+ const defaults = data?.default && typeof data.default === 'object' ? data.default : {};
163
+ return { accounts, defaultId: DEFAULT_ACCOUNT_ID, default: defaults };
164
+ }
165
+ function save(data) {
166
+ writeJsonAtomic(registryPath, { accounts: data.accounts, defaultId: DEFAULT_ACCOUNT_ID, default: data.default || {}, updatedAt: new Date(now()).toISOString() });
167
+ }
168
+ function storedMode(data, id) {
169
+ const mode = id === DEFAULT_ACCOUNT_ID ? data.default?.authMode : data.accounts.find((row) => row.id === id)?.authMode;
170
+ return CLAUDE_AUTH_MODES.includes(mode) ? mode : null;
87
171
  }
88
- function save(data) { writeJsonAtomic(registryPath, { accounts: data.accounts, defaultId: DEFAULT_ACCOUNT_ID, updatedAt: new Date(now()).toISOString() }); }
89
172
 
90
- function list() {
173
+ // Terminal login needs only registry metadata, without reading CLI identity files.
174
+ function profileFor(id) {
175
+ if (id === DEFAULT_ACCOUNT_ID) return { id, home: defaultHome };
176
+ return load().accounts.find(row => row.id === id) || null;
177
+ }
178
+ function keyHome(id) {
179
+ const row = profileFor(id);
180
+ if (!row || row.id !== id) throw new ClaudeAccountError('ACCOUNT_NOT_FOUND', 'Unknown Claude account', 404);
181
+ return row.home;
182
+ }
183
+ function readApiKey(id) { return readClaudeProfileApiKey(keyHome(id)); }
184
+ function saveApiKey(id, key) {
185
+ saveClaudeProfileApiKey(keyHome(id), key);
186
+ // A key just pasted is what the user wants this account to use.
187
+ writeMode(id, 'apiKey');
188
+ emit(id);
189
+ }
190
+ function clearApiKey(id) {
191
+ clearClaudeProfileApiKey(keyHome(id));
91
192
  const data = load();
92
- const rows = data.accounts.map((row) => {
93
- const identity = readClaudeIdentity(row.home);
94
- return { ...row, email: identity?.email || row.email || null, organization: identity?.organization || row.organization || null, builtin: false, loggedIn: !!identity?.email };
95
- });
96
- const base = defaultAccount();
97
- return [{ ...base, loggedIn: !!base.email }, ...rows];
193
+ if (storedMode(data, id) === 'apiKey') writeMode(id, null, data);
194
+ emit(id);
98
195
  }
99
- function listForClient() {
100
- return list().map(({ id, label, email, organization, builtin, loggedIn, createdAt }) => ({ id, label, email, organization, isDefault: id === DEFAULT_ACCOUNT_ID, builtin, loggedIn, createdAt }));
196
+ // The key itself only for a launch; everything else sees whether one is there.
197
+ function keyState(id) {
198
+ try {
199
+ const key = readApiKey(id);
200
+ return key ? { present: true, hint: apiKeyHint(key), error: null, key } : { present: false, hint: null, error: null, key: null };
201
+ } catch (error) {
202
+ if (error?.code === 'ACCOUNT_NOT_FOUND') throw error;
203
+ return { present: false, hint: null, error: 'unreadable', key: null };
204
+ }
205
+ }
206
+ function fileChangedSince(file, at) {
207
+ try { return statSync(file).mtimeMs > at; } catch { return false; }
208
+ }
209
+ function subscriptionOf(id, home) {
210
+ const file = configFileOf(id, home);
211
+ const status = nativeStatus.get(id);
212
+ // Claude Code rewrites its global config when it signs in or out, so a file
213
+ // newer than the last check outranks that check until it is asked again.
214
+ if (status && !fileChangedSince(file, status.wall)) {
215
+ return {
216
+ signedIn: status.loggedIn, email: status.email, organization: status.organization,
217
+ plan: status.plan, method: status.method, checkedAt: status.checkedAt, checked: true,
218
+ };
219
+ }
220
+ // Its own record of an OAuth login, if any: a login is likely, its absence proves nothing.
221
+ const identity = readClaudeIdentity(home, file);
222
+ const signedIn = identity?.email ? true : status?.loggedIn === false ? false : null;
223
+ return {
224
+ signedIn, email: identity?.email || null, organization: identity?.organization || null,
225
+ plan: identity?.email && status?.loggedIn ? status.plan : null, method: identity?.email ? 'claude.ai' : null,
226
+ checkedAt: status?.checkedAt || null, checked: false,
227
+ };
101
228
  }
102
- function get(id) {
229
+ function describeProfile(profile, data = load()) {
230
+ const id = profile.id;
231
+ const isDefault = id === DEFAULT_ACCOUNT_ID;
232
+ const base = isDefault ? defaultAccount() : profile;
233
+ const key = keyState(id);
234
+ const subscription = subscriptionOf(id, base.home);
235
+ const preferredMode = storedMode(data, id);
236
+ const auth = resolveClaudeAuth({ preferredMode, hasKey: key.present, subscription: subscription.signedIn });
237
+ const identity = auth.mode === 'apiKey' ? key.hint : auth.mode === 'subscription' ? (subscription.email || subscription.organization) : null;
238
+ const row = {
239
+ id, provider: 'claude', label: isDefault ? 'Default' : sanitizeLabel(base.label, 'Claude account'), home: base.home,
240
+ isDefault, builtin: isDefault, createdAt: base.createdAt || null,
241
+ mode: auth.mode, preferredMode, signedIn: auth.signedIn, switchable: auth.switchable, identity: identity || null,
242
+ subscription: { signedIn: subscription.signedIn, email: subscription.email, organization: subscription.organization, plan: subscription.plan, method: subscription.method, checked: subscription.checked, checkedAt: subscription.checkedAt },
243
+ apiKey: { present: key.present, hint: key.hint, error: key.error },
244
+ // The fields older panels read, from the same state.
245
+ loggedIn: auth.signedIn,
246
+ email: auth.mode === 'subscription' ? subscription.email : null,
247
+ organization: auth.mode === 'subscription' ? subscription.organization : null,
248
+ authMethod: auth.mode === 'apiKey' ? 'apiKey' : auth.mode === 'subscription' && subscription.signedIn ? (subscription.method || 'claude.ai') : null,
249
+ // Kept in the registry for older readers; the live record wins.
250
+ registryEmail: isDefault ? null : profile.email || null,
251
+ };
252
+ return { row, key: key.key };
253
+ }
254
+ function describe(id) {
103
255
  const wanted = sanitizeId(id) || DEFAULT_ACCOUNT_ID;
104
- if (wanted === DEFAULT_ACCOUNT_ID) return { ...defaultAccount(), loggedIn: !!defaultAccount().email };
105
- return list().find((row) => row.id === wanted) || null;
256
+ const data = load();
257
+ const profile = wanted === DEFAULT_ACCOUNT_ID ? { id: DEFAULT_ACCOUNT_ID, home: defaultHome } : data.accounts.find((row) => row.id === wanted);
258
+ if (!profile) return null;
259
+ return describeProfile(profile, data).row;
260
+ }
261
+ function list() {
262
+ const data = load();
263
+ return [{ id: DEFAULT_ACCOUNT_ID, home: defaultHome }, ...data.accounts].map((profile) => describeProfile(profile, data).row);
264
+ }
265
+ function clientRow(row) {
266
+ const { home, registryEmail, ...rest } = row;
267
+ return rest;
106
268
  }
269
+ function listForClient() { return list().map(clientRow); }
270
+ function describeForClient(id) { const row = describe(id); return row ? clientRow(row) : null; }
271
+ function get(id) { return describe(id); }
107
272
  const find = get;
108
273
  function homeFor(id) {
109
- const account = get(id);
110
- if (!account || account.id === DEFAULT_ACCOUNT_ID) return null;
111
- return account.home;
274
+ const wanted = sanitizeId(id);
275
+ if (!wanted || wanted === DEFAULT_ACCOUNT_ID) return null;
276
+ return profileFor(wanted)?.home || null;
112
277
  }
113
278
  function envFor(id) {
279
+ // Native auth strips ambient config/auth variables before applying this
280
+ // account's environment. Restore Default's explicit config location too.
281
+ if (id === DEFAULT_ACCOUNT_ID && configDir) return { CLAUDE_CONFIG_DIR: defaultHome };
114
282
  const home = homeFor(id);
115
283
  return home ? { CLAUDE_CONFIG_DIR: home } : {};
116
284
  }
117
285
 
286
+ /**
287
+ * How a Claude Code process runs under an account: `unset` the inherited
288
+ * credential variables, then add `env` (its config directory and, in API-key
289
+ * mode, its key). `env` can carry a secret: pass it to the spawn only, never
290
+ * to a log, a response, an event or a file.
291
+ */
292
+ function launchFor(id) {
293
+ const wanted = typeof id === 'string' && id ? id : DEFAULT_ACCOUNT_ID;
294
+ const data = load();
295
+ const profile = wanted === DEFAULT_ACCOUNT_ID ? { id: DEFAULT_ACCOUNT_ID, home: defaultHome } : data.accounts.find((row) => row.id === wanted);
296
+ if (!profile || !profile.home) {
297
+ return { ok: false, accountId: wanted, code: 'account_unavailable', message: 'That Claude account is no longer set up in SynaBun.' };
298
+ }
299
+ const { row, key } = describeProfile(profile, data);
300
+ const launchEnv = {};
301
+ if (wanted !== DEFAULT_ACCOUNT_ID) launchEnv.CLAUDE_CONFIG_DIR = profile.home;
302
+ else if (configDir) launchEnv.CLAUDE_CONFIG_DIR = defaultHome;
303
+ if (row.mode === 'apiKey' && key) launchEnv.ANTHROPIC_API_KEY = key;
304
+ return {
305
+ ok: true, accountId: wanted, label: row.label, mode: row.mode, signedIn: row.signedIn, identity: row.identity,
306
+ env: launchEnv, unset: [...CLAUDE_CREDENTIAL_ENV],
307
+ };
308
+ }
309
+
310
+ /** Why an account cannot run a session now ('' when it can, or when that is not known yet). */
311
+ function unusableReason(id) {
312
+ const row = describe(id);
313
+ if (!row) return 'That Claude account is no longer set up in SynaBun.';
314
+ if (row.apiKey.error && row.mode !== 'subscription') return `The API key saved for ${row.label} cannot be read. Save it again.`;
315
+ if (row.signedIn === false) return `${row.label} is signed out. Sign in with Claude or add an API key to it first.`;
316
+ return '';
317
+ }
318
+
319
+ function writeMode(id, mode, data = load()) {
320
+ if (id === DEFAULT_ACCOUNT_ID) {
321
+ data.default = { ...(data.default || {}) };
322
+ if (mode) data.default.authMode = mode; else delete data.default.authMode;
323
+ } else {
324
+ const account = data.accounts.find((row) => row.id === id);
325
+ if (!account) throw new ClaudeAccountError('ACCOUNT_NOT_FOUND', `Claude account not found: ${id}`, 404);
326
+ if (mode) account.authMode = mode; else delete account.authMode;
327
+ }
328
+ save(data);
329
+ }
330
+ /** Choose what an account's sessions use. The chosen credential must be there. */
331
+ function setMode(id, mode) {
332
+ const wanted = sanitizeId(id) || DEFAULT_ACCOUNT_ID;
333
+ if (!CLAUDE_AUTH_MODES.includes(mode)) throw new ClaudeAccountError('MODE_INVALID', 'Choose "subscription" or "apiKey".', 400);
334
+ const row = describe(wanted);
335
+ if (!row) throw new ClaudeAccountError('ACCOUNT_NOT_FOUND', `Claude account not found: ${id}`, 404);
336
+ if (mode === 'apiKey' && !row.apiKey.present) throw new ClaudeAccountError('MODE_NEEDS_CREDENTIAL', `Add an API key to ${row.label} first.`, 409);
337
+ if (mode === 'subscription' && row.subscription.signedIn === false) throw new ClaudeAccountError('MODE_NEEDS_CREDENTIAL', `Sign in to ${row.label} with Claude first.`, 409);
338
+ writeMode(wanted, mode);
339
+ emit(wanted);
340
+ return describe(wanted);
341
+ }
342
+
343
+ /** Remember what Claude Code said about an account's own login. */
344
+ function noteNativeStatus(id, status) {
345
+ if (!status || typeof status.loggedIn !== 'boolean') return;
346
+ nativeStatus.set(id, {
347
+ loggedIn: status.loggedIn,
348
+ email: status.loggedIn ? text(status.email) : null,
349
+ organization: status.loggedIn ? text(status.orgName ?? status.organization) : null,
350
+ plan: status.loggedIn ? text(status.subscriptionType ?? status.plan, 40) : null,
351
+ method: status.loggedIn ? text(status.authMethod ?? status.method, 40) : null,
352
+ checkedAt: now(),
353
+ // Compared with the config file's modification time: the clock files use.
354
+ wall: Date.now(),
355
+ });
356
+ }
357
+ function forgetNativeStatus(id) { nativeStatus.delete(id); }
358
+ function setNativeStatusReader(reader) { nativeReader = typeof reader === 'function' ? reader : null; }
359
+ function signature(row) { return row ? JSON.stringify([row.label, row.mode, row.signedIn, row.identity, row.subscription.signedIn, row.subscription.email, row.subscription.plan, row.apiKey.present, row.apiKey.hint, row.preferredMode]) : ''; }
360
+ function checkOne(id) {
361
+ if (refreshing.has(id)) return refreshing.get(id);
362
+ const job = (async () => {
363
+ let timer;
364
+ try {
365
+ const status = await Promise.race([
366
+ Promise.resolve().then(() => nativeReader(id)),
367
+ new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('timeout')), statusTimeoutMs); timer.unref?.(); }),
368
+ ]);
369
+ if (status && typeof status.loggedIn === 'boolean' && profileFor(id)) noteNativeStatus(id, status);
370
+ } catch (error) {
371
+ log('claude-accounts', `status check failed for ${id}: ${String(error?.message || error).slice(0, 120)}`);
372
+ } finally { clearTimeout(timer); refreshing.delete(id); }
373
+ })();
374
+ refreshing.set(id, job);
375
+ return job;
376
+ }
377
+ /**
378
+ * Ask Claude Code for the subscription state of these accounts (all by
379
+ * default): `force` asks again, otherwise only an answer older than
380
+ * `maxAgeMs` or none at all. Listeners hear about every account that changed.
381
+ */
382
+ async function refreshStatus(ids = null, { force = false, maxAgeMs = STATUS_MAX_AGE_MS } = {}) {
383
+ if (!nativeReader) return list();
384
+ const wanted = (Array.isArray(ids) ? ids : [DEFAULT_ACCOUNT_ID, ...load().accounts.map((row) => row.id)]).filter((id) => profileFor(id));
385
+ const before = new Map(wanted.map((id) => [id, signature(describe(id))]));
386
+ await Promise.all(wanted.map((id) => {
387
+ const known = nativeStatus.get(id);
388
+ if (!force && known && now() - known.checkedAt < maxAgeMs && !stale(id, maxAgeMs)) return null;
389
+ return checkOne(id);
390
+ }));
391
+ for (const id of wanted) if (before.get(id) !== signature(describe(id))) emit(id);
392
+ return list();
393
+ }
394
+ function stale(id, maxAgeMs = STATUS_MAX_AGE_MS) {
395
+ const known = nativeStatus.get(id);
396
+ if (!known || now() - known.checkedAt >= maxAgeMs) return true;
397
+ const profile = profileFor(id);
398
+ return !!profile && fileChangedSince(configFileOf(id, profile.home), known.wall);
399
+ }
400
+
401
+ function subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); }
402
+ function emit(accountId, extra = {}) {
403
+ for (const listener of [...listeners]) { try { listener({ accountId, ...extra }); } catch {} }
404
+ }
405
+
118
406
  function linkShared(home) {
119
407
  const linked = [];
120
408
  for (const name of SHARED_LINKS) {
@@ -136,15 +424,17 @@ export function createClaudeAccounts({
136
424
  function seedClaudeJson(home) {
137
425
  const target = resolve(home, '.claude.json');
138
426
  if (existsSync(target)) return false;
139
- const source = readJson(resolve(defaultHome, '.claude.json'), {}) || {};
427
+ // Only shared preferences from Default's global config; never its login.
428
+ const source = readJson(defaultConfigFile, {}) || {};
140
429
  const seeded = { hasCompletedOnboarding: true };
141
430
  for (const key of CLAUDE_JSON_WHITELIST) if (source[key] !== undefined) seeded[key] = source[key];
142
431
  writeJsonAtomic(target, seeded);
143
432
  return true;
144
433
  }
145
434
  function seedHome(id) {
146
- const account = get(id);
147
- if (!account || account.id === DEFAULT_ACCOUNT_ID) throw new ClaudeAccountError('ACCOUNT_NOT_FOUND', `Claude account not found: ${id}`, 404);
435
+ const wanted = sanitizeId(id);
436
+ const account = wanted && wanted !== DEFAULT_ACCOUNT_ID ? profileFor(wanted) : null;
437
+ if (!account) throw new ClaudeAccountError('ACCOUNT_NOT_FOUND', `Claude account not found: ${id}`, 404);
148
438
  mkdirSync(resolve(account.home, 'projects'), { recursive: true });
149
439
  mkdirSync(resolve(account.home, 'todos'), { recursive: true });
150
440
  const linked = linkShared(account.home);
@@ -156,10 +446,14 @@ export function createClaudeAccounts({
156
446
  const data = load();
157
447
  const id = `cacct-${now().toString(36)}${Math.floor(Math.random() * 1296).toString(36).padStart(2, '0')}`;
158
448
  const account = { id, label: sanitizeLabel(label, `Claude ${data.accounts.length + 2}`), email: null, organization: null, home: resolve(root, id), createdAt: new Date(now()).toISOString() };
449
+ if (label && String(label).trim()) account.labelCustom = true;
159
450
  data.accounts.push(account);
160
451
  save(data);
161
452
  const seeded = seedHome(id);
162
- return { ...account, isDefault: false, builtin: false, loggedIn: false, seeded };
453
+ // A directory made a moment ago holds no login: nothing to ask Claude Code.
454
+ noteNativeStatus(id, { loggedIn: false });
455
+ emit(id, { created: true });
456
+ return { ...describe(id), seeded };
163
457
  }
164
458
  function rename(id, label) {
165
459
  const wanted = sanitizeId(id);
@@ -168,7 +462,9 @@ export function createClaudeAccounts({
168
462
  const account = data.accounts.find((row) => row.id === wanted);
169
463
  if (!account) throw new ClaudeAccountError('ACCOUNT_NOT_FOUND', `Claude account not found: ${id}`, 404);
170
464
  account.label = sanitizeLabel(label, account.label);
465
+ account.labelCustom = true;
171
466
  save(data);
467
+ emit(wanted);
172
468
  return get(wanted);
173
469
  }
174
470
  function remove(id, { inUse = () => null } = {}) {
@@ -181,9 +477,11 @@ export function createClaudeAccounts({
181
477
  if (reason) throw new ClaudeAccountError('ACCOUNT_IN_USE', typeof reason === 'string' ? reason : `Claude account ${wanted} is in use`, 409);
182
478
  const [account] = data.accounts.splice(index, 1);
183
479
  save(data);
480
+ nativeStatus.delete(wanted);
184
481
  if (account.home && resolve(account.home).startsWith(resolve(root))) {
185
482
  try { rmSync(account.home, { recursive: true, force: true }); } catch (error) { log('claude-accounts', `remove home failed: ${error?.message || error}`); }
186
483
  }
484
+ emit(wanted, { removed: true });
187
485
  return { ok: true, removed: account.id };
188
486
  }
189
487
  function recordIdentity(id) {
@@ -201,6 +499,16 @@ export function createClaudeAccounts({
201
499
  }
202
500
  return { ...account, ...identity };
203
501
  }
502
+ /** A subscription sign-in just finished: it is what the account uses from now on. */
503
+ function noteSubscriptionSignIn(id, status = null) {
504
+ const wanted = sanitizeId(id) || DEFAULT_ACCOUNT_ID;
505
+ if (!profileFor(wanted)) return null;
506
+ if (status) noteNativeStatus(wanted, status);
507
+ if (wanted !== DEFAULT_ACCOUNT_ID) recordIdentity(wanted);
508
+ writeMode(wanted, 'subscription');
509
+ emit(wanted);
510
+ return describe(wanted);
511
+ }
204
512
  /** Poll for the OAuth identity to appear after a login terminal was opened. */
205
513
  function watchLogin(id, { onLogin = () => {}, onTimeout = () => {}, timeoutMs = 10 * 60_000 } = {}) {
206
514
  const started = now();
@@ -223,8 +531,11 @@ export function createClaudeAccounts({
223
531
  }
224
532
 
225
533
  return {
226
- registryPath, defaultHome, root,
227
- list, listForClient, get, find, homeFor, envFor, create, rename, remove, seedHome, recordIdentity, watchLogin, loginCommand,
228
- readIdentity: (id) => readClaudeIdentity(id === DEFAULT_ACCOUNT_ID ? defaultHome : homeFor(id)),
534
+ registryPath, defaultHome, defaultConfigFile, root,
535
+ list, listForClient, describe, describeForClient, profileFor, readApiKey, saveApiKey, clearApiKey, get, find, homeFor, envFor,
536
+ launchFor, unusableReason, setMode, noteNativeStatus, forgetNativeStatus, noteSubscriptionSignIn, setNativeStatusReader,
537
+ refreshStatus, stale, subscribe,
538
+ create, rename, remove, seedHome, recordIdentity, watchLogin, loginCommand,
539
+ readIdentity: (id) => (id === DEFAULT_ACCOUNT_ID ? readClaudeIdentity(defaultHome, defaultConfigFile) : readClaudeIdentity(homeFor(id))),
229
540
  };
230
541
  }
@@ -1,3 +1,5 @@
1
+ import { redactProviderSecrets } from './provider-panel-api-key-auth.js';
2
+ import { applyClaudeLaunchEnv, CLAUDE_CREDENTIAL_ENV } from './claude-accounts.js';
1
3
  import { claudeStallSeconds } from './claude-stall-policy.js';
2
4
  import { detectProject as memoryProject } from '../../mcp-server/dist/config.js';
3
5
  // ── Claude Agent SDK bridge for the Claude sidepanel (claude-skin) ──
@@ -223,7 +225,7 @@ const _allSessions = new Set();
223
225
 
224
226
  function _orphanKey(wid, sid) { return sid ? `${wid}:${sid}` : wid; }
225
227
 
226
- function log(...args) { console.log('[claude-sdk]', ...args); }
228
+ function log(...args) { console.log('[claude-sdk]', ...args.map(redactProviderSecrets)); }
227
229
 
228
230
  // Panel sessions, a temporary chat (lib/claude-temporary.js): the plan files
229
231
  // Claude Code wrote for one that still have to be removed. path → { plansDirs,
@@ -415,6 +417,7 @@ export class ClaudeSession {
415
417
  // ── wire helpers ──────────────────────────────────────────────────────────
416
418
 
417
419
  send(data) {
420
+ data = redactProviderSecrets(data);
418
421
  if (this.destroyed) return;
419
422
  if (this.orphanBuffer) {
420
423
  // High-volume deltas are not buffered while detached — full assistant
@@ -451,6 +454,7 @@ export class ClaudeSession {
451
454
  // without a prompt: the first real ensureQuery() adopts it if the session's
452
455
  // configuration is still the one it was started with.
453
456
  ensureQuery({ warm = false } = {}) {
457
+ if (this._authChanging) { this._startRefused = { code: 'auth_changing', message: 'Sign-in is changing. Try again.' }; return null; }
454
458
  if (this.q) return this.q;
455
459
  // Panel sessions under a named Claude account: every process start checks
456
460
  // the account again (a message, a recreation after a stall, a lost-session
@@ -480,6 +484,16 @@ export class ClaudeSession {
480
484
  return null;
481
485
  }
482
486
  let accountEnv = null;
487
+ if (!this.panel && typeof this.opts.claudeAccountId === 'string' && this.opts.claudeAccountId && this.opts.claudeAccountId !== 'default'
488
+ && typeof deps.claudeAccountLaunch === 'function') {
489
+ let launch = null;
490
+ try { launch = deps.claudeAccountLaunch(this.opts.claudeAccountId); } catch { launch = null; }
491
+ if (!launch?.ok) {
492
+ this._startRefused = { code: 'account_unavailable', message: ACCOUNT_UNAVAILABLE };
493
+ log(`session create refused: the account "${this.opts.claudeAccountId}" is no longer set up`);
494
+ return null;
495
+ }
496
+ }
483
497
  if (this.panel && this.accountId) {
484
498
  accountEnv = this._accountEnvOf(this.accountId);
485
499
  if (!accountEnv) {
@@ -609,9 +623,24 @@ export class ClaudeSession {
609
623
  abortController: this.abortController,
610
624
  stderr: (text) => {
611
625
  const t = String(text || '').trim();
612
- if (t) this.send({ type: 'stderr', text: t });
626
+ // Raw diagnostic chunks can split a credential. API-key sessions use
627
+ // the SDK's structured errors instead of forwarding raw stderr.
628
+ if (t && !this._usesProfileApiKey) this.send({ type: 'stderr', text: t });
613
629
  },
614
630
  };
631
+ // The account decides the credential (lib/claude-accounts.js launchFor()):
632
+ // every inherited credential variable is removed, then the account's own
633
+ // config directory and, in API-key mode, its key are added. Subscription
634
+ // mode adds no key, so Claude Code uses the login in that directory. No
635
+ // other account's key, and nothing SynaBun was started with, reaches it.
636
+ const launch = this._launchFor(accountEnv);
637
+ this._usesProfileApiKey = !!launch?.env?.ANTHROPIC_API_KEY;
638
+ this._credentialMode = launch?.mode || null; // what the process was given: 'subscription' | 'apiKey'
639
+ if (launch) options.env = applyClaudeLaunchEnv(options.env, launch);
640
+ // On Windows, the Git Bash the host names for this process (lib/git-bash/: SynaBun's own copy on a PC without
641
+ // one); without it Claude Code has no Bash tool and runs the hooks in PowerShell. Panel tabs and the brain.
642
+ if (typeof deps?.childEnv === 'function') options.env = deps.childEnv(options.env);
643
+
615
644
  if (Array.isArray(sessionOpts.disallowedTools) && sessionOpts.disallowedTools.length) options.disallowedTools = [...sessionOpts.disallowedTools];
616
645
  if (this.panel) {
617
646
  // The process is launched able to take Bypass, so the tab's mode control
@@ -920,6 +949,29 @@ export class ClaudeSession {
920
949
  return env;
921
950
  }
922
951
 
952
+ // The launch of the account this session runs under (the host's
953
+ // claudeAccountLaunch(), lib/claude-accounts.js launchFor()), or null when
954
+ // the session names none: panel sessions always do ('' = the default
955
+ // account); any other session only when it was given `claudeAccountId` (the
956
+ // Assistant's brain). A host without claudeAccountLaunch gets what it had:
957
+ // the named account's directory and the profile key, nothing stripped for
958
+ // a default account without a key.
959
+ _launchFor(accountEnv) {
960
+ const named = this.panel ? (this.accountId || 'default') : (typeof this.opts.claudeAccountId === 'string' && this.opts.claudeAccountId ? this.opts.claudeAccountId : null);
961
+ if (!named) return null;
962
+ if (typeof deps.claudeAccountLaunch === 'function') {
963
+ let launch = null;
964
+ try { launch = deps.claudeAccountLaunch(named); } catch { launch = null; }
965
+ if (launch?.ok) return launch;
966
+ return null;
967
+ }
968
+ if (!this.panel) return null;
969
+ let key = null;
970
+ try { key = deps.claudeAccountApiKey?.(named) || null; } catch { key = null; }
971
+ if (!accountEnv && !key) return null;
972
+ return { ok: true, accountId: named, mode: key ? 'apiKey' : 'subscription', env: { ...(accountEnv || {}), ...(key ? { ANTHROPIC_API_KEY: key } : {}) }, unset: [...CLAUDE_CREDENTIAL_ENV] };
973
+ }
974
+
923
975
  // A start ensureQuery() refused (see there): tell the tab why and close the
924
976
  // turn it is waiting on. False when the last start was not refused.
925
977
  _announceRefusedStart() {
@@ -1383,6 +1435,11 @@ export class ClaudeSession {
1383
1435
  }
1384
1436
  return;
1385
1437
  }
1438
+ case 'rate_limit_event': {
1439
+ try { deps?.accountRateLimit?.(this.panel ? this.accountId || 'default' : this.opts.claudeAccountId || 'default', m.rate_limit_info); } catch {}
1440
+ this.sendEvent(m);
1441
+ return;
1442
+ }
1386
1443
  case 'result': {
1387
1444
  this._flushDeltas();
1388
1445
  this._handleResult(m);
@@ -1645,7 +1702,7 @@ export class ClaudeSession {
1645
1702
  this.sendEvent({
1646
1703
  type: 'system', subtype: 'session_info',
1647
1704
  info: {
1648
- account: r.account || null,
1705
+ account: this._usesProfileApiKey ? { authMethod: 'apiKey' } : r.account || null,
1649
1706
  outputStyle: r.output_style || '',
1650
1707
  availableOutputStyles: Array.isArray(r.available_output_styles) ? r.available_output_styles : [],
1651
1708
  models: Array.isArray(r.models) ? r.models : [],
@@ -3128,6 +3185,28 @@ export class ClaudeSession {
3128
3185
  }
3129
3186
  }
3130
3187
 
3188
+ // Credentials are fixed in the CLI environment. Retire only this account's
3189
+ // idle panel processes before changing its key; hold starts until storage/logout
3190
+ // finishes. Other accounts and non-panel SDK callers are unaffected.
3191
+ // (The Assistant's brain names its account with `claudeAccountId`: retired the same way.)
3192
+ const sessionAccountOf = (s) => (s.panel ? (s.accountId || 'default') : (typeof s.opts?.claudeAccountId === 'string' && s.opts.claudeAccountId ? s.opts.claudeAccountId : null));
3193
+ export async function withPanelAccountAuthChange(accountId, change) {
3194
+ const sessions = [..._allSessions].filter(s => !s.destroyed && sessionAccountOf(s) === accountId);
3195
+ if (sessions.some(s => s.inTurn || s.pendingTurns || s.pendingPerms.size || s._hasPendingWork() || s._authChanging)) {
3196
+ throw Object.assign(new Error('Finish or stop this account’s work before changing sign-in.'), { status: 409 });
3197
+ }
3198
+ for (const session of sessions) session._authChanging = true;
3199
+ try {
3200
+ await Promise.all(sessions.map(session => session._endQuery({ graceful: false })));
3201
+ return await change();
3202
+ } finally { for (const session of sessions) session._authChanging = false; }
3203
+ }
3204
+
3205
+ /** Whether a live session (a panel tab's, the Assistant brain's) runs under this account. */
3206
+ export function accountInUse(accountId) {
3207
+ return [..._allSessions].some(s => !s.destroyed && sessionAccountOf(s) === accountId);
3208
+ }
3209
+
3131
3210
  // ── connection layer ─────────────────────────────────────────────────────────
3132
3211
 
3133
3212
  export function createClaudeBridge(ws) {
@@ -3216,6 +3295,14 @@ export function isSessionLive(sessionId) {
3216
3295
  return [..._allSessions].some(s => !s.destroyed && s.sessionId === sessionId && !!s.q);
3217
3296
  }
3218
3297
 
3298
+ // How many side-panel sessions are working (a turn, a question, background tasks
3299
+ // or crons, a sign-in change): the desktop app waits for them before it restarts
3300
+ // into an update (GET /api/system/activity). The Assistant's brain counts there itself.
3301
+ export function panelSessionsWorking() {
3302
+ return [..._allSessions].filter(s => s.panel && !s.destroyed
3303
+ && (s.inTurn || s.pendingTurns || s.pendingPerms.size || s._hasPendingWork() || s._authChanging)).length;
3304
+ }
3305
+
3219
3306
  export function shutdownAllBridges() {
3220
3307
  for (const session of [..._allSessions]) {
3221
3308
  try { session.destroy(); } catch {}