claude-code-plus-plus 0.1.0 → 0.2.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 (493) hide show
  1. package/dist/app.d.ts +52 -0
  2. package/dist/app.d.ts.map +1 -0
  3. package/dist/app.js +318 -0
  4. package/dist/app.js.map +1 -0
  5. package/dist/cli/commands/index.d.ts +5 -0
  6. package/dist/cli/commands/index.d.ts.map +1 -0
  7. package/dist/cli/commands/index.js +5 -0
  8. package/dist/cli/commands/index.js.map +1 -0
  9. package/dist/cli/commands/start.d.ts +16 -0
  10. package/dist/cli/commands/start.d.ts.map +1 -0
  11. package/dist/cli/commands/start.js +20 -0
  12. package/dist/cli/commands/start.js.map +1 -0
  13. package/dist/cli/index.d.ts +7 -0
  14. package/dist/cli/index.d.ts.map +1 -0
  15. package/dist/cli/index.js +7 -0
  16. package/dist/cli/index.js.map +1 -0
  17. package/dist/cli/parser.d.ts +31 -0
  18. package/dist/cli/parser.d.ts.map +1 -0
  19. package/dist/cli/parser.js +112 -0
  20. package/dist/cli/parser.js.map +1 -0
  21. package/dist/cli/validators.d.ts +46 -0
  22. package/dist/cli/validators.d.ts.map +1 -0
  23. package/dist/cli/validators.js +149 -0
  24. package/dist/cli/validators.js.map +1 -0
  25. package/dist/config/defaults.d.ts +14 -0
  26. package/dist/config/defaults.d.ts.map +1 -0
  27. package/dist/config/defaults.js +27 -0
  28. package/dist/config/defaults.js.map +1 -0
  29. package/dist/config/index.d.ts +10 -0
  30. package/dist/config/index.d.ts.map +1 -0
  31. package/dist/config/index.js +12 -0
  32. package/dist/config/index.js.map +1 -0
  33. package/dist/config/loader.d.ts +38 -0
  34. package/dist/config/loader.d.ts.map +1 -0
  35. package/dist/config/loader.js +107 -0
  36. package/dist/config/loader.js.map +1 -0
  37. package/dist/config/paths.d.ts +47 -0
  38. package/dist/config/paths.d.ts.map +1 -0
  39. package/dist/config/paths.js +86 -0
  40. package/dist/config/paths.js.map +1 -0
  41. package/dist/config/schema.d.ts +15 -0
  42. package/dist/config/schema.d.ts.map +1 -0
  43. package/dist/config/schema.js +32 -0
  44. package/dist/config/schema.js.map +1 -0
  45. package/dist/core/index.d.ts +13 -1
  46. package/dist/core/index.d.ts.map +1 -1
  47. package/dist/core/index.js +13 -1
  48. package/dist/core/index.js.map +1 -1
  49. package/dist/core/session/index.d.ts +7 -0
  50. package/dist/core/session/index.d.ts.map +1 -0
  51. package/dist/core/session/index.js +6 -0
  52. package/dist/core/session/index.js.map +1 -0
  53. package/dist/core/session/session-manager.d.ts +46 -0
  54. package/dist/core/session/session-manager.d.ts.map +1 -0
  55. package/dist/core/session/session-manager.js +245 -0
  56. package/dist/core/session/session-manager.js.map +1 -0
  57. package/dist/core/session/session.d.ts +44 -0
  58. package/dist/core/session/session.d.ts.map +1 -0
  59. package/dist/core/session/session.js +78 -0
  60. package/dist/core/session/session.js.map +1 -0
  61. package/dist/core/terminal/index.d.ts +7 -0
  62. package/dist/core/terminal/index.d.ts.map +1 -0
  63. package/dist/core/terminal/index.js +6 -0
  64. package/dist/core/terminal/index.js.map +1 -0
  65. package/dist/core/terminal/terminal-manager.d.ts +36 -0
  66. package/dist/core/terminal/terminal-manager.d.ts.map +1 -0
  67. package/dist/core/terminal/terminal-manager.js +182 -0
  68. package/dist/core/terminal/terminal-manager.js.map +1 -0
  69. package/dist/core/terminal/terminal.d.ts +27 -0
  70. package/dist/core/terminal/terminal.d.ts.map +1 -0
  71. package/dist/core/terminal/terminal.js +38 -0
  72. package/dist/core/terminal/terminal.js.map +1 -0
  73. package/dist/core/workspace/index.d.ts +7 -0
  74. package/dist/core/workspace/index.d.ts.map +1 -0
  75. package/dist/core/workspace/index.js +6 -0
  76. package/dist/core/workspace/index.js.map +1 -0
  77. package/dist/core/workspace/workspace-manager.d.ts +59 -0
  78. package/dist/core/workspace/workspace-manager.d.ts.map +1 -0
  79. package/dist/core/workspace/workspace-manager.js +224 -0
  80. package/dist/core/workspace/workspace-manager.js.map +1 -0
  81. package/dist/core/workspace/workspace.d.ts +56 -0
  82. package/dist/core/workspace/workspace.d.ts.map +1 -0
  83. package/dist/core/workspace/workspace.js +96 -0
  84. package/dist/core/workspace/workspace.js.map +1 -0
  85. package/dist/core/worktree/index.d.ts +7 -0
  86. package/dist/core/worktree/index.d.ts.map +1 -0
  87. package/dist/core/worktree/index.js +6 -0
  88. package/dist/core/worktree/index.js.map +1 -0
  89. package/dist/core/worktree/worktree-manager.d.ts +47 -0
  90. package/dist/core/worktree/worktree-manager.d.ts.map +1 -0
  91. package/dist/core/worktree/worktree-manager.js +170 -0
  92. package/dist/core/worktree/worktree-manager.js.map +1 -0
  93. package/dist/core/worktree/worktree.d.ts +35 -0
  94. package/dist/core/worktree/worktree.d.ts.map +1 -0
  95. package/dist/core/worktree/worktree.js +50 -0
  96. package/dist/core/worktree/worktree.js.map +1 -0
  97. package/dist/events/bus.d.ts +76 -0
  98. package/dist/events/bus.d.ts.map +1 -0
  99. package/dist/events/bus.js +223 -0
  100. package/dist/events/bus.js.map +1 -0
  101. package/dist/events/handlers/index.d.ts +28 -0
  102. package/dist/events/handlers/index.d.ts.map +1 -0
  103. package/dist/events/handlers/index.js +44 -0
  104. package/dist/events/handlers/index.js.map +1 -0
  105. package/dist/events/handlers/session-events.d.ts +12 -0
  106. package/dist/events/handlers/session-events.d.ts.map +1 -0
  107. package/dist/events/handlers/session-events.js +38 -0
  108. package/dist/events/handlers/session-events.js.map +1 -0
  109. package/dist/events/handlers/terminal-events.d.ts +12 -0
  110. package/dist/events/handlers/terminal-events.d.ts.map +1 -0
  111. package/dist/events/handlers/terminal-events.js +37 -0
  112. package/dist/events/handlers/terminal-events.js.map +1 -0
  113. package/dist/events/handlers/ui-events.d.ts +11 -0
  114. package/dist/events/handlers/ui-events.d.ts.map +1 -0
  115. package/dist/events/handlers/ui-events.js +202 -0
  116. package/dist/events/handlers/ui-events.js.map +1 -0
  117. package/dist/events/index.d.ts +9 -0
  118. package/dist/events/index.d.ts.map +1 -0
  119. package/dist/events/index.js +10 -0
  120. package/dist/events/index.js.map +1 -0
  121. package/dist/git/index.d.ts +5 -0
  122. package/dist/git/index.d.ts.map +1 -0
  123. package/dist/git/index.js +5 -0
  124. package/dist/git/index.js.map +1 -0
  125. package/dist/git/worktree.d.ts +44 -0
  126. package/dist/git/worktree.d.ts.map +1 -0
  127. package/dist/git/worktree.js +158 -0
  128. package/dist/git/worktree.js.map +1 -0
  129. package/dist/index.d.ts +0 -1
  130. package/dist/index.d.ts.map +1 -1
  131. package/dist/index.js +37 -144
  132. package/dist/index.js.map +1 -1
  133. package/dist/launcher/index.d.ts +24 -0
  134. package/dist/launcher/index.d.ts.map +1 -0
  135. package/dist/launcher/index.js +226 -0
  136. package/dist/launcher/index.js.map +1 -0
  137. package/dist/multiplexer/base.d.ts +73 -0
  138. package/dist/multiplexer/base.d.ts.map +1 -0
  139. package/dist/multiplexer/base.js +68 -0
  140. package/dist/multiplexer/base.js.map +1 -0
  141. package/dist/multiplexer/factory.d.ts +28 -0
  142. package/dist/multiplexer/factory.d.ts.map +1 -0
  143. package/dist/multiplexer/factory.js +72 -0
  144. package/dist/multiplexer/factory.js.map +1 -0
  145. package/dist/multiplexer/index.d.ts +14 -0
  146. package/dist/multiplexer/index.d.ts.map +1 -0
  147. package/dist/multiplexer/index.js +15 -0
  148. package/dist/multiplexer/index.js.map +1 -0
  149. package/dist/multiplexer/tmux/index.d.ts +9 -0
  150. package/dist/multiplexer/tmux/index.d.ts.map +1 -0
  151. package/dist/multiplexer/tmux/index.js +7 -0
  152. package/dist/multiplexer/tmux/index.js.map +1 -0
  153. package/dist/multiplexer/tmux/tmux-commands.d.ts +96 -0
  154. package/dist/multiplexer/tmux/tmux-commands.d.ts.map +1 -0
  155. package/dist/multiplexer/tmux/tmux-commands.js +282 -0
  156. package/dist/multiplexer/tmux/tmux-commands.js.map +1 -0
  157. package/dist/multiplexer/tmux/tmux-config.d.ts +57 -0
  158. package/dist/multiplexer/tmux/tmux-config.d.ts.map +1 -0
  159. package/dist/multiplexer/tmux/tmux-config.js +103 -0
  160. package/dist/multiplexer/tmux/tmux-config.js.map +1 -0
  161. package/dist/multiplexer/tmux/tmux-multiplexer.d.ts +94 -0
  162. package/dist/multiplexer/tmux/tmux-multiplexer.d.ts.map +1 -0
  163. package/dist/multiplexer/tmux/tmux-multiplexer.js +302 -0
  164. package/dist/multiplexer/tmux/tmux-multiplexer.js.map +1 -0
  165. package/dist/multiplexer/windows/index.d.ts +5 -0
  166. package/dist/multiplexer/windows/index.d.ts.map +1 -0
  167. package/dist/multiplexer/windows/index.js +5 -0
  168. package/dist/multiplexer/windows/index.js.map +1 -0
  169. package/dist/multiplexer/windows/windows-multiplexer.d.ts +61 -0
  170. package/dist/multiplexer/windows/windows-multiplexer.d.ts.map +1 -0
  171. package/dist/multiplexer/windows/windows-multiplexer.js +151 -0
  172. package/dist/multiplexer/windows/windows-multiplexer.js.map +1 -0
  173. package/dist/platform/clipboard.d.ts +26 -0
  174. package/dist/platform/clipboard.d.ts.map +1 -0
  175. package/dist/platform/clipboard.js +154 -0
  176. package/dist/platform/clipboard.js.map +1 -0
  177. package/dist/platform/detector.d.ts +78 -0
  178. package/dist/platform/detector.d.ts.map +1 -0
  179. package/dist/platform/detector.js +169 -0
  180. package/dist/platform/detector.js.map +1 -0
  181. package/dist/platform/index.d.ts +14 -0
  182. package/dist/platform/index.d.ts.map +1 -0
  183. package/dist/platform/index.js +15 -0
  184. package/dist/platform/index.js.map +1 -0
  185. package/dist/platform/paths.d.ts +63 -0
  186. package/dist/platform/paths.d.ts.map +1 -0
  187. package/dist/platform/paths.js +149 -0
  188. package/dist/platform/paths.js.map +1 -0
  189. package/dist/platform/shell.d.ts +43 -0
  190. package/dist/platform/shell.d.ts.map +1 -0
  191. package/dist/platform/shell.js +187 -0
  192. package/dist/platform/shell.js.map +1 -0
  193. package/dist/services/claude/claude-detector.d.ts +37 -0
  194. package/dist/services/claude/claude-detector.d.ts.map +1 -0
  195. package/dist/services/claude/claude-detector.js +83 -0
  196. package/dist/services/claude/claude-detector.js.map +1 -0
  197. package/dist/services/claude/claude-service.d.ts +57 -0
  198. package/dist/services/claude/claude-service.d.ts.map +1 -0
  199. package/dist/services/claude/claude-service.js +108 -0
  200. package/dist/services/claude/claude-service.js.map +1 -0
  201. package/dist/services/claude/index.d.ts +7 -0
  202. package/dist/services/claude/index.d.ts.map +1 -0
  203. package/dist/services/claude/index.js +6 -0
  204. package/dist/services/claude/index.js.map +1 -0
  205. package/dist/services/git/branch-service.d.ts +64 -0
  206. package/dist/services/git/branch-service.d.ts.map +1 -0
  207. package/dist/services/git/branch-service.js +217 -0
  208. package/dist/services/git/branch-service.js.map +1 -0
  209. package/dist/services/git/git-service.d.ts +44 -0
  210. package/dist/services/git/git-service.d.ts.map +1 -0
  211. package/dist/services/git/git-service.js +145 -0
  212. package/dist/services/git/git-service.js.map +1 -0
  213. package/dist/services/git/index.d.ts +8 -0
  214. package/dist/services/git/index.d.ts.map +1 -0
  215. package/dist/services/git/index.js +7 -0
  216. package/dist/services/git/index.js.map +1 -0
  217. package/dist/services/git/worktree-service.d.ts +61 -0
  218. package/dist/services/git/worktree-service.d.ts.map +1 -0
  219. package/dist/services/git/worktree-service.js +243 -0
  220. package/dist/services/git/worktree-service.js.map +1 -0
  221. package/dist/services/index.d.ts +12 -0
  222. package/dist/services/index.d.ts.map +1 -0
  223. package/dist/services/index.js +12 -0
  224. package/dist/services/index.js.map +1 -0
  225. package/dist/services/process/index.d.ts +8 -0
  226. package/dist/services/process/index.d.ts.map +1 -0
  227. package/dist/services/process/index.js +6 -0
  228. package/dist/services/process/index.js.map +1 -0
  229. package/dist/services/process/process-monitor.d.ts +98 -0
  230. package/dist/services/process/process-monitor.d.ts.map +1 -0
  231. package/dist/services/process/process-monitor.js +173 -0
  232. package/dist/services/process/process-monitor.js.map +1 -0
  233. package/dist/services/process/process-spawner.d.ts +55 -0
  234. package/dist/services/process/process-spawner.d.ts.map +1 -0
  235. package/dist/services/process/process-spawner.js +93 -0
  236. package/dist/services/process/process-spawner.js.map +1 -0
  237. package/dist/sidebar/app.d.ts +70 -0
  238. package/dist/sidebar/app.d.ts.map +1 -0
  239. package/dist/sidebar/app.js +1203 -0
  240. package/dist/sidebar/app.js.map +1 -0
  241. package/dist/sidebar/index.d.ts +8 -0
  242. package/dist/sidebar/index.d.ts.map +1 -0
  243. package/dist/sidebar/index.js +26 -0
  244. package/dist/sidebar/index.js.map +1 -0
  245. package/dist/sidebar/input.d.ts +33 -0
  246. package/dist/sidebar/input.d.ts.map +1 -0
  247. package/dist/sidebar/input.js +153 -0
  248. package/dist/sidebar/input.js.map +1 -0
  249. package/dist/sidebar/render.d.ts +73 -0
  250. package/dist/sidebar/render.d.ts.map +1 -0
  251. package/dist/sidebar/render.js +403 -0
  252. package/dist/sidebar/render.js.map +1 -0
  253. package/dist/state/actions.d.ts +49 -0
  254. package/dist/state/actions.d.ts.map +1 -0
  255. package/dist/state/actions.js +512 -0
  256. package/dist/state/actions.js.map +1 -0
  257. package/dist/state/index.d.ts +16 -0
  258. package/dist/state/index.d.ts.map +1 -0
  259. package/dist/state/index.js +28 -0
  260. package/dist/state/index.js.map +1 -0
  261. package/dist/state/middleware/index.d.ts +7 -0
  262. package/dist/state/middleware/index.d.ts.map +1 -0
  263. package/dist/state/middleware/index.js +6 -0
  264. package/dist/state/middleware/index.js.map +1 -0
  265. package/dist/state/middleware/logger.d.ts +22 -0
  266. package/dist/state/middleware/logger.d.ts.map +1 -0
  267. package/dist/state/middleware/logger.js +45 -0
  268. package/dist/state/middleware/logger.js.map +1 -0
  269. package/dist/state/middleware/persistence.d.ts +29 -0
  270. package/dist/state/middleware/persistence.d.ts.map +1 -0
  271. package/dist/state/middleware/persistence.js +71 -0
  272. package/dist/state/middleware/persistence.js.map +1 -0
  273. package/dist/state/persistence/file-adapter.d.ts +48 -0
  274. package/dist/state/persistence/file-adapter.d.ts.map +1 -0
  275. package/dist/state/persistence/file-adapter.js +96 -0
  276. package/dist/state/persistence/file-adapter.js.map +1 -0
  277. package/dist/state/persistence/index.d.ts +8 -0
  278. package/dist/state/persistence/index.d.ts.map +1 -0
  279. package/dist/state/persistence/index.js +6 -0
  280. package/dist/state/persistence/index.js.map +1 -0
  281. package/dist/state/persistence/serializer.d.ts +41 -0
  282. package/dist/state/persistence/serializer.d.ts.map +1 -0
  283. package/dist/state/persistence/serializer.js +114 -0
  284. package/dist/state/persistence/serializer.js.map +1 -0
  285. package/dist/state/selectors.d.ts +67 -0
  286. package/dist/state/selectors.d.ts.map +1 -0
  287. package/dist/state/selectors.js +177 -0
  288. package/dist/state/selectors.js.map +1 -0
  289. package/dist/state/store.d.ts +86 -0
  290. package/dist/state/store.d.ts.map +1 -0
  291. package/dist/state/store.js +161 -0
  292. package/dist/state/store.js.map +1 -0
  293. package/dist/state/types.d.ts +269 -0
  294. package/dist/state/types.d.ts.map +1 -0
  295. package/dist/state/types.js +7 -0
  296. package/dist/state/types.js.map +1 -0
  297. package/dist/terminal/bar-handler.d.ts +15 -0
  298. package/dist/terminal/bar-handler.d.ts.map +1 -0
  299. package/dist/terminal/bar-handler.js +242 -0
  300. package/dist/terminal/bar-handler.js.map +1 -0
  301. package/dist/terminal/bar-render.d.ts +33 -0
  302. package/dist/terminal/bar-render.d.ts.map +1 -0
  303. package/dist/terminal/bar-render.js +122 -0
  304. package/dist/terminal/bar-render.js.map +1 -0
  305. package/dist/terminal/index.d.ts +8 -0
  306. package/dist/terminal/index.d.ts.map +1 -0
  307. package/dist/terminal/index.js +9 -0
  308. package/dist/terminal/index.js.map +1 -0
  309. package/dist/tmux/commands.d.ts +26 -0
  310. package/dist/tmux/commands.d.ts.map +1 -0
  311. package/dist/tmux/commands.js +67 -0
  312. package/dist/tmux/commands.js.map +1 -0
  313. package/dist/tmux/index.d.ts +6 -0
  314. package/dist/tmux/index.d.ts.map +1 -0
  315. package/dist/tmux/index.js +16 -0
  316. package/dist/tmux/index.js.map +1 -0
  317. package/dist/tmux/pane.d.ts +111 -0
  318. package/dist/tmux/pane.d.ts.map +1 -0
  319. package/dist/tmux/pane.js +206 -0
  320. package/dist/tmux/pane.js.map +1 -0
  321. package/dist/types/config.d.ts +103 -0
  322. package/dist/types/config.d.ts.map +1 -0
  323. package/dist/types/config.js +7 -0
  324. package/dist/types/config.js.map +1 -0
  325. package/dist/types/entities.d.ts +88 -0
  326. package/dist/types/entities.d.ts.map +1 -0
  327. package/dist/types/entities.js +8 -0
  328. package/dist/types/entities.js.map +1 -0
  329. package/dist/types/events.d.ts +156 -0
  330. package/dist/types/events.d.ts.map +1 -0
  331. package/dist/types/events.js +8 -0
  332. package/dist/types/events.js.map +1 -0
  333. package/dist/types/index.d.ts +11 -0
  334. package/dist/types/index.d.ts.map +1 -0
  335. package/dist/types/index.js +8 -0
  336. package/dist/types/index.js.map +1 -0
  337. package/dist/types/multiplexer.d.ts +238 -0
  338. package/dist/types/multiplexer.d.ts.map +1 -0
  339. package/dist/types/multiplexer.js +8 -0
  340. package/dist/types/multiplexer.js.map +1 -0
  341. package/dist/types.d.ts +67 -3
  342. package/dist/types.d.ts.map +1 -1
  343. package/dist/types.js +6 -2
  344. package/dist/types.js.map +1 -1
  345. package/dist/ui/components/base.d.ts +155 -0
  346. package/dist/ui/components/base.d.ts.map +1 -0
  347. package/dist/ui/components/base.js +269 -0
  348. package/dist/ui/components/base.js.map +1 -0
  349. package/dist/ui/components/index.d.ts +10 -0
  350. package/dist/ui/components/index.d.ts.map +1 -0
  351. package/dist/ui/components/index.js +10 -0
  352. package/dist/ui/components/index.js.map +1 -0
  353. package/dist/ui/components/input-field.d.ts +98 -0
  354. package/dist/ui/components/input-field.d.ts.map +1 -0
  355. package/dist/ui/components/input-field.js +274 -0
  356. package/dist/ui/components/input-field.js.map +1 -0
  357. package/dist/ui/components/list.d.ts +94 -0
  358. package/dist/ui/components/list.d.ts.map +1 -0
  359. package/dist/ui/components/list.js +294 -0
  360. package/dist/ui/components/list.js.map +1 -0
  361. package/dist/ui/components/modal.d.ts +83 -0
  362. package/dist/ui/components/modal.d.ts.map +1 -0
  363. package/dist/ui/components/modal.js +249 -0
  364. package/dist/ui/components/modal.js.map +1 -0
  365. package/dist/ui/components/tabs.d.ts +97 -0
  366. package/dist/ui/components/tabs.d.ts.map +1 -0
  367. package/dist/ui/components/tabs.js +252 -0
  368. package/dist/ui/components/tabs.js.map +1 -0
  369. package/dist/ui/components/text.d.ts +60 -0
  370. package/dist/ui/components/text.d.ts.map +1 -0
  371. package/dist/ui/components/text.js +224 -0
  372. package/dist/ui/components/text.js.map +1 -0
  373. package/dist/ui/index.d.ts +12 -0
  374. package/dist/ui/index.d.ts.map +1 -0
  375. package/dist/ui/index.js +18 -0
  376. package/dist/ui/index.js.map +1 -0
  377. package/dist/ui/input/index.d.ts +8 -0
  378. package/dist/ui/input/index.d.ts.map +1 -0
  379. package/dist/ui/input/index.js +8 -0
  380. package/dist/ui/input/index.js.map +1 -0
  381. package/dist/ui/input/input-manager.d.ts +83 -0
  382. package/dist/ui/input/input-manager.d.ts.map +1 -0
  383. package/dist/ui/input/input-manager.js +200 -0
  384. package/dist/ui/input/input-manager.js.map +1 -0
  385. package/dist/ui/input/keybindings.d.ts +47 -0
  386. package/dist/ui/input/keybindings.d.ts.map +1 -0
  387. package/dist/ui/input/keybindings.js +168 -0
  388. package/dist/ui/input/keybindings.js.map +1 -0
  389. package/dist/ui/input/keyboard.d.ts +24 -0
  390. package/dist/ui/input/keyboard.d.ts.map +1 -0
  391. package/dist/ui/input/keyboard.js +230 -0
  392. package/dist/ui/input/keyboard.js.map +1 -0
  393. package/dist/ui/input/mouse.d.ts +44 -0
  394. package/dist/ui/input/mouse.d.ts.map +1 -0
  395. package/dist/ui/input/mouse.js +161 -0
  396. package/dist/ui/input/mouse.js.map +1 -0
  397. package/dist/ui/layout/box.d.ts +63 -0
  398. package/dist/ui/layout/box.d.ts.map +1 -0
  399. package/dist/ui/layout/box.js +160 -0
  400. package/dist/ui/layout/box.js.map +1 -0
  401. package/dist/ui/layout/constraints.d.ts +77 -0
  402. package/dist/ui/layout/constraints.d.ts.map +1 -0
  403. package/dist/ui/layout/constraints.js +154 -0
  404. package/dist/ui/layout/constraints.js.map +1 -0
  405. package/dist/ui/layout/flex.d.ts +59 -0
  406. package/dist/ui/layout/flex.d.ts.map +1 -0
  407. package/dist/ui/layout/flex.js +163 -0
  408. package/dist/ui/layout/flex.js.map +1 -0
  409. package/dist/ui/layout/index.d.ts +7 -0
  410. package/dist/ui/layout/index.d.ts.map +1 -0
  411. package/dist/ui/layout/index.js +7 -0
  412. package/dist/ui/layout/index.js.map +1 -0
  413. package/dist/ui/modals/delete-modal.d.ts +27 -0
  414. package/dist/ui/modals/delete-modal.d.ts.map +1 -0
  415. package/dist/ui/modals/delete-modal.js +59 -0
  416. package/dist/ui/modals/delete-modal.js.map +1 -0
  417. package/dist/ui/modals/index.d.ts +7 -0
  418. package/dist/ui/modals/index.d.ts.map +1 -0
  419. package/dist/ui/modals/index.js +7 -0
  420. package/dist/ui/modals/index.js.map +1 -0
  421. package/dist/ui/modals/input-modal.d.ts +66 -0
  422. package/dist/ui/modals/input-modal.d.ts.map +1 -0
  423. package/dist/ui/modals/input-modal.js +185 -0
  424. package/dist/ui/modals/input-modal.js.map +1 -0
  425. package/dist/ui/modals/quit-modal.d.ts +30 -0
  426. package/dist/ui/modals/quit-modal.d.ts.map +1 -0
  427. package/dist/ui/modals/quit-modal.js +65 -0
  428. package/dist/ui/modals/quit-modal.js.map +1 -0
  429. package/dist/ui/renderer/ansi.d.ts +206 -0
  430. package/dist/ui/renderer/ansi.d.ts.map +1 -0
  431. package/dist/ui/renderer/ansi.js +318 -0
  432. package/dist/ui/renderer/ansi.js.map +1 -0
  433. package/dist/ui/renderer/canvas.d.ts +126 -0
  434. package/dist/ui/renderer/canvas.d.ts.map +1 -0
  435. package/dist/ui/renderer/canvas.js +346 -0
  436. package/dist/ui/renderer/canvas.js.map +1 -0
  437. package/dist/ui/renderer/colors.d.ts +65 -0
  438. package/dist/ui/renderer/colors.d.ts.map +1 -0
  439. package/dist/ui/renderer/colors.js +260 -0
  440. package/dist/ui/renderer/colors.js.map +1 -0
  441. package/dist/ui/renderer/index.d.ts +8 -0
  442. package/dist/ui/renderer/index.d.ts.map +1 -0
  443. package/dist/ui/renderer/index.js +8 -0
  444. package/dist/ui/renderer/index.js.map +1 -0
  445. package/dist/ui/renderer/symbols.d.ts +176 -0
  446. package/dist/ui/renderer/symbols.d.ts.map +1 -0
  447. package/dist/ui/renderer/symbols.js +246 -0
  448. package/dist/ui/renderer/symbols.js.map +1 -0
  449. package/dist/ui/views/index.d.ts +7 -0
  450. package/dist/ui/views/index.d.ts.map +1 -0
  451. package/dist/ui/views/index.js +7 -0
  452. package/dist/ui/views/index.js.map +1 -0
  453. package/dist/ui/views/sidebar-view.d.ts +89 -0
  454. package/dist/ui/views/sidebar-view.d.ts.map +1 -0
  455. package/dist/ui/views/sidebar-view.js +259 -0
  456. package/dist/ui/views/sidebar-view.js.map +1 -0
  457. package/dist/ui/views/terminal-bar-view.d.ts +67 -0
  458. package/dist/ui/views/terminal-bar-view.d.ts.map +1 -0
  459. package/dist/ui/views/terminal-bar-view.js +172 -0
  460. package/dist/ui/views/terminal-bar-view.js.map +1 -0
  461. package/dist/ui/views/welcome-view.d.ts +34 -0
  462. package/dist/ui/views/welcome-view.d.ts.map +1 -0
  463. package/dist/ui/views/welcome-view.js +116 -0
  464. package/dist/ui/views/welcome-view.js.map +1 -0
  465. package/dist/utils/async.d.ts +68 -0
  466. package/dist/utils/async.d.ts.map +1 -0
  467. package/dist/utils/async.js +138 -0
  468. package/dist/utils/async.js.map +1 -0
  469. package/dist/utils/errors.d.ts +136 -0
  470. package/dist/utils/errors.d.ts.map +1 -0
  471. package/dist/utils/errors.js +224 -0
  472. package/dist/utils/errors.js.map +1 -0
  473. package/dist/utils/id.d.ts +51 -0
  474. package/dist/utils/id.d.ts.map +1 -0
  475. package/dist/utils/id.js +79 -0
  476. package/dist/utils/id.js.map +1 -0
  477. package/dist/utils/index.d.ts +15 -0
  478. package/dist/utils/index.d.ts.map +1 -0
  479. package/dist/utils/index.js +19 -0
  480. package/dist/utils/index.js.map +1 -0
  481. package/dist/utils/logger.d.ts +75 -0
  482. package/dist/utils/logger.d.ts.map +1 -0
  483. package/dist/utils/logger.js +212 -0
  484. package/dist/utils/logger.js.map +1 -0
  485. package/dist/utils/string.d.ts +71 -0
  486. package/dist/utils/string.d.ts.map +1 -0
  487. package/dist/utils/string.js +163 -0
  488. package/dist/utils/string.js.map +1 -0
  489. package/dist/utils/validation.d.ts +72 -0
  490. package/dist/utils/validation.d.ts.map +1 -0
  491. package/dist/utils/validation.js +196 -0
  492. package/dist/utils/validation.js.map +1 -0
  493. package/package.json +4 -4
