claude-mux 0.7.0

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 (636) hide show
  1. package/.beads/README.md +81 -0
  2. package/.beads/config.yaml +62 -0
  3. package/.beads/interactions.jsonl +0 -0
  4. package/.beads/issues.jsonl +5 -0
  5. package/.beads/metadata.json +4 -0
  6. package/.eslintrc.json +25 -0
  7. package/.gitattributes +3 -0
  8. package/.prettierrc +7 -0
  9. package/CLAUDE.md +123 -0
  10. package/LICENSE +21 -0
  11. package/PLAN-beads-integration.md +250 -0
  12. package/README.md +366 -0
  13. package/dist/app.d.ts +2 -0
  14. package/dist/app.d.ts.map +1 -0
  15. package/dist/app.js +147 -0
  16. package/dist/app.js.map +1 -0
  17. package/dist/cli.d.ts +3 -0
  18. package/dist/cli.d.ts.map +1 -0
  19. package/dist/cli.js +65 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/commands/index.d.ts +5 -0
  22. package/dist/commands/index.d.ts.map +1 -0
  23. package/dist/commands/index.js +5 -0
  24. package/dist/commands/index.js.map +1 -0
  25. package/dist/commands/serve.d.ts +8 -0
  26. package/dist/commands/serve.d.ts.map +1 -0
  27. package/dist/commands/serve.js +59 -0
  28. package/dist/commands/serve.js.map +1 -0
  29. package/dist/commands/setup.d.ts +4 -0
  30. package/dist/commands/setup.d.ts.map +1 -0
  31. package/dist/commands/setup.js +12 -0
  32. package/dist/commands/setup.js.map +1 -0
  33. package/dist/commands/tui.d.ts +9 -0
  34. package/dist/commands/tui.d.ts.map +1 -0
  35. package/dist/commands/tui.js +234 -0
  36. package/dist/commands/tui.js.map +1 -0
  37. package/dist/commands/uninstall.d.ts +4 -0
  38. package/dist/commands/uninstall.d.ts.map +1 -0
  39. package/dist/commands/uninstall.js +12 -0
  40. package/dist/commands/uninstall.js.map +1 -0
  41. package/dist/components/Header.d.ts +7 -0
  42. package/dist/components/Header.d.ts.map +1 -0
  43. package/dist/components/Header.js +14 -0
  44. package/dist/components/Header.js.map +1 -0
  45. package/dist/components/HelpDialog.d.ts +6 -0
  46. package/dist/components/HelpDialog.d.ts.map +1 -0
  47. package/dist/components/HelpDialog.js +14 -0
  48. package/dist/components/HelpDialog.js.map +1 -0
  49. package/dist/components/SessionEntry.d.ts +17 -0
  50. package/dist/components/SessionEntry.d.ts.map +1 -0
  51. package/dist/components/SessionEntry.js +102 -0
  52. package/dist/components/SessionEntry.js.map +1 -0
  53. package/dist/components/SessionList.d.ts +14 -0
  54. package/dist/components/SessionList.d.ts.map +1 -0
  55. package/dist/components/SessionList.js +57 -0
  56. package/dist/components/SessionList.js.map +1 -0
  57. package/dist/components/StatusBar.d.ts +6 -0
  58. package/dist/components/StatusBar.d.ts.map +1 -0
  59. package/dist/components/StatusBar.js +7 -0
  60. package/dist/components/StatusBar.js.map +1 -0
  61. package/dist/components/index.d.ts +6 -0
  62. package/dist/components/index.d.ts.map +1 -0
  63. package/dist/components/index.js +6 -0
  64. package/dist/components/index.js.map +1 -0
  65. package/dist/db/index.d.ts +2 -0
  66. package/dist/db/index.d.ts.map +1 -0
  67. package/dist/db/index.js +3 -0
  68. package/dist/db/index.js.map +1 -0
  69. package/dist/db/schema.d.ts +4 -0
  70. package/dist/db/schema.d.ts.map +1 -0
  71. package/dist/db/schema.js +23 -0
  72. package/dist/db/schema.js.map +1 -0
  73. package/dist/db/sessions-json.d.ts +85 -0
  74. package/dist/db/sessions-json.d.ts.map +1 -0
  75. package/dist/db/sessions-json.js +242 -0
  76. package/dist/db/sessions-json.js.map +1 -0
  77. package/dist/db/sessions.d.ts +38 -0
  78. package/dist/db/sessions.d.ts.map +1 -0
  79. package/dist/db/sessions.js +87 -0
  80. package/dist/db/sessions.js.map +1 -0
  81. package/dist/hooks/claude-mux-hook.d.ts +15 -0
  82. package/dist/hooks/claude-mux-hook.d.ts.map +1 -0
  83. package/dist/hooks/claude-mux-hook.js +396 -0
  84. package/dist/hooks/claude-mux-hook.js.map +1 -0
  85. package/dist/hooks/claude-watch-hook.d.ts +15 -0
  86. package/dist/hooks/claude-watch-hook.d.ts.map +1 -0
  87. package/dist/hooks/claude-watch-hook.js +396 -0
  88. package/dist/hooks/claude-watch-hook.js.map +1 -0
  89. package/dist/server/custom-server.d.ts +13 -0
  90. package/dist/server/custom-server.d.ts.map +1 -0
  91. package/dist/server/custom-server.js +63 -0
  92. package/dist/server/custom-server.js.map +1 -0
  93. package/dist/server/index.d.ts +9 -0
  94. package/dist/server/index.d.ts.map +1 -0
  95. package/dist/server/index.js +1143 -0
  96. package/dist/server/index.js.map +1 -0
  97. package/dist/server/middleware/cors.d.ts +2 -0
  98. package/dist/server/middleware/cors.d.ts.map +1 -0
  99. package/dist/server/middleware/cors.js +11 -0
  100. package/dist/server/middleware/cors.js.map +1 -0
  101. package/dist/server/routes/sessions.d.ts +3 -0
  102. package/dist/server/routes/sessions.d.ts.map +1 -0
  103. package/dist/server/routes/sessions.js +78 -0
  104. package/dist/server/routes/sessions.js.map +1 -0
  105. package/dist/server/routes/stream.d.ts +3 -0
  106. package/dist/server/routes/stream.d.ts.map +1 -0
  107. package/dist/server/routes/stream.js +45 -0
  108. package/dist/server/routes/stream.js.map +1 -0
  109. package/dist/server/types.d.ts +28 -0
  110. package/dist/server/types.d.ts.map +1 -0
  111. package/dist/server/types.js +2 -0
  112. package/dist/server/types.js.map +1 -0
  113. package/dist/server/watcher.d.ts +22 -0
  114. package/dist/server/watcher.d.ts.map +1 -0
  115. package/dist/server/watcher.js +119 -0
  116. package/dist/server/watcher.js.map +1 -0
  117. package/dist/server/websocket.d.ts +51 -0
  118. package/dist/server/websocket.d.ts.map +1 -0
  119. package/dist/server/websocket.js +156 -0
  120. package/dist/server/websocket.js.map +1 -0
  121. package/dist/server/ws-handlers.d.ts +174 -0
  122. package/dist/server/ws-handlers.d.ts.map +1 -0
  123. package/dist/server/ws-handlers.js +695 -0
  124. package/dist/server/ws-handlers.js.map +1 -0
  125. package/dist/setup/hooks.d.ts +44 -0
  126. package/dist/setup/hooks.d.ts.map +1 -0
  127. package/dist/setup/hooks.js +267 -0
  128. package/dist/setup/hooks.js.map +1 -0
  129. package/dist/setup/index.d.ts +3 -0
  130. package/dist/setup/index.d.ts.map +1 -0
  131. package/dist/setup/index.js +3 -0
  132. package/dist/setup/index.js.map +1 -0
  133. package/dist/setup/wizard.d.ts +4 -0
  134. package/dist/setup/wizard.d.ts.map +1 -0
  135. package/dist/setup/wizard.js +72 -0
  136. package/dist/setup/wizard.js.map +1 -0
  137. package/dist/tmux/detect.d.ts +25 -0
  138. package/dist/tmux/detect.d.ts.map +1 -0
  139. package/dist/tmux/detect.js +71 -0
  140. package/dist/tmux/detect.js.map +1 -0
  141. package/dist/tmux/navigate.d.ts +13 -0
  142. package/dist/tmux/navigate.d.ts.map +1 -0
  143. package/dist/tmux/navigate.js +41 -0
  144. package/dist/tmux/navigate.js.map +1 -0
  145. package/dist/tmux/pane.d.ts +57 -0
  146. package/dist/tmux/pane.d.ts.map +1 -0
  147. package/dist/tmux/pane.js +156 -0
  148. package/dist/tmux/pane.js.map +1 -0
  149. package/dist/tmux/resize.d.ts +10 -0
  150. package/dist/tmux/resize.d.ts.map +1 -0
  151. package/dist/tmux/resize.js +25 -0
  152. package/dist/tmux/resize.js.map +1 -0
  153. package/dist/utils/paths.d.ts +7 -0
  154. package/dist/utils/paths.d.ts.map +1 -0
  155. package/dist/utils/paths.js +9 -0
  156. package/dist/utils/paths.js.map +1 -0
  157. package/dist/utils/pid.d.ts +5 -0
  158. package/dist/utils/pid.d.ts.map +1 -0
  159. package/dist/utils/pid.js +14 -0
  160. package/dist/utils/pid.js.map +1 -0
  161. package/dist/utils/version.d.ts +6 -0
  162. package/dist/utils/version.d.ts.map +1 -0
  163. package/dist/utils/version.js +6 -0
  164. package/dist/utils/version.js.map +1 -0
  165. package/dist/web/client/_app/immutable/assets/0.WptSHSUl.css +1 -0
  166. package/dist/web/client/_app/immutable/assets/2.s6Kx4oz1.css +1 -0
  167. package/dist/web/client/_app/immutable/assets/4.DoNWy7tW.css +1 -0
  168. package/dist/web/client/_app/immutable/assets/AllSessionsPanel.CGHY3HLy.css +1 -0
  169. package/dist/web/client/_app/immutable/chunks/-3mUPuLP.js +1 -0
  170. package/dist/web/client/_app/immutable/chunks/B5U4_V3d.js +1 -0
  171. package/dist/web/client/_app/immutable/chunks/BHwiZXRv.js +1 -0
  172. package/dist/web/client/_app/immutable/chunks/C9P-coqM.js +1 -0
  173. package/dist/web/client/_app/immutable/chunks/Cegv0r8x.js +1 -0
  174. package/dist/web/client/_app/immutable/chunks/DU91Ml7U.js +3 -0
  175. package/dist/web/client/_app/immutable/chunks/DmdO6ygw.js +1 -0
  176. package/dist/web/client/_app/immutable/chunks/HKNo9LID.js +5 -0
  177. package/dist/web/client/_app/immutable/chunks/U4ip-C0d.js +2 -0
  178. package/dist/web/client/_app/immutable/chunks/cgUjKIhX.js +2 -0
  179. package/dist/web/client/_app/immutable/entry/app.CGIBnoln.js +2 -0
  180. package/dist/web/client/_app/immutable/entry/start.CJk8zB1j.js +1 -0
  181. package/dist/web/client/_app/immutable/nodes/0.CqlJ9a31.js +1 -0
  182. package/dist/web/client/_app/immutable/nodes/1.BQUZh2-w.js +1 -0
  183. package/dist/web/client/_app/immutable/nodes/2.CCV1YdgF.js +1 -0
  184. package/dist/web/client/_app/immutable/nodes/3.D9tDCdq8.js +1 -0
  185. package/dist/web/client/_app/immutable/nodes/4.BqPyNkFA.js +6 -0
  186. package/dist/web/client/_app/version.json +1 -0
  187. package/dist/web/client/robots.txt +3 -0
  188. package/dist/web/env.js +32 -0
  189. package/dist/web/handler.js +744 -0
  190. package/dist/web/index.js +49 -0
  191. package/dist/web/server/chunks/0-BHWsmCJv.js +23 -0
  192. package/dist/web/server/chunks/0-BHWsmCJv.js.map +1 -0
  193. package/dist/web/server/chunks/1-YRx6A8Tm.js +17 -0
  194. package/dist/web/server/chunks/1-YRx6A8Tm.js.map +1 -0
  195. package/dist/web/server/chunks/2-eC6JuGAo.js +22 -0
  196. package/dist/web/server/chunks/2-eC6JuGAo.js.map +1 -0
  197. package/dist/web/server/chunks/3-Bk-wV20p.js +32 -0
  198. package/dist/web/server/chunks/3-Bk-wV20p.js.map +1 -0
  199. package/dist/web/server/chunks/4-nteBgDrW.js +21 -0
  200. package/dist/web/server/chunks/4-nteBgDrW.js.map +1 -0
  201. package/dist/web/server/chunks/AllSessionsPanel.svelte_svelte_type_style_lang-Bt4B0-oi.js +35 -0
  202. package/dist/web/server/chunks/AllSessionsPanel.svelte_svelte_type_style_lang-Bt4B0-oi.js.map +1 -0
  203. package/dist/web/server/chunks/_layout.svelte-BIF9eZCY.js +453 -0
  204. package/dist/web/server/chunks/_layout.svelte-BIF9eZCY.js.map +1 -0
  205. package/dist/web/server/chunks/_page.svelte-Be6iabRn.js +34 -0
  206. package/dist/web/server/chunks/_page.svelte-Be6iabRn.js.map +1 -0
  207. package/dist/web/server/chunks/_page.svelte-C_fGJVSE.js +576 -0
  208. package/dist/web/server/chunks/_page.svelte-C_fGJVSE.js.map +1 -0
  209. package/dist/web/server/chunks/_page.svelte-CnfJk6cu.js +2722 -0
  210. package/dist/web/server/chunks/_page.svelte-CnfJk6cu.js.map +1 -0
  211. package/dist/web/server/chunks/_server.ts-3WAmKvn2.js +34 -0
  212. package/dist/web/server/chunks/_server.ts-3WAmKvn2.js.map +1 -0
  213. package/dist/web/server/chunks/_server.ts-BAWJCSFb.js +29 -0
  214. package/dist/web/server/chunks/_server.ts-BAWJCSFb.js.map +1 -0
  215. package/dist/web/server/chunks/_server.ts-BPpMOZCm.js +24 -0
  216. package/dist/web/server/chunks/_server.ts-BPpMOZCm.js.map +1 -0
  217. package/dist/web/server/chunks/_server.ts-BUKgRb6U.js +13 -0
  218. package/dist/web/server/chunks/_server.ts-BUKgRb6U.js.map +1 -0
  219. package/dist/web/server/chunks/_server.ts-BVHUS8fm.js +41 -0
  220. package/dist/web/server/chunks/_server.ts-BVHUS8fm.js.map +1 -0
  221. package/dist/web/server/chunks/_server.ts-BrF3od0O.js +45 -0
  222. package/dist/web/server/chunks/_server.ts-BrF3od0O.js.map +1 -0
  223. package/dist/web/server/chunks/_server.ts-C4oPmOJR.js +38 -0
  224. package/dist/web/server/chunks/_server.ts-C4oPmOJR.js.map +1 -0
  225. package/dist/web/server/chunks/_server.ts-CDAUUmG_.js +21 -0
  226. package/dist/web/server/chunks/_server.ts-CDAUUmG_.js.map +1 -0
  227. package/dist/web/server/chunks/_server.ts-CR0uWvpz.js +24 -0
  228. package/dist/web/server/chunks/_server.ts-CR0uWvpz.js.map +1 -0
  229. package/dist/web/server/chunks/_server.ts-CSqdCNGi.js +21 -0
  230. package/dist/web/server/chunks/_server.ts-CSqdCNGi.js.map +1 -0
  231. package/dist/web/server/chunks/_server.ts-DI9fzUo9.js +52 -0
  232. package/dist/web/server/chunks/_server.ts-DI9fzUo9.js.map +1 -0
  233. package/dist/web/server/chunks/_server.ts-DNjJTClI.js +46 -0
  234. package/dist/web/server/chunks/_server.ts-DNjJTClI.js.map +1 -0
  235. package/dist/web/server/chunks/_server.ts-DdbOAkOj.js +22 -0
  236. package/dist/web/server/chunks/_server.ts-DdbOAkOj.js.map +1 -0
  237. package/dist/web/server/chunks/_server.ts-Df2isGQc.js +41 -0
  238. package/dist/web/server/chunks/_server.ts-Df2isGQc.js.map +1 -0
  239. package/dist/web/server/chunks/_server.ts-Vpw25_-3.js +12 -0
  240. package/dist/web/server/chunks/_server.ts-Vpw25_-3.js.map +1 -0
  241. package/dist/web/server/chunks/_server.ts-WldRpSRi.js +26 -0
  242. package/dist/web/server/chunks/_server.ts-WldRpSRi.js.map +1 -0
  243. package/dist/web/server/chunks/alert-dialog-description-DDA6u-nS.js +2890 -0
  244. package/dist/web/server/chunks/alert-dialog-description-DDA6u-nS.js.map +1 -0
  245. package/dist/web/server/chunks/auth-DrUf-v4J.js +51 -0
  246. package/dist/web/server/chunks/auth-DrUf-v4J.js.map +1 -0
  247. package/dist/web/server/chunks/button-D6xS9rHt.js +2335 -0
  248. package/dist/web/server/chunks/button-D6xS9rHt.js.map +1 -0
  249. package/dist/web/server/chunks/chunk-EKzHsMy_.js +42 -0
  250. package/dist/web/server/chunks/chunks-DmdqVYC7.js +58 -0
  251. package/dist/web/server/chunks/chunks-DmdqVYC7.js.map +1 -0
  252. package/dist/web/server/chunks/client-CUrSQijh.js +18 -0
  253. package/dist/web/server/chunks/client-CUrSQijh.js.map +1 -0
  254. package/dist/web/server/chunks/clsx-FzI4_gi0.js +332 -0
  255. package/dist/web/server/chunks/clsx-FzI4_gi0.js.map +1 -0
  256. package/dist/web/server/chunks/error.svelte-D-c-9pTE.js +27 -0
  257. package/dist/web/server/chunks/error.svelte-D-c-9pTE.js.map +1 -0
  258. package/dist/web/server/chunks/events-BDBlYddw.js +89 -0
  259. package/dist/web/server/chunks/events-BDBlYddw.js.map +1 -0
  260. package/dist/web/server/chunks/exports-CCurQ-Tl.js +131 -0
  261. package/dist/web/server/chunks/exports-CCurQ-Tl.js.map +1 -0
  262. package/dist/web/server/chunks/hooks.server-BK1bopsh.js +86 -0
  263. package/dist/web/server/chunks/hooks.server-BK1bopsh.js.map +1 -0
  264. package/dist/web/server/chunks/index2-BQnysSj-.js +2588 -0
  265. package/dist/web/server/chunks/index2-BQnysSj-.js.map +1 -0
  266. package/dist/web/server/chunks/internal-DLENj6YI.js +61 -0
  267. package/dist/web/server/chunks/internal-DLENj6YI.js.map +1 -0
  268. package/dist/web/server/chunks/pane-Dg3pKvsm.js +94 -0
  269. package/dist/web/server/chunks/pane-Dg3pKvsm.js.map +1 -0
  270. package/dist/web/server/chunks/sessions-json-DgfkCLO7.js +107 -0
  271. package/dist/web/server/chunks/sessions-json-DgfkCLO7.js.map +1 -0
  272. package/dist/web/server/chunks/sessions.svelte-Ds82MvkB.js +178 -0
  273. package/dist/web/server/chunks/sessions.svelte-Ds82MvkB.js.map +1 -0
  274. package/dist/web/server/chunks/state.svelte-xeAZvWZ6.js +7 -0
  275. package/dist/web/server/chunks/state.svelte-xeAZvWZ6.js.map +1 -0
  276. package/dist/web/server/chunks/ws-handlers-B4r5eSP2.js +733 -0
  277. package/dist/web/server/chunks/ws-handlers-B4r5eSP2.js.map +1 -0
  278. package/dist/web/server/index.js +4907 -0
  279. package/dist/web/server/index.js.map +1 -0
  280. package/dist/web/server/manifest.js +233 -0
  281. package/dist/web/server/manifest.js.map +1 -0
  282. package/docs/images/desktop-dashboard.png +0 -0
  283. package/docs/images/desktop-session.png +0 -0
  284. package/docs/images/mobile-dashboard.png +0 -0
  285. package/docs/images/mobile-session.png +0 -0
  286. package/docs/release-checklist.md +228 -0
  287. package/docs/removing-hooks.md +135 -0
  288. package/docs/state-transitions.md +109 -0
  289. package/package.json +71 -0
  290. package/src/app.tsx +188 -0
  291. package/src/cli.ts +83 -0
  292. package/src/commands/index.ts +4 -0
  293. package/src/commands/serve.ts +75 -0
  294. package/src/commands/setup.ts +13 -0
  295. package/src/commands/tui.ts +255 -0
  296. package/src/commands/uninstall.ts +13 -0
  297. package/src/components/Header.tsx +32 -0
  298. package/src/components/HelpDialog.tsx +45 -0
  299. package/src/components/SessionEntry.tsx +202 -0
  300. package/src/components/SessionList.tsx +98 -0
  301. package/src/components/StatusBar.tsx +26 -0
  302. package/src/components/index.ts +5 -0
  303. package/src/db/index.ts +20 -0
  304. package/src/db/sessions-json.ts +314 -0
  305. package/src/hooks/claude-mux-hook.ts +498 -0
  306. package/src/server/watcher.ts +128 -0
  307. package/src/server/ws-handlers.ts +922 -0
  308. package/src/setup/hooks.ts +333 -0
  309. package/src/setup/index.ts +2 -0
  310. package/src/setup/wizard.ts +81 -0
  311. package/src/tmux/detect.ts +87 -0
  312. package/src/tmux/navigate.ts +42 -0
  313. package/src/tmux/pane.ts +167 -0
  314. package/src/tmux/resize.ts +28 -0
  315. package/src/utils/paths.ts +11 -0
  316. package/src/utils/pid.ts +12 -0
  317. package/src/utils/version.ts +5 -0
  318. package/tests/components/Header.test.tsx +42 -0
  319. package/tests/components/SessionEntry-extended.test.tsx +165 -0
  320. package/tests/components/SessionEntry.test.tsx +138 -0
  321. package/tests/components/SessionList.test.tsx +110 -0
  322. package/tests/components/StatusBar.test.tsx +31 -0
  323. package/tests/db/index.test.ts +78 -0
  324. package/tests/db/sessions.test.ts +230 -0
  325. package/tests/server/integration.test.ts +319 -0
  326. package/tests/server/sessions.test.ts +114 -0
  327. package/tests/setup/hooks-integration.test.ts +148 -0
  328. package/tests/setup/hooks.test.ts +123 -0
  329. package/tests/tmux/detect.test.ts +54 -0
  330. package/tests/tmux/navigate.test.ts +30 -0
  331. package/tests/utils/pid.test.ts +17 -0
  332. package/tsconfig.cli.json +9 -0
  333. package/tsconfig.json +22 -0
  334. package/vitest.config.ts +29 -0
  335. package/web/.svelte-kit/adapter-bun/.vite/manifest.json +408 -0
  336. package/web/.svelte-kit/adapter-bun/_app/immutable/assets/AllSessionsPanel.BKhqOrbV.css +1 -0
  337. package/web/.svelte-kit/adapter-bun/_app/immutable/assets/_layout.WptSHSUl.css +1 -0
  338. package/web/.svelte-kit/adapter-bun/_app/immutable/assets/_page.DldLgTc-.css +1 -0
  339. package/web/.svelte-kit/adapter-bun/_app/immutable/assets/_page.DoNWy7tW.css +1 -0
  340. package/web/.svelte-kit/adapter-bun/chunks/AllSessionsPanel.svelte_svelte_type_style_lang.js +49 -0
  341. package/web/.svelte-kit/adapter-bun/chunks/alert-dialog-description.js +2670 -0
  342. package/web/.svelte-kit/adapter-bun/chunks/auth.js +59 -0
  343. package/web/.svelte-kit/adapter-bun/chunks/button.js +82 -0
  344. package/web/.svelte-kit/adapter-bun/chunks/client.js +29 -0
  345. package/web/.svelte-kit/adapter-bun/chunks/context.js +133 -0
  346. package/web/.svelte-kit/adapter-bun/chunks/environment.js +34 -0
  347. package/web/.svelte-kit/adapter-bun/chunks/events.js +121 -0
  348. package/web/.svelte-kit/adapter-bun/chunks/exports.js +174 -0
  349. package/web/.svelte-kit/adapter-bun/chunks/false.js +4 -0
  350. package/web/.svelte-kit/adapter-bun/chunks/index.js +59 -0
  351. package/web/.svelte-kit/adapter-bun/chunks/index2.js +2828 -0
  352. package/web/.svelte-kit/adapter-bun/chunks/internal.js +920 -0
  353. package/web/.svelte-kit/adapter-bun/chunks/pane.js +82 -0
  354. package/web/.svelte-kit/adapter-bun/chunks/sessions-json.js +124 -0
  355. package/web/.svelte-kit/adapter-bun/chunks/sessions.svelte.js +229 -0
  356. package/web/.svelte-kit/adapter-bun/chunks/shared.js +542 -0
  357. package/web/.svelte-kit/adapter-bun/chunks/state.svelte.js +16 -0
  358. package/web/.svelte-kit/adapter-bun/chunks/utils.js +43 -0
  359. package/web/.svelte-kit/adapter-bun/chunks/ws-handlers.js +782 -0
  360. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/auth/login/_server.ts.js +22 -0
  361. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/auth/logout/_server.ts.js +9 -0
  362. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/beads/_server.ts.js +22 -0
  363. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/browse/_server.ts.js +50 -0
  364. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/chrome/_server.ts.js +30 -0
  365. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/files/image/_server.ts.js +53 -0
  366. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/health/_server.ts.js +7 -0
  367. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/projects/new-session/_server.ts.js +44 -0
  368. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/sessions/_id_/_server.ts.js +20 -0
  369. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/sessions/_id_/kill/_server.ts.js +36 -0
  370. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/sessions/_id_/restart/_server.ts.js +40 -0
  371. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/sessions/_id_/screenshots/_server.ts.js +28 -0
  372. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/sessions/_id_/send/_server.ts.js +29 -0
  373. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/sessions/_server.ts.js +49 -0
  374. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/sessions/_target_/output/_server.ts.js +14 -0
  375. package/web/.svelte-kit/adapter-bun/entries/endpoints/api/tmux/panes/_server.ts.js +21 -0
  376. package/web/.svelte-kit/adapter-bun/entries/fallbacks/error.svelte.js +27 -0
  377. package/web/.svelte-kit/adapter-bun/entries/hooks.server.js +105 -0
  378. package/web/.svelte-kit/adapter-bun/entries/pages/_layout.svelte.js +499 -0
  379. package/web/.svelte-kit/adapter-bun/entries/pages/_page.svelte.js +3057 -0
  380. package/web/.svelte-kit/adapter-bun/entries/pages/login/_page.server.ts.js +15 -0
  381. package/web/.svelte-kit/adapter-bun/entries/pages/login/_page.svelte.js +37 -0
  382. package/web/.svelte-kit/adapter-bun/entries/pages/session/_target_/_page.svelte.js +653 -0
  383. package/web/.svelte-kit/adapter-bun/index.js +3864 -0
  384. package/web/.svelte-kit/adapter-bun/internal.js +13 -0
  385. package/web/.svelte-kit/adapter-bun/manifest-full.js +167 -0
  386. package/web/.svelte-kit/adapter-bun/manifest.js +171 -0
  387. package/web/.svelte-kit/adapter-bun/nodes/0.js +8 -0
  388. package/web/.svelte-kit/adapter-bun/nodes/1.js +8 -0
  389. package/web/.svelte-kit/adapter-bun/nodes/2.js +8 -0
  390. package/web/.svelte-kit/adapter-bun/nodes/3.js +10 -0
  391. package/web/.svelte-kit/adapter-bun/nodes/4.js +8 -0
  392. package/web/.svelte-kit/adapter-bun/remote-entry.js +541 -0
  393. package/web/.svelte-kit/adapter-node/.vite/manifest.json +223 -0
  394. package/web/.svelte-kit/adapter-node/_app/immutable/assets/_layout.4NiX29PU.css +1 -0
  395. package/web/.svelte-kit/adapter-node/_app/immutable/assets/_page.BEMzYUGV.css +1 -0
  396. package/web/.svelte-kit/adapter-node/_app/immutable/assets/_page.DOJn7TG7.css +1 -0
  397. package/web/.svelte-kit/adapter-node/chunks/context.js +121 -0
  398. package/web/.svelte-kit/adapter-node/chunks/environment.js +34 -0
  399. package/web/.svelte-kit/adapter-node/chunks/exports.js +174 -0
  400. package/web/.svelte-kit/adapter-node/chunks/false.js +4 -0
  401. package/web/.svelte-kit/adapter-node/chunks/index.js +59 -0
  402. package/web/.svelte-kit/adapter-node/chunks/index2.js +2710 -0
  403. package/web/.svelte-kit/adapter-node/chunks/internal.js +1005 -0
  404. package/web/.svelte-kit/adapter-node/chunks/sessions-json.js +109 -0
  405. package/web/.svelte-kit/adapter-node/chunks/sessions.svelte.js +67 -0
  406. package/web/.svelte-kit/adapter-node/chunks/shared.js +542 -0
  407. package/web/.svelte-kit/adapter-node/chunks/state.svelte.js +16 -0
  408. package/web/.svelte-kit/adapter-node/chunks/utils.js +43 -0
  409. package/web/.svelte-kit/adapter-node/entries/endpoints/api/browse/_server.ts.js +50 -0
  410. package/web/.svelte-kit/adapter-node/entries/endpoints/api/health/_server.ts.js +7 -0
  411. package/web/.svelte-kit/adapter-node/entries/endpoints/api/projects/new-session/_server.ts.js +44 -0
  412. package/web/.svelte-kit/adapter-node/entries/endpoints/api/sessions/_id_/_server.ts.js +20 -0
  413. package/web/.svelte-kit/adapter-node/entries/endpoints/api/sessions/_id_/kill/_server.ts.js +30 -0
  414. package/web/.svelte-kit/adapter-node/entries/endpoints/api/sessions/_id_/send/_server.ts.js +22 -0
  415. package/web/.svelte-kit/adapter-node/entries/endpoints/api/sessions/_server.ts.js +126 -0
  416. package/web/.svelte-kit/adapter-node/entries/endpoints/api/sessions/_target_/output/_server.ts.js +14 -0
  417. package/web/.svelte-kit/adapter-node/entries/fallbacks/error.svelte.js +44 -0
  418. package/web/.svelte-kit/adapter-node/entries/pages/_layout.svelte.js +12 -0
  419. package/web/.svelte-kit/adapter-node/entries/pages/_page.svelte.js +87 -0
  420. package/web/.svelte-kit/adapter-node/entries/pages/session/_target_/_page.svelte.js +76 -0
  421. package/web/.svelte-kit/adapter-node/index.js +3864 -0
  422. package/web/.svelte-kit/adapter-node/internal.js +13 -0
  423. package/web/.svelte-kit/adapter-node/manifest-full.js +103 -0
  424. package/web/.svelte-kit/adapter-node/manifest.js +107 -0
  425. package/web/.svelte-kit/adapter-node/nodes/0.js +8 -0
  426. package/web/.svelte-kit/adapter-node/nodes/1.js +8 -0
  427. package/web/.svelte-kit/adapter-node/nodes/2.js +8 -0
  428. package/web/.svelte-kit/adapter-node/nodes/3.js +8 -0
  429. package/web/.svelte-kit/adapter-node/remote-entry.js +541 -0
  430. package/web/.svelte-kit/ambient.d.ts +187 -0
  431. package/web/.svelte-kit/generated/client/app.js +33 -0
  432. package/web/.svelte-kit/generated/client/matchers.js +1 -0
  433. package/web/.svelte-kit/generated/client/nodes/0.js +1 -0
  434. package/web/.svelte-kit/generated/client/nodes/1.js +1 -0
  435. package/web/.svelte-kit/generated/client/nodes/2.js +1 -0
  436. package/web/.svelte-kit/generated/client/nodes/3.js +1 -0
  437. package/web/.svelte-kit/generated/client/nodes/4.js +1 -0
  438. package/web/.svelte-kit/generated/client-optimized/app.js +33 -0
  439. package/web/.svelte-kit/generated/client-optimized/matchers.js +1 -0
  440. package/web/.svelte-kit/generated/client-optimized/nodes/0.js +1 -0
  441. package/web/.svelte-kit/generated/client-optimized/nodes/1.js +1 -0
  442. package/web/.svelte-kit/generated/client-optimized/nodes/2.js +1 -0
  443. package/web/.svelte-kit/generated/client-optimized/nodes/3.js +1 -0
  444. package/web/.svelte-kit/generated/client-optimized/nodes/4.js +1 -0
  445. package/web/.svelte-kit/generated/root.js +3 -0
  446. package/web/.svelte-kit/generated/root.svelte +68 -0
  447. package/web/.svelte-kit/generated/server/internal.js +53 -0
  448. package/web/.svelte-kit/non-ambient.d.ts +73 -0
  449. package/web/.svelte-kit/output/client/.vite/manifest.json +203 -0
  450. package/web/.svelte-kit/output/client/_app/immutable/assets/0.WptSHSUl.css +1 -0
  451. package/web/.svelte-kit/output/client/_app/immutable/assets/2.s6Kx4oz1.css +1 -0
  452. package/web/.svelte-kit/output/client/_app/immutable/assets/4.DoNWy7tW.css +1 -0
  453. package/web/.svelte-kit/output/client/_app/immutable/assets/AllSessionsPanel.CGHY3HLy.css +1 -0
  454. package/web/.svelte-kit/output/client/_app/immutable/chunks/-3mUPuLP.js +1 -0
  455. package/web/.svelte-kit/output/client/_app/immutable/chunks/B5U4_V3d.js +1 -0
  456. package/web/.svelte-kit/output/client/_app/immutable/chunks/BHwiZXRv.js +1 -0
  457. package/web/.svelte-kit/output/client/_app/immutable/chunks/C9P-coqM.js +1 -0
  458. package/web/.svelte-kit/output/client/_app/immutable/chunks/Cegv0r8x.js +1 -0
  459. package/web/.svelte-kit/output/client/_app/immutable/chunks/DU91Ml7U.js +3 -0
  460. package/web/.svelte-kit/output/client/_app/immutable/chunks/DmdO6ygw.js +1 -0
  461. package/web/.svelte-kit/output/client/_app/immutable/chunks/HKNo9LID.js +5 -0
  462. package/web/.svelte-kit/output/client/_app/immutable/chunks/U4ip-C0d.js +2 -0
  463. package/web/.svelte-kit/output/client/_app/immutable/chunks/cgUjKIhX.js +2 -0
  464. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CGIBnoln.js +2 -0
  465. package/web/.svelte-kit/output/client/_app/immutable/entry/start.CJk8zB1j.js +1 -0
  466. package/web/.svelte-kit/output/client/_app/immutable/nodes/0.CqlJ9a31.js +1 -0
  467. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.BQUZh2-w.js +1 -0
  468. package/web/.svelte-kit/output/client/_app/immutable/nodes/2.CCV1YdgF.js +1 -0
  469. package/web/.svelte-kit/output/client/_app/immutable/nodes/3.D9tDCdq8.js +1 -0
  470. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.BqPyNkFA.js +6 -0
  471. package/web/.svelte-kit/output/client/_app/version.json +1 -0
  472. package/web/.svelte-kit/output/client/robots.txt +3 -0
  473. package/web/.svelte-kit/output/server/.vite/manifest.json +408 -0
  474. package/web/.svelte-kit/output/server/_app/immutable/assets/AllSessionsPanel.BKhqOrbV.css +1 -0
  475. package/web/.svelte-kit/output/server/_app/immutable/assets/_layout.WptSHSUl.css +1 -0
  476. package/web/.svelte-kit/output/server/_app/immutable/assets/_page.DldLgTc-.css +1 -0
  477. package/web/.svelte-kit/output/server/_app/immutable/assets/_page.DoNWy7tW.css +1 -0
  478. package/web/.svelte-kit/output/server/chunks/AllSessionsPanel.svelte_svelte_type_style_lang.js +49 -0
  479. package/web/.svelte-kit/output/server/chunks/alert-dialog-description.js +2670 -0
  480. package/web/.svelte-kit/output/server/chunks/auth.js +59 -0
  481. package/web/.svelte-kit/output/server/chunks/button.js +82 -0
  482. package/web/.svelte-kit/output/server/chunks/client.js +29 -0
  483. package/web/.svelte-kit/output/server/chunks/context.js +133 -0
  484. package/web/.svelte-kit/output/server/chunks/environment.js +34 -0
  485. package/web/.svelte-kit/output/server/chunks/events.js +121 -0
  486. package/web/.svelte-kit/output/server/chunks/exports.js +174 -0
  487. package/web/.svelte-kit/output/server/chunks/false.js +4 -0
  488. package/web/.svelte-kit/output/server/chunks/index.js +59 -0
  489. package/web/.svelte-kit/output/server/chunks/index2.js +2828 -0
  490. package/web/.svelte-kit/output/server/chunks/internal.js +920 -0
  491. package/web/.svelte-kit/output/server/chunks/pane.js +82 -0
  492. package/web/.svelte-kit/output/server/chunks/sessions-json.js +124 -0
  493. package/web/.svelte-kit/output/server/chunks/sessions.svelte.js +229 -0
  494. package/web/.svelte-kit/output/server/chunks/shared.js +542 -0
  495. package/web/.svelte-kit/output/server/chunks/state.svelte.js +16 -0
  496. package/web/.svelte-kit/output/server/chunks/utils.js +43 -0
  497. package/web/.svelte-kit/output/server/chunks/ws-handlers.js +782 -0
  498. package/web/.svelte-kit/output/server/entries/endpoints/api/auth/login/_server.ts.js +22 -0
  499. package/web/.svelte-kit/output/server/entries/endpoints/api/auth/logout/_server.ts.js +9 -0
  500. package/web/.svelte-kit/output/server/entries/endpoints/api/beads/_server.ts.js +22 -0
  501. package/web/.svelte-kit/output/server/entries/endpoints/api/browse/_server.ts.js +50 -0
  502. package/web/.svelte-kit/output/server/entries/endpoints/api/chrome/_server.ts.js +30 -0
  503. package/web/.svelte-kit/output/server/entries/endpoints/api/files/image/_server.ts.js +53 -0
  504. package/web/.svelte-kit/output/server/entries/endpoints/api/health/_server.ts.js +7 -0
  505. package/web/.svelte-kit/output/server/entries/endpoints/api/projects/new-session/_server.ts.js +44 -0
  506. package/web/.svelte-kit/output/server/entries/endpoints/api/sessions/_id_/_server.ts.js +20 -0
  507. package/web/.svelte-kit/output/server/entries/endpoints/api/sessions/_id_/kill/_server.ts.js +36 -0
  508. package/web/.svelte-kit/output/server/entries/endpoints/api/sessions/_id_/restart/_server.ts.js +40 -0
  509. package/web/.svelte-kit/output/server/entries/endpoints/api/sessions/_id_/screenshots/_server.ts.js +28 -0
  510. package/web/.svelte-kit/output/server/entries/endpoints/api/sessions/_id_/send/_server.ts.js +29 -0
  511. package/web/.svelte-kit/output/server/entries/endpoints/api/sessions/_server.ts.js +49 -0
  512. package/web/.svelte-kit/output/server/entries/endpoints/api/sessions/_target_/output/_server.ts.js +14 -0
  513. package/web/.svelte-kit/output/server/entries/endpoints/api/tmux/panes/_server.ts.js +21 -0
  514. package/web/.svelte-kit/output/server/entries/fallbacks/error.svelte.js +27 -0
  515. package/web/.svelte-kit/output/server/entries/hooks.server.js +105 -0
  516. package/web/.svelte-kit/output/server/entries/pages/_layout.svelte.js +499 -0
  517. package/web/.svelte-kit/output/server/entries/pages/_page.svelte.js +3057 -0
  518. package/web/.svelte-kit/output/server/entries/pages/login/_page.server.ts.js +15 -0
  519. package/web/.svelte-kit/output/server/entries/pages/login/_page.svelte.js +37 -0
  520. package/web/.svelte-kit/output/server/entries/pages/session/_target_/_page.svelte.js +653 -0
  521. package/web/.svelte-kit/output/server/index.js +3864 -0
  522. package/web/.svelte-kit/output/server/internal.js +13 -0
  523. package/web/.svelte-kit/output/server/manifest-full.js +167 -0
  524. package/web/.svelte-kit/output/server/manifest.js +167 -0
  525. package/web/.svelte-kit/output/server/nodes/0.js +8 -0
  526. package/web/.svelte-kit/output/server/nodes/1.js +8 -0
  527. package/web/.svelte-kit/output/server/nodes/2.js +8 -0
  528. package/web/.svelte-kit/output/server/nodes/3.js +10 -0
  529. package/web/.svelte-kit/output/server/nodes/4.js +8 -0
  530. package/web/.svelte-kit/output/server/remote-entry.js +541 -0
  531. package/web/.svelte-kit/tsconfig.json +58 -0
  532. package/web/.svelte-kit/types/route_meta_data.json +55 -0
  533. package/web/.svelte-kit/types/src/routes/$types.d.ts +24 -0
  534. package/web/.svelte-kit/types/src/routes/api/auth/login/$types.d.ts +10 -0
  535. package/web/.svelte-kit/types/src/routes/api/auth/logout/$types.d.ts +10 -0
  536. package/web/.svelte-kit/types/src/routes/api/beads/$types.d.ts +10 -0
  537. package/web/.svelte-kit/types/src/routes/api/browse/$types.d.ts +10 -0
  538. package/web/.svelte-kit/types/src/routes/api/chrome/$types.d.ts +10 -0
  539. package/web/.svelte-kit/types/src/routes/api/files/image/$types.d.ts +10 -0
  540. package/web/.svelte-kit/types/src/routes/api/health/$types.d.ts +10 -0
  541. package/web/.svelte-kit/types/src/routes/api/projects/new-session/$types.d.ts +10 -0
  542. package/web/.svelte-kit/types/src/routes/api/sessions/$types.d.ts +10 -0
  543. package/web/.svelte-kit/types/src/routes/api/sessions/[id]/$types.d.ts +11 -0
  544. package/web/.svelte-kit/types/src/routes/api/sessions/[id]/kill/$types.d.ts +11 -0
  545. package/web/.svelte-kit/types/src/routes/api/sessions/[id]/restart/$types.d.ts +11 -0
  546. package/web/.svelte-kit/types/src/routes/api/sessions/[id]/screenshots/$types.d.ts +11 -0
  547. package/web/.svelte-kit/types/src/routes/api/sessions/[id]/send/$types.d.ts +11 -0
  548. package/web/.svelte-kit/types/src/routes/api/sessions/[target]/output/$types.d.ts +11 -0
  549. package/web/.svelte-kit/types/src/routes/api/tmux/panes/$types.d.ts +10 -0
  550. package/web/.svelte-kit/types/src/routes/login/$types.d.ts +25 -0
  551. package/web/.svelte-kit/types/src/routes/login/proxy+page.server.ts +19 -0
  552. package/web/.svelte-kit/types/src/routes/session/[target]/$types.d.ts +19 -0
  553. package/web/README.md +42 -0
  554. package/web/components.json +16 -0
  555. package/web/package.json +35 -0
  556. package/web/src/app.css +128 -0
  557. package/web/src/app.d.ts +13 -0
  558. package/web/src/app.html +11 -0
  559. package/web/src/hooks.server.ts +156 -0
  560. package/web/src/lib/assets/favicon.svg +1 -0
  561. package/web/src/lib/components/AllSessionsPanel.svelte +789 -0
  562. package/web/src/lib/components/BeadsPanel.svelte +146 -0
  563. package/web/src/lib/components/IssueItem.svelte +287 -0
  564. package/web/src/lib/components/ScreenshotsPanel.svelte +336 -0
  565. package/web/src/lib/components/SessionsSidebar.svelte +312 -0
  566. package/web/src/lib/components/TerminalRenderer.svelte +189 -0
  567. package/web/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte +18 -0
  568. package/web/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +18 -0
  569. package/web/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte +29 -0
  570. package/web/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte +17 -0
  571. package/web/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte +20 -0
  572. package/web/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
  573. package/web/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte +20 -0
  574. package/web/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte +7 -0
  575. package/web/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte +17 -0
  576. package/web/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte +7 -0
  577. package/web/src/lib/components/ui/alert-dialog/alert-dialog.svelte +7 -0
  578. package/web/src/lib/components/ui/alert-dialog/index.ts +37 -0
  579. package/web/src/lib/components/ui/badge/badge.svelte +50 -0
  580. package/web/src/lib/components/ui/badge/index.ts +2 -0
  581. package/web/src/lib/components/ui/button/button.svelte +86 -0
  582. package/web/src/lib/components/ui/button/index.ts +17 -0
  583. package/web/src/lib/components/ui/checkbox/checkbox.svelte +36 -0
  584. package/web/src/lib/components/ui/checkbox/index.ts +6 -0
  585. package/web/src/lib/components/ui/dialog/dialog-close.svelte +7 -0
  586. package/web/src/lib/components/ui/dialog/dialog-content.svelte +45 -0
  587. package/web/src/lib/components/ui/dialog/dialog-description.svelte +17 -0
  588. package/web/src/lib/components/ui/dialog/dialog-footer.svelte +20 -0
  589. package/web/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
  590. package/web/src/lib/components/ui/dialog/dialog-overlay.svelte +20 -0
  591. package/web/src/lib/components/ui/dialog/dialog-portal.svelte +7 -0
  592. package/web/src/lib/components/ui/dialog/dialog-title.svelte +17 -0
  593. package/web/src/lib/components/ui/dialog/dialog-trigger.svelte +7 -0
  594. package/web/src/lib/components/ui/dialog/dialog.svelte +7 -0
  595. package/web/src/lib/components/ui/dialog/index.ts +34 -0
  596. package/web/src/lib/components/ui/scroll-area/index.ts +10 -0
  597. package/web/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte +31 -0
  598. package/web/src/lib/components/ui/scroll-area/scroll-area.svelte +43 -0
  599. package/web/src/lib/components/ui/textarea/index.ts +7 -0
  600. package/web/src/lib/components/ui/textarea/textarea.svelte +23 -0
  601. package/web/src/lib/index.ts +1 -0
  602. package/web/src/lib/server/auth.ts +90 -0
  603. package/web/src/lib/stores/beads.svelte.ts +163 -0
  604. package/web/src/lib/stores/input-injection.svelte.ts +39 -0
  605. package/web/src/lib/stores/preferences.svelte.ts +55 -0
  606. package/web/src/lib/stores/sessions.svelte.ts +108 -0
  607. package/web/src/lib/stores/terminal.svelte.ts +96 -0
  608. package/web/src/lib/stores/websocket-base.svelte.ts +209 -0
  609. package/web/src/lib/types/terminal.ts +31 -0
  610. package/web/src/lib/utils/terminal-parser.ts +239 -0
  611. package/web/src/lib/utils.ts +13 -0
  612. package/web/src/routes/+layout.svelte +450 -0
  613. package/web/src/routes/+page.svelte +19 -0
  614. package/web/src/routes/api/auth/login/+server.ts +34 -0
  615. package/web/src/routes/api/auth/logout/+server.ts +10 -0
  616. package/web/src/routes/api/beads/+server.ts +28 -0
  617. package/web/src/routes/api/browse/+server.ts +59 -0
  618. package/web/src/routes/api/chrome/+server.ts +35 -0
  619. package/web/src/routes/api/files/image/+server.ts +64 -0
  620. package/web/src/routes/api/health/+server.ts +6 -0
  621. package/web/src/routes/api/projects/new-session/+server.ts +50 -0
  622. package/web/src/routes/api/sessions/+server.ts +62 -0
  623. package/web/src/routes/api/sessions/[id]/+server.ts +19 -0
  624. package/web/src/routes/api/sessions/[id]/kill/+server.ts +46 -0
  625. package/web/src/routes/api/sessions/[id]/restart/+server.ts +59 -0
  626. package/web/src/routes/api/sessions/[id]/screenshots/+server.ts +32 -0
  627. package/web/src/routes/api/sessions/[id]/send/+server.ts +31 -0
  628. package/web/src/routes/api/sessions/[target]/output/+server.ts +13 -0
  629. package/web/src/routes/api/tmux/panes/+server.ts +32 -0
  630. package/web/src/routes/login/+page.server.ts +18 -0
  631. package/web/src/routes/login/+page.svelte +69 -0
  632. package/web/src/routes/session/[target]/+page.svelte +450 -0
  633. package/web/static/robots.txt +3 -0
  634. package/web/svelte.config.js +20 -0
  635. package/web/tsconfig.json +20 -0
  636. package/web/vite.config.ts +154 -0