@@ -0,0 +1,1203 @@
1
+ /**
2
+ * Sidebar Application
3
+ *
4
+ * Main sidebar class that handles state management, input handling, and rendering.
5
+ */
6
+ import { appendFileSync, existsSync, writeFileSync } from 'fs';
7
+ import { execSync } from 'child_process';
8
+ import { fileURLToPath } from 'url';
9
+ import { dirname, resolve } from 'path';
10
+ import * as tmux from '../tmux';
11
+ import { WorktreeManager } from '../git';
12
+ import { parseKey, parseMouseEvent, isMouseEvent, setupRawMode, restoreMode } from './input';
13
+ import { ansi, buildListItems, renderMain, renderQuitModal, renderDeleteModal, renderInputModal, renderCollapsed, } from './render';
14
+ // Debug logging
15
+ function debugLog(...args) {
16
+ const msg = `[${new Date().toISOString()}] ${args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ')}\n`;
17
+ appendFileSync('/tmp/claude-pp-sidebar.log', msg);
18
+ }
19
+ // ============================================================================
20
+ // Constants
21
+ // ============================================================================
22
+ const SIDEBAR_WIDTH = 25;
23
+ const DEFAULT_CLAUDE_CMD = 'claude --dangerously-skip-permissions';
24
+ const TERMINAL_BAR_HEIGHT = 1;
25
+ const CLAUDE_PANE_PERCENT = 70; // Claude pane gets 70%, terminal area gets 30%
26
+ /**
27
+ * Set up terminal bar resize enforcement (hook + mouse binding).
28
+ * Uses a file-based script to avoid complex quoting issues.
29
+ * Sets up both after-resize-pane hook AND MouseDragEnd1Border binding.
30
+ */
31
+ function setupTerminalBarResize(sessionName, claudePaneId, barPaneId, terminalBodyPaneId) {
32
+ const hookCmd = getTerminalBarResizeHook(claudePaneId, barPaneId, terminalBodyPaneId);
33
+ // Set up the after-resize-pane hook
34
+ tmux.setHook(sessionName, 'after-resize-pane', hookCmd);
35
+ // Also bind MouseDragEnd1Border to run the script when mouse drag ends
36
+ // This ensures the fix runs when user releases mouse after dragging border
37
+ const safeName = `${claudePaneId}-${barPaneId}`.replace(/%/g, '');
38
+ const scriptPath = `/tmp/cpp-resize-hook-${safeName}.sh`;
39
+ try {
40
+ execSync(`tmux bind-key -T root MouseDragEnd1Border run-shell "sh ${scriptPath}"`, { stdio: 'ignore' });
41
+ }
42
+ catch {
43
+ // Ignore errors
44
+ }
45
+ }
46
+ /**
47
+ * Create a shell script for terminal bar resize hook and return the hook command.
48
+ * Uses a file-based script to avoid complex quoting issues.
49
+ */
50
+ function getTerminalBarResizeHook(claudePaneId, barPaneId, terminalBodyPaneId) {
51
+ // Use pane IDs in filename to make it unique per terminal configuration
52
+ const safeName = `${claudePaneId}-${barPaneId}`.replace(/%/g, '');
53
+ const scriptPath = `/tmp/cpp-resize-hook-${safeName}.sh`;
54
+ // Write the script file
55
+ const scriptContent = `#!/bin/sh
56
+ # Terminal bar resize hook - keeps bar at ${TERMINAL_BAR_HEIGHT} row(s)
57
+ # Lock check - prevent recursion
58
+ LOCK=$(tmux show-option -gqv @cpp-resizing 2>/dev/null)
59
+ [ -n "$LOCK" ] && exit 0
60
+
61
+ # Get current heights
62
+ BAR_H=$(tmux display-message -p -t "${barPaneId}" '#{pane_height}' 2>/dev/null)
63
+ [ -z "$BAR_H" ] && exit 0
64
+ [ "$BAR_H" -eq ${TERMINAL_BAR_HEIGHT} ] && exit 0
65
+
66
+ CLAUDE_H=$(tmux display-message -p -t "${claudePaneId}" '#{pane_height}' 2>/dev/null)
67
+ BODY_H=$(tmux display-message -p -t "${terminalBodyPaneId}" '#{pane_height}' 2>/dev/null)
68
+
69
+ # Get previous heights (to detect which pane shrank)
70
+ PREV_CLAUDE=$(tmux show-option -gqv @cpp-prev-claude 2>/dev/null)
71
+ PREV_BODY=$(tmux show-option -gqv @cpp-prev-body 2>/dev/null)
72
+
73
+ # Acquire lock and set trap
74
+ tmux set-option -g @cpp-resizing 1
75
+ trap 'tmux set-option -gu @cpp-resizing 2>/dev/null' EXIT
76
+
77
+ # Calculate how much bar is over target
78
+ D=$((BAR_H - ${TERMINAL_BAR_HEIGHT}))
79
+
80
+ # Determine which pane to grow based on which one shrank
81
+ if [ -n "$PREV_CLAUDE" ] && [ "$CLAUDE_H" -lt "$PREV_CLAUDE" ]; then
82
+ # Claude shrank (user dragged tabs ceiling UP) -> grow Terminal body
83
+ tmux resize-pane -t "${terminalBodyPaneId}" -U "$D" 2>/dev/null
84
+ elif [ -n "$PREV_BODY" ] && [ "$BODY_H" -lt "$PREV_BODY" ]; then
85
+ # Terminal body shrank (user dragged body ceiling DOWN) -> grow Claude
86
+ tmux resize-pane -t "${claudePaneId}" -D "$D" 2>/dev/null
87
+ else
88
+ # Fallback: just set bar height directly, let tmux decide
89
+ :
90
+ fi
91
+
92
+ # Set bar to exact height
93
+ tmux resize-pane -t "${barPaneId}" -y ${TERMINAL_BAR_HEIGHT} 2>/dev/null
94
+
95
+ # Store FINAL heights (after adjustment) for next comparison
96
+ FINAL_CLAUDE=$(tmux display-message -p -t "${claudePaneId}" '#{pane_height}' 2>/dev/null)
97
+ FINAL_BODY=$(tmux display-message -p -t "${terminalBodyPaneId}" '#{pane_height}' 2>/dev/null)
98
+ tmux set-option -g @cpp-prev-claude "$FINAL_CLAUDE"
99
+ tmux set-option -g @cpp-prev-body "$FINAL_BODY"
100
+ `;
101
+ // Write script to file (synchronously)
102
+ writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
103
+ return `"run-shell 'sh ${scriptPath}'"`;
104
+ }
105
+ // ============================================================================
106
+ // Sidebar App
107
+ // ============================================================================
108
+ export class SidebarApp {
109
+ state;
110
+ worktreeManager;
111
+ running = false;
112
+ constructor(repoPath, sessionName, mainPaneId, sidebarPaneId) {
113
+ this.worktreeManager = new WorktreeManager(repoPath);
114
+ this.state = {
115
+ repoPath,
116
+ sessionName,
117
+ mainPaneId,
118
+ sidebarPaneId,
119
+ worktrees: [],
120
+ sessions: [],
121
+ selectedIndex: 0,
122
+ activeSessionId: null,
123
+ expandedWorktrees: new Set(),
124
+ modal: 'none',
125
+ modalSelection: 0,
126
+ inputBuffer: '',
127
+ deleteTarget: null,
128
+ fullscreenModal: false,
129
+ hiddenPaneId: null,
130
+ collapsed: false,
131
+ terminalCommandMode: false,
132
+ terminalCommandBuffer: '',
133
+ };
134
+ }
135
+ // ==========================================================================
136
+ // Lifecycle
137
+ // ==========================================================================
138
+ async init() {
139
+ this.state.worktrees = await this.worktreeManager.list();
140
+ // Fallback if not a git repo
141
+ if (this.state.worktrees.length === 0) {
142
+ const { basename } = await import('path');
143
+ this.state.worktrees = [{
144
+ id: 'current',
145
+ path: this.state.repoPath,
146
+ branch: basename(this.state.repoPath),
147
+ isMain: true,
148
+ }];
149
+ }
150
+ }
151
+ start() {
152
+ this.running = true;
153
+ // Set up terminal
154
+ process.stdout.write(ansi.hideCursor);
155
+ process.stdout.write(ansi.enableMouse);
156
+ setupRawMode();
157
+ // Input handling
158
+ process.stdin.on('data', (data) => this.handleInput(data));
159
+ // Resize handling
160
+ process.stdout.on('resize', () => {
161
+ this.syncCollapsedState();
162
+ this.render();
163
+ });
164
+ // Initial render
165
+ this.render();
166
+ }
167
+ stop() {
168
+ this.running = false;
169
+ process.stdout.write(ansi.disableMouse);
170
+ process.stdout.write(ansi.showCursor);
171
+ process.stdout.write(ansi.reset);
172
+ restoreMode();
173
+ }
174
+ // ==========================================================================
175
+ // Rendering
176
+ // ==========================================================================
177
+ render() {
178
+ if (!this.running)
179
+ return;
180
+ let output;
181
+ // Get dimensions - use tmux dimensions for fullscreen modals
182
+ let dims;
183
+ if (this.state.fullscreenModal) {
184
+ const paneDims = tmux.getPaneDimensions(this.state.sidebarPaneId);
185
+ dims = { cols: paneDims.width, rows: paneDims.height };
186
+ debugLog('render: fullscreen dims', dims);
187
+ }
188
+ if (this.state.collapsed) {
189
+ output = renderCollapsed(this.state.sessions.length);
190
+ }
191
+ else if (this.state.modal === 'quit') {
192
+ output = renderQuitModal(this.state, dims);
193
+ }
194
+ else if (this.state.modal === 'delete') {
195
+ const target = this.state.deleteTarget;
196
+ const name = target?.name || '';
197
+ output = renderDeleteModal(this.state, name, dims);
198
+ }
199
+ else if (this.state.modal === 'new-worktree') {
200
+ output = renderInputModal(this.state, 'New Worktree', 'Branch name:', dims);
201
+ }
202
+ else if (this.state.modal === 'rename') {
203
+ const target = this.getSelectedItem();
204
+ const title = target?.type === 'session' ? 'Rename Session' : 'Rename Branch';
205
+ output = renderInputModal(this.state, title, 'New name:', dims);
206
+ }
207
+ else if (this.state.modal === 'new-session') {
208
+ output = renderInputModal(this.state, 'New Session', 'Session name:', dims);
209
+ }
210
+ else {
211
+ output = renderMain(this.state);
212
+ }
213
+ process.stdout.write(output);
214
+ }
215
+ syncCollapsedState() {
216
+ const width = process.stdout.columns || SIDEBAR_WIDTH;
217
+ this.state.collapsed = width < SIDEBAR_WIDTH / 2;
218
+ }
219
+ // ==========================================================================
220
+ // Input Handling
221
+ // ==========================================================================
222
+ handleInput(data) {
223
+ const str = data.toString();
224
+ debugLog('handleInput:', 'hex=' + data.toString('hex'), 'modal=' + this.state.modal);
225
+ // Handle terminal command mode (commands from terminal bar handler)
226
+ if (this.state.terminalCommandMode) {
227
+ if (str === '\r' || str === '\n') {
228
+ // Enter - execute command
229
+ this.executeTerminalCommand(this.state.terminalCommandBuffer);
230
+ this.state.terminalCommandMode = false;
231
+ this.state.terminalCommandBuffer = '';
232
+ return;
233
+ }
234
+ else if (str === '\x1b') {
235
+ // Escape - cancel
236
+ this.state.terminalCommandMode = false;
237
+ this.state.terminalCommandBuffer = '';
238
+ return;
239
+ }
240
+ else {
241
+ // Accumulate command
242
+ this.state.terminalCommandBuffer += str;
243
+ return;
244
+ }
245
+ }
246
+ // Handle mouse events
247
+ if (isMouseEvent(str)) {
248
+ const event = parseMouseEvent(str);
249
+ if (event && event.button === 0 && event.release) {
250
+ this.handleClick(event.y, event.x);
251
+ }
252
+ return;
253
+ }
254
+ const key = parseKey(data);
255
+ debugLog('parsedKey:', key.key, 'ctrl=' + key.ctrl);
256
+ // Ctrl+U - enter terminal command mode (for commands from bar handler)
257
+ if (key.ctrl && key.key === 'u') {
258
+ this.state.terminalCommandMode = true;
259
+ this.state.terminalCommandBuffer = '';
260
+ return;
261
+ }
262
+ // Route input based on modal state
263
+ switch (this.state.modal) {
264
+ case 'quit':
265
+ this.handleQuitModalInput(key);
266
+ break;
267
+ case 'delete':
268
+ this.handleDeleteModalInput(key);
269
+ break;
270
+ case 'new-worktree':
271
+ case 'rename':
272
+ case 'new-session':
273
+ this.handleTextInput(key, data);
274
+ break;
275
+ default:
276
+ this.handleMainInput(key);
277
+ }
278
+ }
279
+ handleMainInput(key) {
280
+ // Ctrl+C - show quit modal (when sidebar is focused)
281
+ if (key.ctrl && key.key === 'c') {
282
+ this.enterFullscreenModal();
283
+ this.state.modal = 'quit';
284
+ this.state.modalSelection = 0;
285
+ this.render();
286
+ return;
287
+ }
288
+ // If collapsed, only expand on any key
289
+ if (this.state.collapsed) {
290
+ this.state.collapsed = false;
291
+ this.enforceSidebarWidth();
292
+ this.render();
293
+ return;
294
+ }
295
+ // Ctrl+Q - show quit modal (works from any pane via tmux binding)
296
+ if (key.ctrl && key.key === 'q') {
297
+ this.enterFullscreenModal();
298
+ this.state.modal = 'quit';
299
+ this.state.modalSelection = 0;
300
+ this.render();
301
+ return;
302
+ }
303
+ // Ctrl+G - toggle collapsed
304
+ if (key.ctrl && key.key === 'g') {
305
+ this.toggleCollapsed();
306
+ return;
307
+ }
308
+ // Ctrl+T - create new terminal for active session
309
+ if (key.ctrl && key.key === 't') {
310
+ this.createTerminal();
311
+ return;
312
+ }
313
+ // Navigation
314
+ if (key.key === 'up' || key.key === 'k') {
315
+ this.state.selectedIndex = Math.max(0, this.state.selectedIndex - 1);
316
+ this.render();
317
+ return;
318
+ }
319
+ if (key.key === 'down' || key.key === 'j') {
320
+ const maxIndex = this.getMaxIndex();
321
+ this.state.selectedIndex = Math.min(maxIndex, this.state.selectedIndex + 1);
322
+ this.render();
323
+ return;
324
+ }
325
+ // Enter - activate selected
326
+ if (key.key === 'enter') {
327
+ debugLog('handleMainInput: enter pressed, calling activateSelected');
328
+ this.activateSelected();
329
+ return;
330
+ }
331
+ // n - new worktree
332
+ if (key.key === 'n') {
333
+ this.enterFullscreenModal();
334
+ this.state.modal = 'new-worktree';
335
+ this.state.inputBuffer = '';
336
+ this.render();
337
+ return;
338
+ }
339
+ // d - delete
340
+ if (key.key === 'd') {
341
+ const item = this.getSelectedItem();
342
+ if (item && !(item.type === 'worktree' && item.worktree?.isMain)) {
343
+ // Store delete target info for context in modal
344
+ this.state.deleteTarget = {
345
+ type: item.type,
346
+ id: item.id,
347
+ name: item.type === 'session' ? (item.session?.title || '') : (item.worktree?.branch || ''),
348
+ worktree: item.worktree,
349
+ session: item.session,
350
+ };
351
+ this.enterFullscreenModal();
352
+ this.state.modal = 'delete';
353
+ this.state.modalSelection = 0; // Default to No
354
+ this.render();
355
+ }
356
+ return;
357
+ }
358
+ // r - rename
359
+ if (key.key === 'r') {
360
+ const item = this.getSelectedItem();
361
+ if (item && !(item.type === 'worktree' && item.worktree?.isMain)) {
362
+ this.enterFullscreenModal();
363
+ this.state.modal = 'rename';
364
+ this.state.inputBuffer = item.type === 'session'
365
+ ? item.session?.title || ''
366
+ : item.worktree?.branch || '';
367
+ this.render();
368
+ }
369
+ return;
370
+ }
371
+ }
372
+ handleQuitModalInput(key) {
373
+ // Escape - close modal
374
+ if (key.key === 'escape') {
375
+ this.state.modal = 'none';
376
+ this.exitFullscreenModal();
377
+ this.render();
378
+ return;
379
+ }
380
+ // Arrow keys - toggle selection
381
+ if (key.key === 'up' || key.key === 'down' || key.key === 'j' || key.key === 'k') {
382
+ this.state.modalSelection = this.state.modalSelection === 0 ? 1 : 0;
383
+ this.render();
384
+ return;
385
+ }
386
+ // Enter - confirm
387
+ if (key.key === 'enter') {
388
+ if (this.state.modalSelection === 0) {
389
+ // Detach
390
+ this.state.modal = 'none';
391
+ this.exitFullscreenModal();
392
+ this.render();
393
+ tmux.detachClient();
394
+ }
395
+ else {
396
+ // Kill - no need to restore pane since we're exiting
397
+ this.stop();
398
+ tmux.killSession(this.state.sessionName);
399
+ process.exit(0);
400
+ }
401
+ return;
402
+ }
403
+ }
404
+ handleDeleteModalInput(key) {
405
+ // Escape - cancel
406
+ if (key.key === 'escape') {
407
+ this.state.modal = 'none';
408
+ this.state.deleteTarget = null;
409
+ this.exitFullscreenModal();
410
+ this.render();
411
+ return;
412
+ }
413
+ // Arrow keys - toggle selection
414
+ if (key.key === 'up' || key.key === 'down' || key.key === 'j' || key.key === 'k') {
415
+ this.state.modalSelection = this.state.modalSelection === 0 ? 1 : 0;
416
+ this.render();
417
+ return;
418
+ }
419
+ // Enter - confirm
420
+ if (key.key === 'enter') {
421
+ if (this.state.modalSelection === 1) {
422
+ this.deleteSelected();
423
+ }
424
+ this.state.modal = 'none';
425
+ this.state.deleteTarget = null;
426
+ this.exitFullscreenModal();
427
+ this.render();
428
+ return;
429
+ }
430
+ // Quick keys
431
+ if (key.key === 'y' || key.key === 'Y') {
432
+ this.deleteSelected();
433
+ this.state.modal = 'none';
434
+ this.state.deleteTarget = null;
435
+ this.exitFullscreenModal();
436
+ this.render();
437
+ return;
438
+ }
439
+ if (key.key === 'n' || key.key === 'N') {
440
+ this.state.modal = 'none';
441
+ this.state.deleteTarget = null;
442
+ this.exitFullscreenModal();
443
+ this.render();
444
+ return;
445
+ }
446
+ }
447
+ handleTextInput(key, data) {
448
+ debugLog('handleTextInput: key=' + key.key, 'modal=' + this.state.modal, 'buffer=' + this.state.inputBuffer);
449
+ // Escape - cancel
450
+ if (key.key === 'escape') {
451
+ debugLog('handleTextInput: escape pressed, canceling');
452
+ this.state.modal = 'none';
453
+ this.state.inputBuffer = '';
454
+ this.exitFullscreenModal();
455
+ this.render();
456
+ return;
457
+ }
458
+ // Enter - confirm
459
+ if (key.key === 'enter') {
460
+ const value = this.state.inputBuffer.trim();
461
+ debugLog('handleTextInput: enter pressed, value=' + value);
462
+ const wasNewSession = this.state.modal === 'new-session';
463
+ if (value) {
464
+ debugLog('handleTextInput: calling confirmTextInput');
465
+ this.confirmTextInput(value);
466
+ }
467
+ this.state.modal = 'none';
468
+ this.state.inputBuffer = '';
469
+ this.exitFullscreenModal();
470
+ // After creating a new session, focus the Claude pane (exitFullscreenModal selects sidebar)
471
+ if (wasNewSession && value) {
472
+ const activeSession = this.state.sessions.find(s => s.id === this.state.activeSessionId);
473
+ if (activeSession) {
474
+ tmux.selectPane(activeSession.paneId);
475
+ }
476
+ }
477
+ this.render();
478
+ return;
479
+ }
480
+ // Backspace
481
+ if (key.key === 'backspace') {
482
+ this.state.inputBuffer = this.state.inputBuffer.slice(0, -1);
483
+ this.render();
484
+ return;
485
+ }
486
+ // Regular characters
487
+ if (data.length === 1 && data[0] >= 32 && data[0] < 127) {
488
+ const char = String.fromCharCode(data[0]);
489
+ // For branch names, only allow valid characters
490
+ if (this.state.modal === 'new-worktree') {
491
+ if (/[a-zA-Z0-9\-_\/.]/.test(char)) {
492
+ this.state.inputBuffer += char;
493
+ this.render();
494
+ }
495
+ }
496
+ else {
497
+ this.state.inputBuffer += char;
498
+ this.render();
499
+ }
500
+ }
501
+ }
502
+ confirmTextInput(value) {
503
+ switch (this.state.modal) {
504
+ case 'new-worktree':
505
+ this.createWorktree(value);
506
+ break;
507
+ case 'rename':
508
+ this.renameSelected(value);
509
+ break;
510
+ case 'new-session':
511
+ this.createSession(value);
512
+ break;
513
+ }
514
+ }
515
+ handleClick(row, col) {
516
+ const cols = process.stdout.columns || SIDEBAR_WIDTH;
517
+ if (this.state.collapsed) {
518
+ this.state.collapsed = false;
519
+ this.enforceSidebarWidth();
520
+ this.render();
521
+ return;
522
+ }
523
+ // Ignore clicks when modals are open
524
+ if (this.state.modal !== 'none')
525
+ return;
526
+ // Check for collapse button click (row 1, right side)
527
+ if (row === 1 && col >= cols - 3) {
528
+ this.toggleCollapsed();
529
+ return;
530
+ }
531
+ // Build items and find clicked item
532
+ const items = buildListItems(this.state);
533
+ const itemRow = row - 3; // Header takes 2 rows
534
+ // Check for "New Worktree" button click (after items + 1 empty row)
535
+ const newWorktreeRow = items.length + 1; // +1 for empty row after items
536
+ if (itemRow === newWorktreeRow) {
537
+ this.enterFullscreenModal();
538
+ this.state.modal = 'new-worktree';
539
+ this.state.inputBuffer = '';
540
+ this.render();
541
+ return;
542
+ }
543
+ if (itemRow >= 0 && itemRow < items.length) {
544
+ this.state.selectedIndex = itemRow;
545
+ this.activateSelected();
546
+ }
547
+ }
548
+ // ==========================================================================
549
+ // Actions
550
+ // ==========================================================================
551
+ activateSelected() {
552
+ debugLog('activateSelected: selectedIndex=' + this.state.selectedIndex);
553
+ const item = this.getSelectedItem();
554
+ debugLog('activateSelected: item=', item);
555
+ if (!item) {
556
+ debugLog('activateSelected: no item found');
557
+ return;
558
+ }
559
+ if (item.type === 'worktree') {
560
+ // Show new session modal
561
+ debugLog('activateSelected: showing new-session modal for worktree', item.worktree?.branch);
562
+ this.enterFullscreenModal();
563
+ this.state.modal = 'new-session';
564
+ const sessions = this.state.sessions.filter(s => s.worktreeId === item.id);
565
+ this.state.inputBuffer = `${sessions.length + 1}: ${item.worktree?.branch || 'session'}`;
566
+ this.render();
567
+ }
568
+ else {
569
+ // Switch to session
570
+ debugLog('activateSelected: switching to session', item.session?.title);
571
+ this.switchToSession(item.session);
572
+ }
573
+ }
574
+ async createWorktree(branchName) {
575
+ try {
576
+ const worktree = await this.worktreeManager.create(branchName, true);
577
+ this.state.worktrees.push(worktree);
578
+ this.state.selectedIndex = this.getMaxIndex();
579
+ this.render();
580
+ }
581
+ catch (err) {
582
+ // Failed to create worktree
583
+ }
584
+ }
585
+ createSession(title) {
586
+ debugLog('createSession: title=' + title);
587
+ const item = this.getSelectedItem();
588
+ debugLog('createSession: selectedItem=', item);
589
+ if (!item || item.type !== 'worktree') {
590
+ debugLog('createSession: FAILED - item is not a worktree or is null');
591
+ return;
592
+ }
593
+ const worktree = item.worktree;
594
+ debugLog('createSession: worktree=' + worktree.branch, 'path=' + worktree.path);
595
+ const sessionId = `session-${Date.now()}`;
596
+ let paneId;
597
+ const claudeCmd = DEFAULT_CLAUDE_CMD;
598
+ if (this.state.sessions.length === 0) {
599
+ // First session - use existing main pane
600
+ // If in fullscreen mode, the pane is broken but we can still send keys to it
601
+ // exitFullscreenModal will join it back
602
+ paneId = this.state.mainPaneId;
603
+ tmux.sendControlKey(paneId, 'C-c');
604
+ tmux.sendKeys(paneId, `cd "${worktree.path}" && clear && ${claudeCmd}`, true);
605
+ }
606
+ else {
607
+ // Additional session - need to handle fullscreen mode specially
608
+ const currentSession = this.state.sessions.find(s => s.id === this.state.activeSessionId);
609
+ if (this.state.fullscreenModal) {
610
+ // In fullscreen mode, the current session pane is already broken
611
+ // We'll create a new pane and DON'T want to rejoin the old one
612
+ // Clear hiddenPaneId so exitFullscreenModal won't join it back
613
+ // The old session stays hidden and can be switched to later
614
+ debugLog('createSession: in fullscreen mode, clearing hiddenPaneId to keep old session hidden');
615
+ this.state.hiddenPaneId = null;
616
+ }
617
+ else if (currentSession) {
618
+ // Normal mode - break the current session pane
619
+ tmux.breakPane(currentSession.paneId);
620
+ }
621
+ paneId = tmux.splitHorizontal(this.state.sessionName, 80, worktree.path);
622
+ tmux.sendKeys(paneId, claudeCmd, true);
623
+ }
624
+ const session = {
625
+ id: sessionId,
626
+ worktreeId: worktree.id,
627
+ paneId,
628
+ title,
629
+ createdAt: Date.now(),
630
+ // Terminal management
631
+ terminals: [],
632
+ activeTerminalIndex: 0,
633
+ terminalBarPaneId: null,
634
+ };
635
+ this.state.sessions.push(session);
636
+ this.state.activeSessionId = sessionId;
637
+ this.enforceSidebarWidth();
638
+ // Focus the Claude pane so user can start interacting immediately
639
+ tmux.selectPane(paneId);
640
+ this.render();
641
+ }
642
+ switchToSession(session) {
643
+ if (session.id === this.state.activeSessionId) {
644
+ // Already active - focus pane
645
+ tmux.selectPane(session.paneId);
646
+ return;
647
+ }
648
+ // Break current session's panes (Claude pane + terminals)
649
+ const currentSession = this.state.sessions.find(s => s.id === this.state.activeSessionId);
650
+ if (currentSession) {
651
+ // Break active terminal first (if any)
652
+ if (currentSession.terminals.length > 0) {
653
+ const activeTerminal = currentSession.terminals[currentSession.activeTerminalIndex];
654
+ if (activeTerminal) {
655
+ tmux.breakPane(activeTerminal.paneId);
656
+ }
657
+ // Break terminal bar
658
+ if (currentSession.terminalBarPaneId) {
659
+ tmux.breakPane(currentSession.terminalBarPaneId);
660
+ }
661
+ }
662
+ // Break Claude pane
663
+ tmux.breakPane(currentSession.paneId);
664
+ }
665
+ // Join new session's Claude pane
666
+ tmux.joinPane(session.paneId, this.state.sidebarPaneId, true);
667
+ // Join terminal bar and active terminal if session has terminals
668
+ if (session.terminals.length > 0 && session.terminalBarPaneId) {
669
+ // Join terminal bar below Claude pane
670
+ tmux.joinPane(session.terminalBarPaneId, session.paneId, false);
671
+ // Join active terminal below terminal bar
672
+ const activeTerminal = session.terminals[session.activeTerminalIndex];
673
+ if (activeTerminal) {
674
+ tmux.joinPane(activeTerminal.paneId, session.terminalBarPaneId, false);
675
+ }
676
+ // Ensure terminal bar is exactly 1 row
677
+ tmux.resizePane(session.terminalBarPaneId, undefined, TERMINAL_BAR_HEIGHT);
678
+ // Update resize enforcement for this session's terminal bar
679
+ if (activeTerminal) {
680
+ setupTerminalBarResize(this.state.sessionName, session.paneId, session.terminalBarPaneId, activeTerminal.paneId);
681
+ }
682
+ // Update terminal bar display
683
+ this.updateTerminalBar(session);
684
+ }
685
+ this.state.activeSessionId = session.id;
686
+ this.enforceSidebarWidth();
687
+ tmux.selectPane(this.state.sidebarPaneId);
688
+ this.render();
689
+ }
690
+ async deleteSelected() {
691
+ const item = this.getSelectedItem();
692
+ if (!item)
693
+ return;
694
+ if (item.type === 'session') {
695
+ this.deleteSession(item.session);
696
+ }
697
+ else if (item.type === 'worktree' && !item.worktree?.isMain) {
698
+ await this.deleteWorktree(item.worktree);
699
+ }
700
+ }
701
+ deleteSession(session) {
702
+ // Kill all terminal panes
703
+ for (const terminal of session.terminals) {
704
+ tmux.killPane(terminal.paneId);
705
+ }
706
+ // Kill terminal bar pane and remove hook
707
+ if (session.terminalBarPaneId) {
708
+ try {
709
+ tmux.removeHook(this.state.sessionName, 'after-resize-pane');
710
+ }
711
+ catch {
712
+ // Hook may not exist, ignore
713
+ }
714
+ tmux.killPane(session.terminalBarPaneId);
715
+ }
716
+ // Kill the Claude pane
717
+ tmux.killPane(session.paneId);
718
+ // Remove from sessions
719
+ this.state.sessions = this.state.sessions.filter(s => s.id !== session.id);
720
+ // If this was active, switch to another
721
+ if (this.state.activeSessionId === session.id) {
722
+ this.state.activeSessionId = null;
723
+ if (this.state.sessions.length > 0) {
724
+ const nextSession = this.state.sessions[0];
725
+ tmux.joinPane(nextSession.paneId, this.state.sidebarPaneId, true);
726
+ this.state.activeSessionId = nextSession.id;
727
+ // Join terminal panes if next session has terminals
728
+ if (nextSession.terminals.length > 0 && nextSession.terminalBarPaneId) {
729
+ tmux.joinPane(nextSession.terminalBarPaneId, nextSession.paneId, false);
730
+ const activeTerminal = nextSession.terminals[nextSession.activeTerminalIndex];
731
+ if (activeTerminal) {
732
+ tmux.joinPane(activeTerminal.paneId, nextSession.terminalBarPaneId, false);
733
+ }
734
+ tmux.resizePane(nextSession.terminalBarPaneId, undefined, TERMINAL_BAR_HEIGHT);
735
+ // Update resize enforcement for the new session's terminal bar
736
+ if (activeTerminal) {
737
+ setupTerminalBarResize(this.state.sessionName, nextSession.paneId, nextSession.terminalBarPaneId, activeTerminal.paneId);
738
+ }
739
+ this.updateTerminalBar(nextSession);
740
+ }
741
+ this.enforceSidebarWidth();
742
+ }
743
+ else {
744
+ // No more sessions - create empty pane
745
+ const newPaneId = tmux.splitHorizontal(this.state.sessionName, 80, this.state.repoPath);
746
+ this.state.mainPaneId = newPaneId;
747
+ tmux.sendKeys(newPaneId, 'echo "Press Enter in sidebar to start a session"', true);
748
+ this.enforceSidebarWidth();
749
+ }
750
+ tmux.selectPane(this.state.sidebarPaneId);
751
+ }
752
+ // Adjust selection
753
+ this.state.selectedIndex = Math.max(0, this.state.selectedIndex - 1);
754
+ this.render();
755
+ }
756
+ async deleteWorktree(worktree) {
757
+ // Delete all sessions for this worktree (including their terminals)
758
+ const sessionsToDelete = this.state.sessions.filter(s => s.worktreeId === worktree.id);
759
+ for (const session of sessionsToDelete) {
760
+ // Kill all terminal panes
761
+ for (const terminal of session.terminals) {
762
+ tmux.killPane(terminal.paneId);
763
+ }
764
+ // Kill terminal bar pane and remove hook
765
+ if (session.terminalBarPaneId) {
766
+ try {
767
+ tmux.removeHook(this.state.sessionName, 'after-resize-pane');
768
+ }
769
+ catch {
770
+ // Hook may not exist, ignore
771
+ }
772
+ tmux.killPane(session.terminalBarPaneId);
773
+ }
774
+ // Kill Claude pane
775
+ tmux.killPane(session.paneId);
776
+ }
777
+ this.state.sessions = this.state.sessions.filter(s => s.worktreeId !== worktree.id);
778
+ // If active session was deleted, switch to another
779
+ if (sessionsToDelete.some(s => s.id === this.state.activeSessionId)) {
780
+ this.state.activeSessionId = null;
781
+ if (this.state.sessions.length > 0) {
782
+ const nextSession = this.state.sessions[0];
783
+ tmux.joinPane(nextSession.paneId, this.state.sidebarPaneId, true);
784
+ this.state.activeSessionId = nextSession.id;
785
+ // Join terminal panes if next session has terminals
786
+ if (nextSession.terminals.length > 0 && nextSession.terminalBarPaneId) {
787
+ tmux.joinPane(nextSession.terminalBarPaneId, nextSession.paneId, false);
788
+ const activeTerminal = nextSession.terminals[nextSession.activeTerminalIndex];
789
+ if (activeTerminal) {
790
+ tmux.joinPane(activeTerminal.paneId, nextSession.terminalBarPaneId, false);
791
+ }
792
+ tmux.resizePane(nextSession.terminalBarPaneId, undefined, TERMINAL_BAR_HEIGHT);
793
+ // Update resize enforcement for the new session's terminal bar
794
+ if (activeTerminal) {
795
+ setupTerminalBarResize(this.state.sessionName, nextSession.paneId, nextSession.terminalBarPaneId, activeTerminal.paneId);
796
+ }
797
+ this.updateTerminalBar(nextSession);
798
+ }
799
+ this.enforceSidebarWidth();
800
+ }
801
+ tmux.selectPane(this.state.sidebarPaneId);
802
+ }
803
+ // Remove worktree via git
804
+ try {
805
+ await this.worktreeManager.remove(worktree.path, true);
806
+ }
807
+ catch {
808
+ // Ignore errors
809
+ }
810
+ // Remove from state
811
+ this.state.worktrees = this.state.worktrees.filter(w => w.id !== worktree.id);
812
+ // Adjust selection
813
+ const totalItems = this.getTotalItemCount();
814
+ if (this.state.selectedIndex >= totalItems) {
815
+ this.state.selectedIndex = Math.max(0, totalItems - 1);
816
+ }
817
+ this.render();
818
+ }
819
+ renameSelected(newName) {
820
+ const item = this.getSelectedItem();
821
+ if (!item)
822
+ return;
823
+ if (item.type === 'session' && item.session) {
824
+ item.session.title = newName;
825
+ }
826
+ // Worktree rename would require git branch rename - skip for now
827
+ }
828
+ toggleCollapsed() {
829
+ this.state.collapsed = !this.state.collapsed;
830
+ if (this.state.collapsed) {
831
+ tmux.resizePane(this.state.sidebarPaneId, 2);
832
+ }
833
+ else {
834
+ this.enforceSidebarWidth();
835
+ }
836
+ this.render();
837
+ }
838
+ enforceSidebarWidth() {
839
+ if (!this.state.collapsed) {
840
+ tmux.resizePane(this.state.sidebarPaneId, SIDEBAR_WIDTH);
841
+ }
842
+ }
843
+ // ==========================================================================
844
+ // Terminal Management
845
+ // ==========================================================================
846
+ /**
847
+ * Execute a terminal command received from the bar handler
848
+ * Format: "TERM:<action>:<data>"
849
+ */
850
+ executeTerminalCommand(command) {
851
+ debugLog('executeTerminalCommand:', command);
852
+ if (!command.startsWith('TERM:'))
853
+ return;
854
+ const parts = command.slice(5).split(':');
855
+ const action = parts[0];
856
+ const data = parts[1] || '';
857
+ const session = this.state.sessions.find(s => s.id === this.state.activeSessionId);
858
+ if (!session && action !== 'escape')
859
+ return;
860
+ switch (action) {
861
+ case 'switch':
862
+ const index = parseInt(data, 10);
863
+ if (!isNaN(index) && session) {
864
+ this.switchTerminal(session, index);
865
+ }
866
+ break;
867
+ case 'new':
868
+ this.createTerminal();
869
+ break;
870
+ case 'delete':
871
+ const delIndex = parseInt(data, 10);
872
+ if (!isNaN(delIndex) && session) {
873
+ this.deleteTerminal(session, delIndex);
874
+ }
875
+ break;
876
+ case 'focus':
877
+ if (session && session.terminals.length > 0) {
878
+ const activeTerminal = session.terminals[session.activeTerminalIndex];
879
+ if (activeTerminal) {
880
+ tmux.selectPane(activeTerminal.paneId);
881
+ }
882
+ }
883
+ break;
884
+ case 'escape':
885
+ tmux.selectPane(this.state.sidebarPaneId);
886
+ break;
887
+ }
888
+ }
889
+ /**
890
+ * Create a new terminal for the active session
891
+ */
892
+ createTerminal() {
893
+ if (!this.state.activeSessionId) {
894
+ debugLog('createTerminal: no active session');
895
+ return;
896
+ }
897
+ const session = this.state.sessions.find(s => s.id === this.state.activeSessionId);
898
+ if (!session)
899
+ return;
900
+ const worktree = this.state.worktrees.find(w => w.id === session.worktreeId);
901
+ if (!worktree)
902
+ return;
903
+ const terminalNum = session.terminals.length + 1;
904
+ const terminalTitle = `Terminal ${terminalNum}`;
905
+ const terminalId = `terminal-${Date.now()}`;
906
+ debugLog('createTerminal:', terminalTitle, 'for session', session.title);
907
+ if (session.terminals.length === 0) {
908
+ // First terminal - need to create terminal bar pane + terminal pane
909
+ // Split Claude pane vertically (Claude gets top 70%, terminal area gets bottom 30%)
910
+ tmux.selectPane(session.paneId);
911
+ const terminalAreaPaneId = tmux.splitVertical(this.state.sessionName, 100 - CLAUDE_PANE_PERCENT, worktree.path);
912
+ // Split terminal area: top 1 row for bar, rest for terminal
913
+ tmux.selectPane(terminalAreaPaneId);
914
+ const terminalPaneId = tmux.splitVertical(this.state.sessionName, 95, worktree.path);
915
+ // The terminalAreaPaneId is now the bar pane (top part after split)
916
+ const terminalBarPaneId = terminalAreaPaneId;
917
+ // Resize bar pane to exactly 1 row
918
+ tmux.resizePane(terminalBarPaneId, undefined, TERMINAL_BAR_HEIGHT);
919
+ // Set up resize enforcement (hook + mouse binding)
920
+ setupTerminalBarResize(this.state.sessionName, session.paneId, terminalBarPaneId, terminalPaneId);
921
+ // Update session
922
+ session.terminalBarPaneId = terminalBarPaneId;
923
+ session.terminals.push({
924
+ id: terminalId,
925
+ sessionId: session.id,
926
+ paneId: terminalPaneId,
927
+ title: terminalTitle,
928
+ createdAt: Date.now(),
929
+ });
930
+ session.activeTerminalIndex = 0;
931
+ // Start terminal bar handler in the bar pane
932
+ this.startTerminalBarHandler(session);
933
+ // Focus the terminal pane
934
+ tmux.selectPane(terminalPaneId);
935
+ }
936
+ else {
937
+ // Additional terminal - split current terminal, break old, show new
938
+ const currentTerminal = session.terminals[session.activeTerminalIndex];
939
+ // Split from current terminal to create new one
940
+ tmux.selectPane(currentTerminal.paneId);
941
+ const newTerminalPaneId = tmux.splitVertical(this.state.sessionName, 50, worktree.path);
942
+ // Break the current terminal to background
943
+ tmux.breakPane(currentTerminal.paneId);
944
+ // Add new terminal
945
+ session.terminals.push({
946
+ id: terminalId,
947
+ sessionId: session.id,
948
+ paneId: newTerminalPaneId,
949
+ title: terminalTitle,
950
+ createdAt: Date.now(),
951
+ });
952
+ session.activeTerminalIndex = session.terminals.length - 1;
953
+ // Ensure terminal bar stays at 1 row after the split
954
+ if (session.terminalBarPaneId) {
955
+ tmux.resizePane(session.terminalBarPaneId, undefined, TERMINAL_BAR_HEIGHT);
956
+ }
957
+ // Update terminal bar
958
+ this.updateTerminalBar(session);
959
+ // Focus the new terminal
960
+ tmux.selectPane(newTerminalPaneId);
961
+ }
962
+ // Ensure sidebar stays at fixed width
963
+ this.enforceSidebarWidth();
964
+ this.render();
965
+ }
966
+ /**
967
+ * Switch to a different terminal tab within a session
968
+ */
969
+ switchTerminal(session, targetIndex) {
970
+ if (targetIndex < 0 || targetIndex >= session.terminals.length)
971
+ return;
972
+ if (targetIndex === session.activeTerminalIndex)
973
+ return;
974
+ debugLog('switchTerminal:', targetIndex, 'in session', session.title);
975
+ const currentTerminal = session.terminals[session.activeTerminalIndex];
976
+ const newTerminal = session.terminals[targetIndex];
977
+ // Break current terminal to background
978
+ tmux.breakPane(currentTerminal.paneId);
979
+ // Join new terminal below the bar pane
980
+ if (session.terminalBarPaneId) {
981
+ tmux.joinPane(newTerminal.paneId, session.terminalBarPaneId, false);
982
+ // Ensure terminal bar stays at 1 row
983
+ tmux.resizePane(session.terminalBarPaneId, undefined, TERMINAL_BAR_HEIGHT);
984
+ }
985
+ // Update active index
986
+ session.activeTerminalIndex = targetIndex;
987
+ // Update terminal bar
988
+ this.updateTerminalBar(session);
989
+ this.enforceSidebarWidth();
990
+ }
991
+ /**
992
+ * Delete a terminal at the given index
993
+ */
994
+ deleteTerminal(session, index) {
995
+ if (index < 0 || index >= session.terminals.length)
996
+ return;
997
+ debugLog('deleteTerminal:', index, 'in session', session.title);
998
+ const terminal = session.terminals[index];
999
+ const wasActive = index === session.activeTerminalIndex;
1000
+ // Kill the terminal pane
1001
+ tmux.killPane(terminal.paneId);
1002
+ // Remove from terminals array
1003
+ session.terminals.splice(index, 1);
1004
+ if (session.terminals.length === 0) {
1005
+ // No more terminals - kill terminal bar pane and remove resize hook
1006
+ if (session.terminalBarPaneId) {
1007
+ // Remove the after-resize-pane hook for this session
1008
+ try {
1009
+ tmux.removeHook(this.state.sessionName, 'after-resize-pane');
1010
+ }
1011
+ catch {
1012
+ // Hook may not exist, ignore
1013
+ }
1014
+ tmux.killPane(session.terminalBarPaneId);
1015
+ session.terminalBarPaneId = null;
1016
+ }
1017
+ session.activeTerminalIndex = 0;
1018
+ }
1019
+ else {
1020
+ // Adjust activeTerminalIndex
1021
+ if (index < session.activeTerminalIndex) {
1022
+ session.activeTerminalIndex--;
1023
+ }
1024
+ else if (session.activeTerminalIndex >= session.terminals.length) {
1025
+ session.activeTerminalIndex = session.terminals.length - 1;
1026
+ }
1027
+ // If deleted was the visible terminal, show the new active one
1028
+ if (wasActive) {
1029
+ const newActiveTerminal = session.terminals[session.activeTerminalIndex];
1030
+ if (newActiveTerminal && session.terminalBarPaneId) {
1031
+ tmux.joinPane(newActiveTerminal.paneId, session.terminalBarPaneId, false);
1032
+ tmux.resizePane(session.terminalBarPaneId, undefined, TERMINAL_BAR_HEIGHT);
1033
+ }
1034
+ }
1035
+ // Update terminal bar
1036
+ this.updateTerminalBar(session);
1037
+ }
1038
+ this.enforceSidebarWidth();
1039
+ this.render();
1040
+ }
1041
+ /**
1042
+ * Start the terminal bar handler in a session's bar pane
1043
+ */
1044
+ startTerminalBarHandler(session) {
1045
+ if (!session.terminalBarPaneId)
1046
+ return;
1047
+ // Build the command to run the bar handler
1048
+ // We pass the initial state as a JSON argument
1049
+ const initialState = JSON.stringify({
1050
+ terminals: session.terminals,
1051
+ activeIndex: session.activeTerminalIndex,
1052
+ });
1053
+ // Get path to bar-handler - check for .ts first (dev mode), then .js (compiled)
1054
+ const __filename = fileURLToPath(import.meta.url);
1055
+ const __dirname = dirname(__filename);
1056
+ // Check both src and dist locations
1057
+ const tsPath = resolve(__dirname, '../terminal/bar-handler.ts');
1058
+ const jsPath = resolve(__dirname, '../terminal/bar-handler.js');
1059
+ let cmd;
1060
+ const escapedState = initialState.replace(/'/g, "'\\''");
1061
+ const args = `"${this.state.sidebarPaneId}" "${session.id}" '${escapedState}'`;
1062
+ // Check which file exists
1063
+ if (existsSync(tsPath)) {
1064
+ cmd = `npx tsx "${tsPath}" ${args}`;
1065
+ }
1066
+ else if (existsSync(jsPath)) {
1067
+ cmd = `node "${jsPath}" ${args}`;
1068
+ }
1069
+ else {
1070
+ debugLog('startTerminalBarHandler: bar-handler not found at', tsPath, 'or', jsPath);
1071
+ return;
1072
+ }
1073
+ debugLog('startTerminalBarHandler:', cmd);
1074
+ tmux.sendKeys(session.terminalBarPaneId, cmd, true);
1075
+ }
1076
+ /**
1077
+ * Send updated state to the terminal bar handler
1078
+ */
1079
+ updateTerminalBar(session) {
1080
+ if (!session.terminalBarPaneId)
1081
+ return;
1082
+ const renderData = JSON.stringify({
1083
+ terminals: session.terminals,
1084
+ activeIndex: session.activeTerminalIndex,
1085
+ });
1086
+ // Send render command to bar handler
1087
+ tmux.sendKeys(session.terminalBarPaneId, `RENDER:${renderData}`, false);
1088
+ }
1089
+ // ==========================================================================
1090
+ // Fullscreen Modal Management
1091
+ // ==========================================================================
1092
+ /**
1093
+ * Enter fullscreen modal mode - hides all session panes so sidebar can expand
1094
+ */
1095
+ enterFullscreenModal() {
1096
+ if (this.state.fullscreenModal)
1097
+ return; // Already in fullscreen
1098
+ debugLog('enterFullscreenModal: hiding panes');
1099
+ if (this.state.activeSessionId) {
1100
+ // Hide the active session's panes (Claude pane + terminals)
1101
+ const activeSession = this.state.sessions.find(s => s.id === this.state.activeSessionId);
1102
+ if (activeSession) {
1103
+ try {
1104
+ // Break active terminal first (if any)
1105
+ if (activeSession.terminals.length > 0) {
1106
+ const activeTerminal = activeSession.terminals[activeSession.activeTerminalIndex];
1107
+ if (activeTerminal) {
1108
+ tmux.breakPane(activeTerminal.paneId);
1109
+ }
1110
+ // Break terminal bar
1111
+ if (activeSession.terminalBarPaneId) {
1112
+ tmux.breakPane(activeSession.terminalBarPaneId);
1113
+ }
1114
+ }
1115
+ // Break Claude pane
1116
+ tmux.breakPane(activeSession.paneId);
1117
+ this.state.hiddenPaneId = activeSession.paneId;
1118
+ debugLog('enterFullscreenModal: broke session panes');
1119
+ }
1120
+ catch (err) {
1121
+ debugLog('enterFullscreenModal: failed to break panes', err);
1122
+ }
1123
+ }
1124
+ }
1125
+ else {
1126
+ // Hide the main/welcome pane
1127
+ try {
1128
+ tmux.breakPane(this.state.mainPaneId);
1129
+ this.state.hiddenPaneId = this.state.mainPaneId;
1130
+ debugLog('enterFullscreenModal: broke main pane');
1131
+ }
1132
+ catch (err) {
1133
+ debugLog('enterFullscreenModal: failed to break main pane', err);
1134
+ }
1135
+ }
1136
+ this.state.fullscreenModal = true;
1137
+ }
1138
+ /**
1139
+ * Exit fullscreen modal mode - restores all session panes
1140
+ */
1141
+ exitFullscreenModal() {
1142
+ if (!this.state.fullscreenModal)
1143
+ return; // Not in fullscreen
1144
+ debugLog('exitFullscreenModal: restoring panes');
1145
+ if (this.state.hiddenPaneId) {
1146
+ try {
1147
+ // Join Claude pane
1148
+ tmux.joinPane(this.state.hiddenPaneId, this.state.sidebarPaneId, true);
1149
+ debugLog('exitFullscreenModal: joined Claude pane');
1150
+ // If active session has terminals, join those too
1151
+ const activeSession = this.state.sessions.find(s => s.id === this.state.activeSessionId);
1152
+ if (activeSession && activeSession.terminals.length > 0 && activeSession.terminalBarPaneId) {
1153
+ // Join terminal bar below Claude pane
1154
+ tmux.joinPane(activeSession.terminalBarPaneId, activeSession.paneId, false);
1155
+ // Join active terminal below terminal bar
1156
+ const activeTerminal = activeSession.terminals[activeSession.activeTerminalIndex];
1157
+ if (activeTerminal) {
1158
+ tmux.joinPane(activeTerminal.paneId, activeSession.terminalBarPaneId, false);
1159
+ }
1160
+ // Ensure terminal bar is exactly 1 row
1161
+ tmux.resizePane(activeSession.terminalBarPaneId, undefined, TERMINAL_BAR_HEIGHT);
1162
+ // Update terminal bar display
1163
+ this.updateTerminalBar(activeSession);
1164
+ debugLog('exitFullscreenModal: joined terminal panes');
1165
+ }
1166
+ this.enforceSidebarWidth();
1167
+ }
1168
+ catch (err) {
1169
+ debugLog('exitFullscreenModal: failed to join panes', err);
1170
+ }
1171
+ this.state.hiddenPaneId = null;
1172
+ }
1173
+ this.state.fullscreenModal = false;
1174
+ tmux.selectPane(this.state.sidebarPaneId);
1175
+ }
1176
+ // ==========================================================================
1177
+ // Helpers
1178
+ // ==========================================================================
1179
+ getMaxIndex() {
1180
+ return Math.max(0, this.getTotalItemCount() - 1);
1181
+ }
1182
+ getTotalItemCount() {
1183
+ let count = 0;
1184
+ for (const wt of this.state.worktrees) {
1185
+ count++; // worktree
1186
+ count += this.state.sessions.filter(s => s.worktreeId === wt.id).length;
1187
+ }
1188
+ return count;
1189
+ }
1190
+ getSelectedItem() {
1191
+ const items = buildListItems(this.state);
1192
+ const item = items[this.state.selectedIndex];
1193
+ if (!item)
1194
+ return null;
1195
+ return {
1196
+ type: item.type,
1197
+ id: item.id,
1198
+ worktree: item.worktree,
1199
+ session: item.session,
1200
+ };
1201
+ }
1202
+ }
1203
+ //# sourceMappingURL=app.js.map