@@ -0,0 +1,922 @@
1
+ /**
2
+ * Shared WebSocket handler logic for both dev (ws) and production (Bun) servers.
3
+ * Abstracts the WebSocket-specific APIs behind a simple interface.
4
+ *
5
+ * Features:
6
+ * - Max clients limit to prevent server overload
7
+ * - Structured error logging
8
+ * - Backpressure handling for slow clients
9
+ */
10
+
11
+ import { execFileSync } from 'child_process';
12
+ import { getAllSessions, updateSession, type Session } from '../db/index.js';
13
+ import { checkForInterruption, getPaneTitle } from '../tmux/pane.js';
14
+ import { resizeTmuxWindow } from '../tmux/resize.js';
15
+ import { sessionWatcher } from './watcher.js';
16
+
17
+ // ============================================================================
18
+ // Configuration
19
+ // ============================================================================
20
+
21
+ export interface WsConfig {
22
+ /** Max clients for sessions endpoint (default: 50) */
23
+ maxSessionsClients?: number;
24
+ /** Max clients per terminal target (default: 10) */
25
+ maxTerminalClientsPerTarget?: number;
26
+ /** Max total terminal clients across all targets (default: 100) */
27
+ maxTerminalClientsTotal?: number;
28
+ /** Max queued messages before dropping slow client (default: 100) */
29
+ maxQueuedMessages?: number;
30
+ /** Enable debug logging (default: false) */
31
+ debug?: boolean;
32
+ }
33
+
34
+ const DEFAULT_CONFIG: Required<WsConfig> = {
35
+ maxSessionsClients: 50,
36
+ maxTerminalClientsPerTarget: 10,
37
+ maxTerminalClientsTotal: 100,
38
+ maxQueuedMessages: 100,
39
+ debug: false
40
+ };
41
+
42
+ // ============================================================================
43
+ // Logging
44
+ // ============================================================================
45
+
46
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
47
+
48
+ interface LogEntry {
49
+ level: LogLevel;
50
+ component: string;
51
+ message: string;
52
+ data?: Record<string, unknown>;
53
+ }
54
+
55
+ function log(entry: LogEntry, config: Required<WsConfig>): void {
56
+ if (entry.level === 'debug' && !config.debug) return;
57
+
58
+ const prefix = `[ws:${entry.component}]`;
59
+ const msg = entry.data
60
+ ? `${prefix} ${entry.message} ${JSON.stringify(entry.data)}`
61
+ : `${prefix} ${entry.message}`;
62
+
63
+ switch (entry.level) {
64
+ case 'debug':
65
+ console.debug(msg);
66
+ break;
67
+ case 'info':
68
+ console.info(msg);
69
+ break;
70
+ case 'warn':
71
+ console.warn(msg);
72
+ break;
73
+ case 'error':
74
+ console.error(msg);
75
+ break;
76
+ }
77
+ }
78
+
79
+ // ============================================================================
80
+ // Generic WebSocket client interface
81
+ // ============================================================================
82
+
83
+ export interface WsClient {
84
+ send: (data: string) => void;
85
+ isOpen: () => boolean;
86
+ close: () => void;
87
+ /** Optional: Get buffered amount for backpressure detection */
88
+ getBufferedAmount?: () => number;
89
+ }
90
+
91
+ // ============================================================================
92
+ // Message types
93
+ // ============================================================================
94
+
95
+ export interface SessionsMessage {
96
+ type: 'sessions' | 'connected';
97
+ sessions: (Session & { pane_title: string | null })[];
98
+ count: number;
99
+ timestamp: number;
100
+ }
101
+
102
+ export interface TerminalMessage {
103
+ type: 'output';
104
+ output: string;
105
+ timestamp: number;
106
+ }
107
+
108
+ export interface ResizeMessage {
109
+ type: 'resize';
110
+ cols: number;
111
+ rows: number;
112
+ }
113
+
114
+ // ============================================================================
115
+ // Session Helpers
116
+ // ============================================================================
117
+
118
+ function syncSessionStates(): void {
119
+ const sessions = getAllSessions().filter((s) => s.tmux_target);
120
+ for (const session of sessions) {
121
+ if (!session.tmux_target) continue;
122
+ const update = checkForInterruption(session.tmux_target);
123
+ if (update && session.state !== 'idle') {
124
+ updateSession(session.id, update);
125
+ }
126
+ }
127
+ }
128
+
129
+ function deduplicateByTmuxTarget<T extends { tmux_target: string | null; last_update: number }>(
130
+ sessions: T[]
131
+ ): T[] {
132
+ const byTarget = new Map<string, T>();
133
+ const noTarget: T[] = [];
134
+ for (const session of sessions) {
135
+ if (!session.tmux_target) {
136
+ noTarget.push(session);
137
+ continue;
138
+ }
139
+ const existing = byTarget.get(session.tmux_target);
140
+ if (!existing || session.last_update > existing.last_update) {
141
+ byTarget.set(session.tmux_target, session);
142
+ }
143
+ }
144
+ return [...byTarget.values(), ...noTarget];
145
+ }
146
+
147
+ export function getEnrichedSessions(): (Session & { pane_title: string | null })[] {
148
+ syncSessionStates();
149
+ const sessions = getAllSessions();
150
+ const enrichedSessions = sessions.map((s) => ({
151
+ ...s,
152
+ pane_title: s.tmux_target ? getPaneTitle(s.tmux_target) : null
153
+ }));
154
+ return deduplicateByTmuxTarget(enrichedSessions);
155
+ }
156
+
157
+ // ============================================================================
158
+ // Terminal Helpers
159
+ // ============================================================================
160
+
161
+ export function capturePaneOutput(target: string): string | null {
162
+ try {
163
+ return execFileSync('tmux', ['capture-pane', '-t', target, '-p', '-S', '-100'], {
164
+ encoding: 'utf-8',
165
+ stdio: ['pipe', 'pipe', 'pipe'],
166
+ timeout: 2000
167
+ });
168
+ } catch {
169
+ return null;
170
+ }
171
+ }
172
+
173
+ export function resizePane(target: string, cols: number, rows: number): void {
174
+ resizeTmuxWindow(target, cols, rows);
175
+ }
176
+
177
+ // ============================================================================
178
+ // Sessions WebSocket Manager
179
+ // ============================================================================
180
+
181
+ export class SessionsWsManager {
182
+ private clients = new Set<WsClient>();
183
+ private unsubscribe: (() => void) | null = null;
184
+ private interruptCheckTimer: ReturnType<typeof setInterval> | null = null;
185
+ private lastHash = '';
186
+ private config: Required<WsConfig>;
187
+ private droppedClients = 0;
188
+
189
+ constructor(config?: WsConfig) {
190
+ this.config = { ...DEFAULT_CONFIG, ...config };
191
+ }
192
+
193
+ /** Get current stats */
194
+ getStats(): { clients: number; droppedClients: number } {
195
+ return {
196
+ clients: this.clients.size,
197
+ droppedClients: this.droppedClients
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Add a client. Returns false if max clients reached.
203
+ */
204
+ addClient(client: WsClient): boolean {
205
+ if (this.clients.size >= this.config.maxSessionsClients) {
206
+ log(
207
+ {
208
+ level: 'warn',
209
+ component: 'sessions',
210
+ message: 'Max clients reached, rejecting connection',
211
+ data: { current: this.clients.size, max: this.config.maxSessionsClients }
212
+ },
213
+ this.config
214
+ );
215
+ return false;
216
+ }
217
+
218
+ this.clients.add(client);
219
+ log(
220
+ {
221
+ level: 'debug',
222
+ component: 'sessions',
223
+ message: 'Client connected',
224
+ data: { total: this.clients.size }
225
+ },
226
+ this.config
227
+ );
228
+
229
+ if (this.clients.size === 1 && !this.unsubscribe) {
230
+ console.log('[ws:sessions] First client, subscribing to watcher');
231
+ this.unsubscribe = sessionWatcher.subscribe(() => this.broadcastIfChanged());
232
+ // Start interrupt check timer - runs independently of file changes
233
+ // to catch interruptions triggered by web UI (Escape key)
234
+ this.interruptCheckTimer = setInterval(() => {
235
+ syncSessionStates();
236
+ this.broadcastIfChanged();
237
+ }, 500);
238
+ } else {
239
+ console.log('[ws:sessions] addClient: clients=', this.clients.size, 'hasUnsubscribe=', !!this.unsubscribe);
240
+ }
241
+ this.sendToClient(client, this.createMessage('connected'));
242
+ return true;
243
+ }
244
+
245
+ removeClient(client: WsClient): void {
246
+ const had = this.clients.delete(client);
247
+ if (had) {
248
+ log(
249
+ {
250
+ level: 'debug',
251
+ component: 'sessions',
252
+ message: 'Client disconnected',
253
+ data: { total: this.clients.size }
254
+ },
255
+ this.config
256
+ );
257
+ }
258
+
259
+ if (this.clients.size === 0 && this.unsubscribe) {
260
+ this.unsubscribe();
261
+ this.unsubscribe = null;
262
+ this.lastHash = '';
263
+ // Stop interrupt check timer
264
+ if (this.interruptCheckTimer) {
265
+ clearInterval(this.interruptCheckTimer);
266
+ this.interruptCheckTimer = null;
267
+ }
268
+ }
269
+ }
270
+
271
+ private createMessage(type: 'sessions' | 'connected'): SessionsMessage {
272
+ const sessions = getEnrichedSessions();
273
+ return { type, sessions, count: sessions.length, timestamp: Date.now() };
274
+ }
275
+
276
+ private broadcastIfChanged(): void {
277
+ console.log('[ws:sessions] broadcastIfChanged called, clients:', this.clients.size);
278
+ const message = this.createMessage('sessions');
279
+ const hash = JSON.stringify(message.sessions);
280
+ if (hash === this.lastHash) {
281
+ console.log('[ws:sessions] Hash unchanged, skipping broadcast');
282
+ return;
283
+ }
284
+ console.log('[ws:sessions] Broadcasting to', this.clients.size, 'clients');
285
+ this.lastHash = hash;
286
+ const data = JSON.stringify(message);
287
+
288
+ for (const client of this.clients) {
289
+ if (!this.sendToClient(client, message, data)) {
290
+ // Client is slow/dead, remove it
291
+ this.clients.delete(client);
292
+ this.droppedClients++;
293
+ log(
294
+ {
295
+ level: 'warn',
296
+ component: 'sessions',
297
+ message: 'Dropped slow client',
298
+ data: { total: this.clients.size }
299
+ },
300
+ this.config
301
+ );
302
+ }
303
+ }
304
+ }
305
+
306
+ private sendToClient(client: WsClient, _message: SessionsMessage, data?: string): boolean {
307
+ try {
308
+ if (!client.isOpen()) return false;
309
+
310
+ // Backpressure check
311
+ if (client.getBufferedAmount) {
312
+ const buffered = client.getBufferedAmount();
313
+ // If buffer is building up, consider this a slow client
314
+ if (buffered > 64 * 1024) {
315
+ // 64KB threshold
316
+ log(
317
+ {
318
+ level: 'warn',
319
+ component: 'sessions',
320
+ message: 'Client backpressure detected',
321
+ data: { buffered }
322
+ },
323
+ this.config
324
+ );
325
+ return false;
326
+ }
327
+ }
328
+
329
+ client.send(data ?? JSON.stringify(_message));
330
+ return true;
331
+ } catch (err) {
332
+ log(
333
+ {
334
+ level: 'error',
335
+ component: 'sessions',
336
+ message: 'Failed to send to client',
337
+ data: { error: String(err) }
338
+ },
339
+ this.config
340
+ );
341
+ return false;
342
+ }
343
+ }
344
+ }
345
+
346
+ // ============================================================================
347
+ // Terminal WebSocket Manager
348
+ // ============================================================================
349
+
350
+ export class TerminalWsManager {
351
+ private clients = new Map<string, Set<WsClient>>();
352
+ private pollTimers = new Map<string, ReturnType<typeof setInterval>>();
353
+ private lastOutput = new Map<string, string>();
354
+ private config: Required<WsConfig>;
355
+ private totalClients = 0;
356
+ private droppedClients = 0;
357
+
358
+ constructor(config?: WsConfig) {
359
+ this.config = { ...DEFAULT_CONFIG, ...config };
360
+ }
361
+
362
+ /** Get current stats */
363
+ getStats(): { totalClients: number; targets: number; droppedClients: number } {
364
+ return {
365
+ totalClients: this.totalClients,
366
+ targets: this.clients.size,
367
+ droppedClients: this.droppedClients
368
+ };
369
+ }
370
+
371
+ /**
372
+ * Add a client for a target. Returns false if max clients reached.
373
+ */
374
+ addClient(client: WsClient, target: string): boolean {
375
+ // Check total limit
376
+ if (this.totalClients >= this.config.maxTerminalClientsTotal) {
377
+ log(
378
+ {
379
+ level: 'warn',
380
+ component: 'terminal',
381
+ message: 'Max total clients reached',
382
+ data: { current: this.totalClients, max: this.config.maxTerminalClientsTotal }
383
+ },
384
+ this.config
385
+ );
386
+ return false;
387
+ }
388
+
389
+ // Check per-target limit
390
+ const targetClients = this.clients.get(target);
391
+ if (targetClients && targetClients.size >= this.config.maxTerminalClientsPerTarget) {
392
+ log(
393
+ {
394
+ level: 'warn',
395
+ component: 'terminal',
396
+ message: 'Max clients per target reached',
397
+ data: { target, current: targetClients.size, max: this.config.maxTerminalClientsPerTarget }
398
+ },
399
+ this.config
400
+ );
401
+ return false;
402
+ }
403
+
404
+ if (!this.clients.has(target)) {
405
+ this.clients.set(target, new Set());
406
+ }
407
+ this.clients.get(target)!.add(client);
408
+ this.totalClients++;
409
+
410
+ log(
411
+ {
412
+ level: 'debug',
413
+ component: 'terminal',
414
+ message: 'Client connected',
415
+ data: { target, targetClients: this.clients.get(target)!.size, total: this.totalClients }
416
+ },
417
+ this.config
418
+ );
419
+
420
+ if (this.clients.get(target)!.size === 1) {
421
+ this.startPolling(target);
422
+ }
423
+ const output = capturePaneOutput(target) ?? '';
424
+ this.sendToClient(client, { type: 'output', output, timestamp: Date.now() });
425
+ return true;
426
+ }
427
+
428
+ removeClient(client: WsClient, target?: string): void {
429
+ if (target) {
430
+ const targetClients = this.clients.get(target);
431
+ if (targetClients && targetClients.delete(client)) {
432
+ this.totalClients--;
433
+ log(
434
+ {
435
+ level: 'debug',
436
+ component: 'terminal',
437
+ message: 'Client disconnected',
438
+ data: { target, targetClients: targetClients.size, total: this.totalClients }
439
+ },
440
+ this.config
441
+ );
442
+
443
+ if (targetClients.size === 0) {
444
+ this.stopPolling(target);
445
+ this.clients.delete(target);
446
+ this.lastOutput.delete(target);
447
+ }
448
+ }
449
+ } else {
450
+ // Find and remove from any target
451
+ for (const [t, clients] of this.clients) {
452
+ if (clients.delete(client)) {
453
+ this.totalClients--;
454
+ if (clients.size === 0) {
455
+ this.stopPolling(t);
456
+ this.clients.delete(t);
457
+ this.lastOutput.delete(t);
458
+ }
459
+ break;
460
+ }
461
+ }
462
+ }
463
+ }
464
+
465
+ private startPolling(target: string): void {
466
+ if (this.pollTimers.has(target)) return;
467
+ const timer = setInterval(() => this.pollAndBroadcast(target), 200);
468
+ this.pollTimers.set(target, timer);
469
+ }
470
+
471
+ private stopPolling(target: string): void {
472
+ const timer = this.pollTimers.get(target);
473
+ if (timer) {
474
+ clearInterval(timer);
475
+ this.pollTimers.delete(target);
476
+ }
477
+ }
478
+
479
+ private pollAndBroadcast(target: string): void {
480
+ const output = capturePaneOutput(target) ?? '';
481
+ const lastOutput = this.lastOutput.get(target) ?? '';
482
+ if (output === lastOutput) return;
483
+ this.lastOutput.set(target, output);
484
+ const message: TerminalMessage = { type: 'output', output, timestamp: Date.now() };
485
+ const data = JSON.stringify(message);
486
+
487
+ const clients = this.clients.get(target);
488
+ if (clients) {
489
+ for (const client of clients) {
490
+ if (!this.sendToClient(client, message, data)) {
491
+ // Client is slow/dead, remove it
492
+ clients.delete(client);
493
+ this.totalClients--;
494
+ this.droppedClients++;
495
+ log(
496
+ {
497
+ level: 'warn',
498
+ component: 'terminal',
499
+ message: 'Dropped slow client',
500
+ data: { target, total: this.totalClients }
501
+ },
502
+ this.config
503
+ );
504
+ }
505
+ }
506
+
507
+ // Clean up if no clients left
508
+ if (clients.size === 0) {
509
+ this.stopPolling(target);
510
+ this.clients.delete(target);
511
+ this.lastOutput.delete(target);
512
+ }
513
+ }
514
+ }
515
+
516
+ private sendToClient(client: WsClient, _message: TerminalMessage, data?: string): boolean {
517
+ try {
518
+ if (!client.isOpen()) return false;
519
+
520
+ // Backpressure check
521
+ if (client.getBufferedAmount) {
522
+ const buffered = client.getBufferedAmount();
523
+ if (buffered > 64 * 1024) {
524
+ log(
525
+ {
526
+ level: 'warn',
527
+ component: 'terminal',
528
+ message: 'Client backpressure detected',
529
+ data: { buffered }
530
+ },
531
+ this.config
532
+ );
533
+ return false;
534
+ }
535
+ }
536
+
537
+ client.send(data ?? JSON.stringify(_message));
538
+ return true;
539
+ } catch (err) {
540
+ log(
541
+ {
542
+ level: 'error',
543
+ component: 'terminal',
544
+ message: 'Failed to send to client',
545
+ data: { error: String(err) }
546
+ },
547
+ this.config
548
+ );
549
+ return false;
550
+ }
551
+ }
552
+ }
553
+
554
+ // ============================================================================
555
+ // Message Handling
556
+ // ============================================================================
557
+
558
+ /**
559
+ * Handle incoming WebSocket message. Returns 'pong' if ping, otherwise parses resize.
560
+ */
561
+ export function handleWsMessage(
562
+ msgStr: string,
563
+ onResize?: (cols: number, rows: number) => void
564
+ ): 'pong' | null {
565
+ if (msgStr === 'ping') {
566
+ return 'pong';
567
+ }
568
+
569
+ if (onResize) {
570
+ try {
571
+ const msg = JSON.parse(msgStr) as ResizeMessage;
572
+ if (msg.type === 'resize' && typeof msg.cols === 'number' && typeof msg.rows === 'number') {
573
+ onResize(msg.cols, msg.rows);
574
+ }
575
+ } catch {
576
+ // Ignore malformed messages
577
+ }
578
+ }
579
+
580
+ return null;
581
+ }
582
+
583
+ // ============================================================================
584
+ // Beads Types and Helpers
585
+ // ============================================================================
586
+
587
+ export interface BeadsIssue {
588
+ id: string;
589
+ title: string;
590
+ status: 'open' | 'in_progress' | 'blocked' | 'deferred' | 'closed';
591
+ priority: number;
592
+ issue_type: string;
593
+ owner?: string;
594
+ assignee?: string;
595
+ created_at?: string;
596
+ updated_at?: string;
597
+ dependencies?: { depends_on_id: string; type: string }[];
598
+ }
599
+
600
+ export interface BeadsMessage {
601
+ type: 'issues' | 'connected';
602
+ issues: BeadsIssue[];
603
+ project: string;
604
+ timestamp: number;
605
+ }
606
+
607
+ /**
608
+ * Fetch beads issues using bd CLI
609
+ */
610
+ export function getBeadsIssues(projectPath: string): BeadsIssue[] {
611
+ try {
612
+ const result = execFileSync('bd', ['list', '--json'], {
613
+ encoding: 'utf-8',
614
+ cwd: projectPath,
615
+ stdio: ['pipe', 'pipe', 'pipe'],
616
+ timeout: 5000
617
+ });
618
+ return JSON.parse(result) as BeadsIssue[];
619
+ } catch {
620
+ return [];
621
+ }
622
+ }
623
+
624
+ // ============================================================================
625
+ // Beads Watcher (per-project file watching)
626
+ // ============================================================================
627
+
628
+ import { existsSync, statSync } from 'fs';
629
+ import { join } from 'path';
630
+
631
+ interface BeadsWatcherCallback {
632
+ (): void;
633
+ }
634
+
635
+ class BeadsProjectWatcher {
636
+ private subscribers = new Map<string, Set<BeadsWatcherCallback>>();
637
+ private pollTimers = new Map<string, ReturnType<typeof setInterval>>();
638
+ private lastMtime = new Map<string, number>();
639
+ private readonly pollInterval = 1000;
640
+
641
+ /**
642
+ * Subscribe to changes in a project's beads issues
643
+ */
644
+ subscribe(projectPath: string, callback: BeadsWatcherCallback): () => void {
645
+ if (!this.subscribers.has(projectPath)) {
646
+ this.subscribers.set(projectPath, new Set());
647
+ }
648
+ this.subscribers.get(projectPath)!.add(callback);
649
+
650
+ // Start watching if first subscriber for this project
651
+ if (this.subscribers.get(projectPath)!.size === 1) {
652
+ this.startWatching(projectPath);
653
+ }
654
+
655
+ return () => {
656
+ const subs = this.subscribers.get(projectPath);
657
+ if (subs) {
658
+ subs.delete(callback);
659
+ if (subs.size === 0) {
660
+ this.stopWatching(projectPath);
661
+ this.subscribers.delete(projectPath);
662
+ }
663
+ }
664
+ };
665
+ }
666
+
667
+ private startWatching(projectPath: string): void {
668
+ const issuesFile = join(projectPath, '.beads', 'issues.jsonl');
669
+
670
+ // Get initial mtime
671
+ try {
672
+ if (existsSync(issuesFile)) {
673
+ this.lastMtime.set(projectPath, statSync(issuesFile).mtimeMs);
674
+ }
675
+ } catch {
676
+ // File may not exist
677
+ }
678
+
679
+ console.log('[beads-watcher] Started watching', projectPath);
680
+
681
+ const timer = setInterval(() => {
682
+ this.checkForChanges(projectPath, issuesFile);
683
+ }, this.pollInterval);
684
+
685
+ this.pollTimers.set(projectPath, timer);
686
+ }
687
+
688
+ private stopWatching(projectPath: string): void {
689
+ const timer = this.pollTimers.get(projectPath);
690
+ if (timer) {
691
+ clearInterval(timer);
692
+ this.pollTimers.delete(projectPath);
693
+ }
694
+ this.lastMtime.delete(projectPath);
695
+ console.log('[beads-watcher] Stopped watching', projectPath);
696
+ }
697
+
698
+ private checkForChanges(projectPath: string, issuesFile: string): void {
699
+ try {
700
+ if (!existsSync(issuesFile)) {
701
+ // File was deleted, notify
702
+ if (this.lastMtime.has(projectPath)) {
703
+ this.lastMtime.delete(projectPath);
704
+ this.notifySubscribers(projectPath);
705
+ }
706
+ return;
707
+ }
708
+
709
+ const currentMtime = statSync(issuesFile).mtimeMs;
710
+ const lastMtime = this.lastMtime.get(projectPath);
711
+
712
+ if (lastMtime === undefined || currentMtime !== lastMtime) {
713
+ this.lastMtime.set(projectPath, currentMtime);
714
+ if (lastMtime !== undefined) {
715
+ // Only notify on changes, not initial read
716
+ console.log('[beads-watcher] Issues changed in', projectPath);
717
+ this.notifySubscribers(projectPath);
718
+ }
719
+ }
720
+ } catch {
721
+ // Ignore errors during polling
722
+ }
723
+ }
724
+
725
+ private notifySubscribers(projectPath: string): void {
726
+ const subs = this.subscribers.get(projectPath);
727
+ if (subs) {
728
+ for (const callback of subs) {
729
+ try {
730
+ callback();
731
+ } catch (error) {
732
+ console.error('[beads-watcher] Subscriber error:', error);
733
+ }
734
+ }
735
+ }
736
+ }
737
+ }
738
+
739
+ export const beadsWatcher = new BeadsProjectWatcher();
740
+
741
+ // ============================================================================
742
+ // Beads WebSocket Manager
743
+ // ============================================================================
744
+
745
+ export class BeadsWsManager {
746
+ private clients = new Map<string, Set<WsClient>>();
747
+ private unsubscribes = new Map<string, () => void>();
748
+ private lastHash = new Map<string, string>();
749
+ private config: Required<WsConfig>;
750
+ private totalClients = 0;
751
+
752
+ constructor(config?: WsConfig) {
753
+ this.config = { ...DEFAULT_CONFIG, ...config };
754
+ }
755
+
756
+ getStats(): { totalClients: number; projects: number } {
757
+ return {
758
+ totalClients: this.totalClients,
759
+ projects: this.clients.size
760
+ };
761
+ }
762
+
763
+ addClient(client: WsClient, projectPath: string): boolean {
764
+ if (!projectPath) return false;
765
+
766
+ if (!this.clients.has(projectPath)) {
767
+ this.clients.set(projectPath, new Set());
768
+ }
769
+
770
+ this.clients.get(projectPath)!.add(client);
771
+ this.totalClients++;
772
+
773
+ log(
774
+ {
775
+ level: 'debug',
776
+ component: 'beads',
777
+ message: 'Client connected',
778
+ data: { project: projectPath, total: this.totalClients }
779
+ },
780
+ this.config
781
+ );
782
+
783
+ // Subscribe to watcher if first client for this project
784
+ if (this.clients.get(projectPath)!.size === 1 && !this.unsubscribes.has(projectPath)) {
785
+ console.log('[ws:beads] First client for project, subscribing to watcher');
786
+ const unsub = beadsWatcher.subscribe(projectPath, () =>
787
+ this.broadcastIfChanged(projectPath)
788
+ );
789
+ this.unsubscribes.set(projectPath, unsub);
790
+ }
791
+
792
+ // Send initial issues
793
+ this.sendToClient(client, this.createMessage(projectPath, 'connected'));
794
+ return true;
795
+ }
796
+
797
+ removeClient(client: WsClient, projectPath?: string): void {
798
+ if (projectPath) {
799
+ const projectClients = this.clients.get(projectPath);
800
+ if (projectClients && projectClients.delete(client)) {
801
+ this.totalClients--;
802
+
803
+ if (projectClients.size === 0) {
804
+ this.clients.delete(projectPath);
805
+ this.lastHash.delete(projectPath);
806
+
807
+ const unsub = this.unsubscribes.get(projectPath);
808
+ if (unsub) {
809
+ unsub();
810
+ this.unsubscribes.delete(projectPath);
811
+ }
812
+ }
813
+ }
814
+ } else {
815
+ // Find and remove from any project
816
+ for (const [project, clients] of this.clients) {
817
+ if (clients.delete(client)) {
818
+ this.totalClients--;
819
+ if (clients.size === 0) {
820
+ this.clients.delete(project);
821
+ this.lastHash.delete(project);
822
+
823
+ const unsub = this.unsubscribes.get(project);
824
+ if (unsub) {
825
+ unsub();
826
+ this.unsubscribes.delete(project);
827
+ }
828
+ }
829
+ break;
830
+ }
831
+ }
832
+ }
833
+ }
834
+
835
+ private createMessage(projectPath: string, type: 'issues' | 'connected'): BeadsMessage {
836
+ const issues = getBeadsIssues(projectPath);
837
+ return { type, issues, project: projectPath, timestamp: Date.now() };
838
+ }
839
+
840
+ private broadcastIfChanged(projectPath: string): void {
841
+ const message = this.createMessage(projectPath, 'issues');
842
+ const hash = JSON.stringify(message.issues);
843
+
844
+ if (hash === this.lastHash.get(projectPath)) {
845
+ return;
846
+ }
847
+
848
+ this.lastHash.set(projectPath, hash);
849
+ const data = JSON.stringify(message);
850
+
851
+ const clients = this.clients.get(projectPath);
852
+ if (clients) {
853
+ console.log('[ws:beads] Broadcasting to', clients.size, 'clients for', projectPath);
854
+ for (const client of clients) {
855
+ if (!this.sendToClient(client, message, data)) {
856
+ clients.delete(client);
857
+ this.totalClients--;
858
+ }
859
+ }
860
+
861
+ if (clients.size === 0) {
862
+ this.clients.delete(projectPath);
863
+ this.lastHash.delete(projectPath);
864
+
865
+ const unsub = this.unsubscribes.get(projectPath);
866
+ if (unsub) {
867
+ unsub();
868
+ this.unsubscribes.delete(projectPath);
869
+ }
870
+ }
871
+ }
872
+ }
873
+
874
+ private sendToClient(client: WsClient, _message: BeadsMessage, data?: string): boolean {
875
+ try {
876
+ if (!client.isOpen()) return false;
877
+
878
+ if (client.getBufferedAmount) {
879
+ const buffered = client.getBufferedAmount();
880
+ if (buffered > 64 * 1024) {
881
+ return false;
882
+ }
883
+ }
884
+
885
+ client.send(data ?? JSON.stringify(_message));
886
+ return true;
887
+ } catch {
888
+ return false;
889
+ }
890
+ }
891
+ }
892
+
893
+ // ============================================================================
894
+ // URL Parsing
895
+ // ============================================================================
896
+
897
+ export type WsPathResult =
898
+ | { type: 'sessions' }
899
+ | { type: 'terminal'; target: string }
900
+ | { type: 'beads'; project: string }
901
+ | null;
902
+
903
+ export function parseWsPath(pathname: string, searchParams?: URLSearchParams): WsPathResult {
904
+ if (pathname === '/api/sessions/stream') {
905
+ return { type: 'sessions' };
906
+ }
907
+
908
+ if (pathname === '/api/beads/stream') {
909
+ const project = searchParams?.get('project');
910
+ if (project) {
911
+ return { type: 'beads', project: decodeURIComponent(project) };
912
+ }
913
+ return null;
914
+ }
915
+
916
+ const termMatch = pathname.match(/^\/api\/sessions\/([^/]+)\/stream$/);
917
+ if (termMatch) {
918
+ return { type: 'terminal', target: decodeURIComponent(termMatch[1]) };
919
+ }
920
+
921
+ return null;
922
+ }