commandmate 0.1.0 → 0.1.6

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 (337) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/app-build-manifest.json +72 -0
  3. package/.next/app-path-routes-manifest.json +1 -0
  4. package/.next/build-manifest.json +32 -0
  5. package/.next/cache/.tsbuildinfo +1 -0
  6. package/.next/cache/config.json +7 -0
  7. package/.next/cache/webpack/client-production/0.pack +0 -0
  8. package/.next/cache/webpack/client-production/1.pack +0 -0
  9. package/.next/cache/webpack/client-production/2.pack +0 -0
  10. package/.next/cache/webpack/client-production/index.pack +0 -0
  11. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  12. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  13. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  14. package/.next/cache/webpack/server-production/0.pack +0 -0
  15. package/.next/cache/webpack/server-production/index.pack +0 -0
  16. package/.next/export-marker.json +1 -0
  17. package/.next/images-manifest.json +1 -0
  18. package/.next/next-minimal-server.js.nft.json +1 -0
  19. package/.next/next-server.js.nft.json +1 -0
  20. package/.next/package.json +1 -0
  21. package/.next/prerender-manifest.json +1 -0
  22. package/.next/react-loadable-manifest.json +249 -0
  23. package/.next/required-server-files.json +1 -0
  24. package/.next/routes-manifest.json +1 -0
  25. package/.next/server/app/_not-found/page.js +1 -0
  26. package/.next/server/app/_not-found/page.js.nft.json +1 -0
  27. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  28. package/.next/server/app/_not-found.html +1 -0
  29. package/.next/server/app/_not-found.meta +6 -0
  30. package/.next/server/app/_not-found.rsc +10 -0
  31. package/.next/server/app/api/external-apps/[id]/health/route.js +45 -0
  32. package/.next/server/app/api/external-apps/[id]/health/route.js.nft.json +1 -0
  33. package/.next/server/app/api/external-apps/[id]/route.js +45 -0
  34. package/.next/server/app/api/external-apps/[id]/route.js.nft.json +1 -0
  35. package/.next/server/app/api/external-apps/route.js +45 -0
  36. package/.next/server/app/api/external-apps/route.js.nft.json +1 -0
  37. package/.next/server/app/api/hooks/claude-done/route.js +19 -0
  38. package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -0
  39. package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -0
  40. package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -0
  41. package/.next/server/app/api/repositories/clone/route.js +1 -0
  42. package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -0
  43. package/.next/server/app/api/repositories/route.js +1 -0
  44. package/.next/server/app/api/repositories/route.js.nft.json +1 -0
  45. package/.next/server/app/api/repositories/scan/route.js +1 -0
  46. package/.next/server/app/api/repositories/scan/route.js.nft.json +1 -0
  47. package/.next/server/app/api/repositories/sync/route.js +1 -0
  48. package/.next/server/app/api/repositories/sync/route.js.nft.json +1 -0
  49. package/.next/server/app/api/slash-commands/route.js +1 -0
  50. package/.next/server/app/api/slash-commands/route.js.nft.json +1 -0
  51. package/.next/server/app/api/slash-commands.body +1 -0
  52. package/.next/server/app/api/slash-commands.meta +1 -0
  53. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -0
  54. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -0
  55. package/.next/server/app/api/worktrees/[id]/capture/route.js +2 -0
  56. package/.next/server/app/api/worktrees/[id]/capture/route.js.nft.json +1 -0
  57. package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -0
  58. package/.next/server/app/api/worktrees/[id]/cli-tool/route.js.nft.json +1 -0
  59. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -0
  60. package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -0
  61. package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js +1 -0
  62. package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js.nft.json +1 -0
  63. package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -0
  64. package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -0
  65. package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -0
  66. package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -0
  67. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -0
  68. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js.nft.json +1 -0
  69. package/.next/server/app/api/worktrees/[id]/logs/route.js +19 -0
  70. package/.next/server/app/api/worktrees/[id]/logs/route.js.nft.json +1 -0
  71. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js +1 -0
  72. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js.nft.json +1 -0
  73. package/.next/server/app/api/worktrees/[id]/memos/route.js +1 -0
  74. package/.next/server/app/api/worktrees/[id]/memos/route.js.nft.json +1 -0
  75. package/.next/server/app/api/worktrees/[id]/messages/route.js +1 -0
  76. package/.next/server/app/api/worktrees/[id]/messages/route.js.nft.json +1 -0
  77. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -0
  78. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -0
  79. package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -0
  80. package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -0
  81. package/.next/server/app/api/worktrees/[id]/route.js +1 -0
  82. package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -0
  83. package/.next/server/app/api/worktrees/[id]/search/route.js +1 -0
  84. package/.next/server/app/api/worktrees/[id]/search/route.js.nft.json +1 -0
  85. package/.next/server/app/api/worktrees/[id]/send/route.js +1 -0
  86. package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -0
  87. package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -0
  88. package/.next/server/app/api/worktrees/[id]/slash-commands/route.js.nft.json +1 -0
  89. package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -0
  90. package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -0
  91. package/.next/server/app/api/worktrees/[id]/terminal/route.js +1 -0
  92. package/.next/server/app/api/worktrees/[id]/terminal/route.js.nft.json +1 -0
  93. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js +1 -0
  94. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js.nft.json +1 -0
  95. package/.next/server/app/api/worktrees/[id]/tree/route.js +1 -0
  96. package/.next/server/app/api/worktrees/[id]/tree/route.js.nft.json +1 -0
  97. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js +1 -0
  98. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js.nft.json +1 -0
  99. package/.next/server/app/api/worktrees/[id]/viewed/route.js +1 -0
  100. package/.next/server/app/api/worktrees/[id]/viewed/route.js.nft.json +1 -0
  101. package/.next/server/app/api/worktrees/route.js +1 -0
  102. package/.next/server/app/api/worktrees/route.js.nft.json +1 -0
  103. package/.next/server/app/apple-icon.png/route.js +1 -0
  104. package/.next/server/app/apple-icon.png/route.js.nft.json +1 -0
  105. package/.next/server/app/apple-icon.png.body +0 -0
  106. package/.next/server/app/apple-icon.png.meta +1 -0
  107. package/.next/server/app/icon.png/route.js +1 -0
  108. package/.next/server/app/icon.png/route.js.nft.json +1 -0
  109. package/.next/server/app/icon.png.body +0 -0
  110. package/.next/server/app/icon.png.meta +1 -0
  111. package/.next/server/app/index.html +9 -0
  112. package/.next/server/app/index.meta +5 -0
  113. package/.next/server/app/index.rsc +8 -0
  114. package/.next/server/app/page.js +16 -0
  115. package/.next/server/app/page.js.nft.json +1 -0
  116. package/.next/server/app/page_client-reference-manifest.js +1 -0
  117. package/.next/server/app/proxy/[...path]/route.js +45 -0
  118. package/.next/server/app/proxy/[...path]/route.js.nft.json +1 -0
  119. package/.next/server/app/worktrees/[id]/files/[...path]/page.js +1 -0
  120. package/.next/server/app/worktrees/[id]/files/[...path]/page.js.nft.json +1 -0
  121. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -0
  122. package/.next/server/app/worktrees/[id]/page.js +21 -0
  123. package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -0
  124. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -0
  125. package/.next/server/app/worktrees/[id]/simple-terminal/page.js +4 -0
  126. package/.next/server/app/worktrees/[id]/simple-terminal/page.js.nft.json +1 -0
  127. package/.next/server/app/worktrees/[id]/simple-terminal/page_client-reference-manifest.js +1 -0
  128. package/.next/server/app/worktrees/[id]/terminal/page.js +6 -0
  129. package/.next/server/app/worktrees/[id]/terminal/page.js.nft.json +1 -0
  130. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -0
  131. package/.next/server/app-paths-manifest.json +46 -0
  132. package/.next/server/chunks/1318.js +29 -0
  133. package/.next/server/chunks/1528.js +1 -0
  134. package/.next/server/chunks/1682.js +6 -0
  135. package/.next/server/chunks/2518.js +12 -0
  136. package/.next/server/chunks/3053.js +1 -0
  137. package/.next/server/chunks/3673.js +1 -0
  138. package/.next/server/chunks/3853.js +1 -0
  139. package/.next/server/chunks/434.js +1 -0
  140. package/.next/server/chunks/4471.js +2 -0
  141. package/.next/server/chunks/4893.js +2 -0
  142. package/.next/server/chunks/5972.js +12 -0
  143. package/.next/server/chunks/6550.js +1 -0
  144. package/.next/server/chunks/6621.js +1 -0
  145. package/.next/server/chunks/7213.js +1 -0
  146. package/.next/server/chunks/7425.js +500 -0
  147. package/.next/server/chunks/8585.js +1 -0
  148. package/.next/server/chunks/8887.js +1 -0
  149. package/.next/server/chunks/8948.js +2 -0
  150. package/.next/server/chunks/9703.js +31 -0
  151. package/.next/server/chunks/9723.js +19 -0
  152. package/.next/server/chunks/font-manifest.json +1 -0
  153. package/.next/server/edge-runtime-webpack.js +2 -0
  154. package/.next/server/edge-runtime-webpack.js.map +1 -0
  155. package/.next/server/font-manifest.json +1 -0
  156. package/.next/server/functions-config-manifest.json +1 -0
  157. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  158. package/.next/server/middleware-build-manifest.js +1 -0
  159. package/.next/server/middleware-manifest.json +32 -0
  160. package/.next/server/middleware-react-loadable-manifest.js +1 -0
  161. package/.next/server/next-font-manifest.js +1 -0
  162. package/.next/server/next-font-manifest.json +1 -0
  163. package/.next/server/pages/404.html +1 -0
  164. package/.next/server/pages/500.html +1 -0
  165. package/.next/server/pages/_app.js +1 -0
  166. package/.next/server/pages/_app.js.nft.json +1 -0
  167. package/.next/server/pages/_document.js +1 -0
  168. package/.next/server/pages/_document.js.nft.json +1 -0
  169. package/.next/server/pages/_error.js +1 -0
  170. package/.next/server/pages/_error.js.nft.json +1 -0
  171. package/.next/server/pages-manifest.json +1 -0
  172. package/.next/server/server-reference-manifest.js +1 -0
  173. package/.next/server/server-reference-manifest.json +1 -0
  174. package/.next/server/src/middleware.js +14 -0
  175. package/.next/server/src/middleware.js.map +1 -0
  176. package/.next/server/webpack-runtime.js +1 -0
  177. package/.next/static/chunks/0dbeb660.3e800dfbd28be3bd.js +53 -0
  178. package/.next/static/chunks/1015.0eaa4da7f61149bc.js +59 -0
  179. package/.next/static/chunks/1098.49268c9fe1b028fa.js +1 -0
  180. package/.next/static/chunks/13.feeafc7cc620f8c4.js +1 -0
  181. package/.next/static/chunks/1423.7b1e8bf760d28078.js +1 -0
  182. package/.next/static/chunks/1582.9f8590f71ff798ca.js +55 -0
  183. package/.next/static/chunks/1817.a66d96cedb761daa.js +262 -0
  184. package/.next/static/chunks/2117-d845c2cd62e344a6.js +2 -0
  185. package/.next/static/chunks/2398.0b21e4eb7006a230.js +93 -0
  186. package/.next/static/chunks/2526.8ac62b527c9ab703.js +43 -0
  187. package/.next/static/chunks/2626.2125083a1ff3b80a.js +29 -0
  188. package/.next/static/chunks/2689.720a4874b02d4211.js +174 -0
  189. package/.next/static/chunks/2853-d11a80b03c9a1640.js +1 -0
  190. package/.next/static/chunks/2957-327e43ef4c12808f.js +1 -0
  191. package/.next/static/chunks/2cdb6380.35626fc6e41bbba4.js +136 -0
  192. package/.next/static/chunks/30d07d85-393352a92199f695.js +3 -0
  193. package/.next/static/chunks/3559.f073f72c4466ce0e.js +1 -0
  194. package/.next/static/chunks/3574.7a94c27e6a496a56.js +63 -0
  195. package/.next/static/chunks/383.20683891c9a5f2c4.js +4 -0
  196. package/.next/static/chunks/3843.3fdda732987f7bb8.js +1 -0
  197. package/.next/static/chunks/3852.822389f445c9b427.js +1 -0
  198. package/.next/static/chunks/3991.4bc063cb5be3a86c.js +1 -0
  199. package/.next/static/chunks/4212.52c1bb34fc97d0d0.js +131 -0
  200. package/.next/static/chunks/4327.3b84aa049900fdeb.js +60 -0
  201. package/.next/static/chunks/4362.7bd6f0282e49d79b.js +1 -0
  202. package/.next/static/chunks/4721.40615a5f4f32b5fb.js +1 -0
  203. package/.next/static/chunks/4851-45df4d388db5623f.js +1 -0
  204. package/.next/static/chunks/5112.17318d1c6b28044b.js +1 -0
  205. package/.next/static/chunks/5126.93fa4e797d609286.js +56 -0
  206. package/.next/static/chunks/5387.47590ac4ef66c864.js +5 -0
  207. package/.next/static/chunks/5813.4483664ba482beb1.js +1 -0
  208. package/.next/static/chunks/6143.1450875bd03a2366.js +36 -0
  209. package/.next/static/chunks/6406.9653f0d41ab85059.js +1 -0
  210. package/.next/static/chunks/656.d72f25ce819bd77e.js +149 -0
  211. package/.next/static/chunks/6678.492e73ca42b2a273.js +62 -0
  212. package/.next/static/chunks/6725-f7607851b7d57eb1.js +1 -0
  213. package/.next/static/chunks/6792.3c01ac4dda4b5c6d.js +1 -0
  214. package/.next/static/chunks/7004.808cbf327ef5955e.js +1 -0
  215. package/.next/static/chunks/7290.09ef84cf94f90c4d.js +1 -0
  216. package/.next/static/chunks/7415.6b481c2baf363262.js +148 -0
  217. package/.next/static/chunks/7648-325564a6e12a3257.js +1 -0
  218. package/.next/static/chunks/7665.47fccad04449a8f9.js +215 -0
  219. package/.next/static/chunks/7753.6bdce86b7fde3d10.js +166 -0
  220. package/.next/static/chunks/8125.245a9df052d274fb.js +1 -0
  221. package/.next/static/chunks/816-7e340dad784be28c.js +1 -0
  222. package/.next/static/chunks/8288.4883743fa40672e2.js +24 -0
  223. package/.next/static/chunks/8522.1607e96011c66877.js +1 -0
  224. package/.next/static/chunks/8772.863c564498d88487.js +1 -0
  225. package/.next/static/chunks/8841.dadeb1ece8e46004.js +1 -0
  226. package/.next/static/chunks/8885.f8d9912b40d74811.js +1 -0
  227. package/.next/static/chunks/90542734.c1553d0fe7fc14fc.js +1 -0
  228. package/.next/static/chunks/9365-733d8c05712d2888.js +1 -0
  229. package/.next/static/chunks/9552.b7dfb7903ead934b.js +1 -0
  230. package/.next/static/chunks/9834.295b45635ce04f5e.js +24 -0
  231. package/.next/static/chunks/app/_not-found/page-a9d04e58c81115ec.js +1 -0
  232. package/.next/static/chunks/app/layout-37e55f11dcc8b1bf.js +1 -0
  233. package/.next/static/chunks/app/page-9cd00de9cc0abc43.js +1 -0
  234. package/.next/static/chunks/app/worktrees/[id]/files/[...path]/page-9e5adf57cbbbdf05.js +1 -0
  235. package/.next/static/chunks/app/worktrees/[id]/page-8c6676303b63fdaf.js +1 -0
  236. package/.next/static/chunks/app/worktrees/[id]/simple-terminal/page-16feb3e86e42f4d1.js +1 -0
  237. package/.next/static/chunks/app/worktrees/[id]/terminal/page-be802baffc84dbd2.js +1 -0
  238. package/.next/static/chunks/d3ac728e.6c9c508274d4d2d5.js +1 -0
  239. package/.next/static/chunks/fd9d1056-bbe86e4ae099d5cd.js +1 -0
  240. package/.next/static/chunks/framework-8e0e0f4a6b83a956.js +1 -0
  241. package/.next/static/chunks/main-a960f4a5e1a2f598.js +1 -0
  242. package/.next/static/chunks/main-app-420d93e43682fee5.js +1 -0
  243. package/.next/static/chunks/pages/_app-3c9ca398d360b709.js +1 -0
  244. package/.next/static/chunks/pages/_error-cf5ca766ac8f493f.js +1 -0
  245. package/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  246. package/.next/static/chunks/webpack-3fc79fab9bb738d7.js +1 -0
  247. package/.next/static/css/5eacd01f773eed7f.css +11 -0
  248. package/.next/static/css/85fa6dafca566008.css +1 -0
  249. package/.next/static/css/e174aa24f94ce607.css +3 -0
  250. package/.next/static/pQTquVjewvoJa7BML07ip/_buildManifest.js +1 -0
  251. package/.next/static/pQTquVjewvoJa7BML07ip/_ssgManifest.js +1 -0
  252. package/.next/trace +5 -0
  253. package/.next/types/app/api/external-apps/[id]/health/route.ts +343 -0
  254. package/.next/types/app/api/external-apps/[id]/route.ts +343 -0
  255. package/.next/types/app/api/external-apps/route.ts +343 -0
  256. package/.next/types/app/api/hooks/claude-done/route.ts +343 -0
  257. package/.next/types/app/api/repositories/clone/[jobId]/route.ts +343 -0
  258. package/.next/types/app/api/repositories/clone/route.ts +343 -0
  259. package/.next/types/app/api/repositories/route.ts +343 -0
  260. package/.next/types/app/api/repositories/scan/route.ts +343 -0
  261. package/.next/types/app/api/repositories/sync/route.ts +343 -0
  262. package/.next/types/app/api/slash-commands/route.ts +343 -0
  263. package/.next/types/app/api/worktrees/[id]/auto-yes/route.ts +343 -0
  264. package/.next/types/app/api/worktrees/[id]/capture/route.ts +343 -0
  265. package/.next/types/app/api/worktrees/[id]/cli-tool/route.ts +343 -0
  266. package/.next/types/app/api/worktrees/[id]/current-output/route.ts +343 -0
  267. package/.next/types/app/api/worktrees/[id]/files/[...path]/route.ts +343 -0
  268. package/.next/types/app/api/worktrees/[id]/interrupt/route.ts +343 -0
  269. package/.next/types/app/api/worktrees/[id]/kill-session/route.ts +343 -0
  270. package/.next/types/app/api/worktrees/[id]/logs/[filename]/route.ts +343 -0
  271. package/.next/types/app/api/worktrees/[id]/logs/route.ts +343 -0
  272. package/.next/types/app/api/worktrees/[id]/memos/[memoId]/route.ts +343 -0
  273. package/.next/types/app/api/worktrees/[id]/memos/route.ts +343 -0
  274. package/.next/types/app/api/worktrees/[id]/messages/route.ts +343 -0
  275. package/.next/types/app/api/worktrees/[id]/prompt-response/route.ts +343 -0
  276. package/.next/types/app/api/worktrees/[id]/respond/route.ts +343 -0
  277. package/.next/types/app/api/worktrees/[id]/route.ts +343 -0
  278. package/.next/types/app/api/worktrees/[id]/search/route.ts +343 -0
  279. package/.next/types/app/api/worktrees/[id]/send/route.ts +343 -0
  280. package/.next/types/app/api/worktrees/[id]/slash-commands/route.ts +343 -0
  281. package/.next/types/app/api/worktrees/[id]/start-polling/route.ts +343 -0
  282. package/.next/types/app/api/worktrees/[id]/terminal/route.ts +343 -0
  283. package/.next/types/app/api/worktrees/[id]/tree/[...path]/route.ts +343 -0
  284. package/.next/types/app/api/worktrees/[id]/tree/route.ts +343 -0
  285. package/.next/types/app/api/worktrees/[id]/upload/[...path]/route.ts +343 -0
  286. package/.next/types/app/api/worktrees/[id]/viewed/route.ts +343 -0
  287. package/.next/types/app/api/worktrees/route.ts +343 -0
  288. package/.next/types/app/page.ts +79 -0
  289. package/.next/types/app/proxy/[...path]/route.ts +343 -0
  290. package/.next/types/app/worktrees/[id]/files/[...path]/page.ts +79 -0
  291. package/.next/types/app/worktrees/[id]/page.ts +79 -0
  292. package/.next/types/app/worktrees/[id]/simple-terminal/page.ts +79 -0
  293. package/.next/types/app/worktrees/[id]/terminal/page.ts +79 -0
  294. package/.next/types/package.json +1 -0
  295. package/README.md +25 -20
  296. package/dist/cli/commands/start.d.ts.map +1 -1
  297. package/dist/cli/commands/start.js +4 -1
  298. package/dist/cli/utils/daemon.d.ts.map +1 -1
  299. package/dist/cli/utils/daemon.js +4 -2
  300. package/dist/cli/utils/paths.d.ts +25 -0
  301. package/dist/cli/utils/paths.d.ts.map +1 -0
  302. package/dist/cli/utils/paths.js +35 -0
  303. package/dist/server/server.js +123 -0
  304. package/dist/server/src/lib/claude-output.js +33 -0
  305. package/dist/server/src/lib/claude-session.js +312 -0
  306. package/dist/server/src/lib/cli-patterns.js +137 -0
  307. package/dist/server/src/lib/cli-session.js +73 -0
  308. package/dist/server/src/lib/cli-tools/base.js +51 -0
  309. package/dist/server/src/lib/cli-tools/claude.js +65 -0
  310. package/dist/server/src/lib/cli-tools/codex.js +132 -0
  311. package/dist/server/src/lib/cli-tools/gemini.js +122 -0
  312. package/dist/server/src/lib/cli-tools/index.js +22 -0
  313. package/dist/server/src/lib/cli-tools/manager.js +143 -0
  314. package/dist/server/src/lib/cli-tools/types.js +5 -0
  315. package/dist/server/src/lib/conversation-logger.js +25 -0
  316. package/dist/server/src/lib/db-instance.js +51 -0
  317. package/dist/server/src/lib/db-migrations.js +777 -0
  318. package/dist/server/src/lib/db.js +835 -0
  319. package/dist/server/src/lib/env.js +179 -0
  320. package/dist/server/src/lib/log-manager.js +234 -0
  321. package/dist/server/src/lib/logger.js +232 -0
  322. package/dist/server/src/lib/prompt-detector.js +285 -0
  323. package/dist/server/src/lib/response-poller.js +638 -0
  324. package/dist/server/src/lib/tmux.js +299 -0
  325. package/dist/server/src/lib/worktrees.js +231 -0
  326. package/dist/server/src/lib/ws-server.js +323 -0
  327. package/dist/server/src/types/clone.js +39 -0
  328. package/dist/server/src/types/conversation.js +9 -0
  329. package/dist/server/src/types/external-apps.js +6 -0
  330. package/dist/server/src/types/infinite-messages.js +65 -0
  331. package/dist/server/src/types/markdown-editor.js +94 -0
  332. package/dist/server/src/types/models.js +5 -0
  333. package/dist/server/src/types/sidebar.js +89 -0
  334. package/dist/server/src/types/slash-commands.js +47 -0
  335. package/dist/server/src/types/ui-actions.js +8 -0
  336. package/dist/server/src/types/ui-state.js +62 -0
  337. package/package.json +14 -6
@@ -0,0 +1,638 @@
1
+ "use strict";
2
+ /**
3
+ * CLI Tool response polling
4
+ * Periodically checks tmux sessions for CLI tool responses (Claude, Codex, Gemini)
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.cleanClaudeResponse = cleanClaudeResponse;
8
+ exports.cleanGeminiResponse = cleanGeminiResponse;
9
+ exports.startPolling = startPolling;
10
+ exports.stopPolling = stopPolling;
11
+ exports.stopAllPolling = stopAllPolling;
12
+ exports.getActivePollers = getActivePollers;
13
+ const cli_session_1 = require("./cli-session");
14
+ const db_instance_1 = require("./db-instance");
15
+ const db_1 = require("./db");
16
+ const ws_server_1 = require("./ws-server");
17
+ const prompt_detector_1 = require("./prompt-detector");
18
+ const conversation_logger_1 = require("./conversation-logger");
19
+ const claude_output_1 = require("./claude-output");
20
+ const cli_patterns_1 = require("./cli-patterns");
21
+ /**
22
+ * Polling interval in milliseconds (default: 2 seconds)
23
+ */
24
+ const POLLING_INTERVAL = 2000;
25
+ /**
26
+ * Maximum polling duration in milliseconds (default: 5 minutes)
27
+ */
28
+ const MAX_POLLING_DURATION = 5 * 60 * 1000;
29
+ /**
30
+ * Active pollers map: "worktreeId:cliToolId" -> NodeJS.Timeout
31
+ */
32
+ const activePollers = new Map();
33
+ /**
34
+ * Polling start times map: "worktreeId:cliToolId" -> timestamp
35
+ */
36
+ const pollingStartTimes = new Map();
37
+ /**
38
+ * Generate poller key from worktree ID and CLI tool ID
39
+ */
40
+ function getPollerKey(worktreeId, cliToolId) {
41
+ return `${worktreeId}:${cliToolId}`;
42
+ }
43
+ /**
44
+ * Clean up Claude response by removing shell setup commands, environment exports, ANSI codes, and banner
45
+ * Also extracts only the LATEST response to avoid including conversation history
46
+ *
47
+ * @param response - Raw Claude response
48
+ * @returns Cleaned response (only the latest response)
49
+ */
50
+ function cleanClaudeResponse(response) {
51
+ // First, strip ANSI escape codes
52
+ const cleanedResponse = (0, cli_patterns_1.stripAnsi)(response);
53
+ // Find the LAST user prompt (❯ followed by content) and extract only the response after it
54
+ // This ensures we only get the latest response, not the entire conversation history
55
+ const lines = cleanedResponse.split('\n');
56
+ // Find the last user prompt line index
57
+ let lastUserPromptIndex = -1;
58
+ for (let i = lines.length - 1; i >= 0; i--) {
59
+ // User prompt line: ❯ followed by actual content (not empty ❯)
60
+ if (/^❯\s+\S/.test(lines[i])) {
61
+ lastUserPromptIndex = i;
62
+ break;
63
+ }
64
+ }
65
+ // Extract lines after the last user prompt
66
+ const startIndex = lastUserPromptIndex >= 0 ? lastUserPromptIndex + 1 : 0;
67
+ const responseLines = lines.slice(startIndex);
68
+ // Patterns to remove (Claude-specific setup commands and UI elements)
69
+ // IMPORTANT: These patterns should NOT match legitimate Claude response content
70
+ // Lines starting with ⏺ (Claude output marker) are typically valid content
71
+ const skipPatterns = [
72
+ /CLAUDE_HOOKS_/, // Any CLAUDE_HOOKS reference
73
+ /\/bin\/claude/, // Claude binary path (any variant)
74
+ /^claude\s*$/, // Just "claude" on a line
75
+ /@.*\s+%\s*$/, // Shell prompt (any user@host followed by % at end of line)
76
+ /^[^⏺]*curl.*POST/, // Curl POST commands (not starting with ⏺)
77
+ /^[^⏺]*Content-Type/, // HTTP headers (not in Claude output)
78
+ /^[^⏺]*export\s+CLAUDE_/, // Claude environment exports only
79
+ /^\s*$/, // Empty lines
80
+ // Claude Code banner patterns (only match pure banner elements)
81
+ /^[╭╮╰╯│─\s]+$/, // Box drawing characters only (with spaces)
82
+ /^[│╭╮╰╯].*[│╭╮╰╯]$/, // Lines with box drawing on both sides (banner rows)
83
+ /Claude Code v[\d.]+/, // Version info
84
+ /^Tips for getting started/, // Tips header (at line start)
85
+ /^Welcome back/, // Welcome message (at line start)
86
+ /Run \/init to create/, // Init instruction
87
+ /^Recent activity/, // Activity header (at line start)
88
+ /^No recent activity/, // No activity message (at line start)
89
+ /▐▛███▜▌|▝▜█████▛▘|▘▘ ▝▝/, // ASCII art logo
90
+ /^\s*Opus \d+\.\d+\s*·\s*Claude Max/, // Model info in banner format
91
+ /\.com's Organization/, // Organization info
92
+ /\?\s*for shortcuts\s*$/, // Shortcuts hint at end of line
93
+ /^─{10,}$/, // Separator lines
94
+ /^❯\s*$/, // Empty prompt lines
95
+ ];
96
+ // Filter out UI elements and keep only the response content
97
+ const cleanedLines = [];
98
+ for (const line of responseLines) {
99
+ const shouldSkip = skipPatterns.some(pattern => pattern.test(line));
100
+ if (!shouldSkip && line.trim()) {
101
+ cleanedLines.push(line);
102
+ }
103
+ }
104
+ // Return cleaned content
105
+ return cleanedLines.join('\n').trim();
106
+ }
107
+ /**
108
+ * Clean up Gemini response by removing shell prompts and error messages
109
+ *
110
+ * @param response - Raw Gemini response
111
+ * @returns Cleaned response
112
+ */
113
+ function cleanGeminiResponse(response) {
114
+ // Split response into lines
115
+ const lines = response.split('\n');
116
+ const cleanedLines = [];
117
+ // Patterns to remove
118
+ const skipPatterns = [
119
+ /^maenokota@.*%/, // Shell prompt
120
+ /^zsh:/, // Shell error messages
121
+ /^feature-issue-\d+/, // Worktree indicator
122
+ /^\s*$/, // Empty lines at start
123
+ ];
124
+ // Find the ✦ marker (actual Gemini response start)
125
+ let foundMarker = false;
126
+ const afterMarker = [];
127
+ for (const line of lines) {
128
+ if (line.includes('✦')) {
129
+ foundMarker = true;
130
+ // Extract content after ✦ marker
131
+ const markerIndex = line.indexOf('✦');
132
+ const afterMarkerContent = line.substring(markerIndex + 1).trim();
133
+ if (afterMarkerContent) {
134
+ afterMarker.push(afterMarkerContent);
135
+ }
136
+ continue;
137
+ }
138
+ if (foundMarker) {
139
+ // Skip shell prompts and errors after ✦ marker
140
+ if (skipPatterns.some(pattern => pattern.test(line))) {
141
+ continue;
142
+ }
143
+ afterMarker.push(line);
144
+ }
145
+ }
146
+ // If we found content after ✦, use only that
147
+ if (afterMarker.length > 0) {
148
+ return afterMarker.join('\n').trim();
149
+ }
150
+ // Otherwise, filter the original response
151
+ for (const line of lines) {
152
+ if (skipPatterns.some(pattern => pattern.test(line))) {
153
+ continue;
154
+ }
155
+ cleanedLines.push(line);
156
+ }
157
+ return cleanedLines.join('\n').trim();
158
+ }
159
+ /**
160
+ * Extract CLI tool response from tmux output
161
+ * Detects when a CLI tool has completed a response by looking for tool-specific patterns
162
+ *
163
+ * @param output - Full tmux output
164
+ * @param lastCapturedLine - Number of lines previously captured
165
+ * @param cliToolId - CLI tool ID (claude, codex, gemini)
166
+ * @returns Extracted response or null if incomplete
167
+ */
168
+ function extractResponse(output, lastCapturedLine, cliToolId) {
169
+ // Trim trailing empty lines from the output before processing
170
+ // This prevents the "last 20 lines" from being all empty due to tmux buffer padding
171
+ const rawLines = output.split('\n');
172
+ let trimmedLength = rawLines.length;
173
+ while (trimmedLength > 0 && rawLines[trimmedLength - 1].trim() === '') {
174
+ trimmedLength--;
175
+ }
176
+ const lines = rawLines.slice(0, trimmedLength);
177
+ const totalLines = lines.length;
178
+ const BUFFER_RESET_TOLERANCE = 25;
179
+ const bufferShrank = totalLines > 0 && lastCapturedLine > BUFFER_RESET_TOLERANCE && (totalLines + BUFFER_RESET_TOLERANCE) < lastCapturedLine;
180
+ const sessionRestarted = totalLines > 0 && lastCapturedLine > 50 && totalLines < 50;
181
+ const bufferReset = bufferShrank || sessionRestarted;
182
+ // No new output (with buffer to handle newline inconsistencies)
183
+ // BUT: if totalLines is much smaller than lastCapturedLine, the buffer was likely reset (session restart)
184
+ // In that case, don't skip - proceed to check for completion
185
+ if (!bufferReset && totalLines < lastCapturedLine - 5) {
186
+ return null;
187
+ }
188
+ // Always check the last 20 lines for completion pattern (more robust than tracking line numbers)
189
+ const checkLineCount = 20;
190
+ const startLine = Math.max(0, totalLines - checkLineCount);
191
+ const linesToCheck = lines.slice(startLine);
192
+ const outputToCheck = linesToCheck.join('\n');
193
+ // Get tool-specific patterns from shared module
194
+ const { promptPattern, separatorPattern, thinkingPattern, skipPatterns } = (0, cli_patterns_1.getCliToolPatterns)(cliToolId);
195
+ const findRecentUserPromptIndex = (windowSize = 60) => {
196
+ // User prompt pattern: supports legacy '>' and new '❯' for Claude
197
+ const userPromptPattern = cliToolId === 'codex'
198
+ ? /^›\s+(?!Implement|Find and fix|Type|Summarize)/
199
+ : /^[>❯]\s+\S/;
200
+ for (let i = totalLines - 1; i >= Math.max(0, totalLines - windowSize); i--) {
201
+ const cleanLine = (0, cli_patterns_1.stripAnsi)(lines[i]);
202
+ if (userPromptPattern.test(cleanLine)) {
203
+ return i;
204
+ }
205
+ }
206
+ return -1;
207
+ };
208
+ // Early check for Claude permission prompts (before extraction logic)
209
+ // Permission prompts appear after normal responses and need special handling
210
+ if (cliToolId === 'claude') {
211
+ const fullOutput = lines.join('\n');
212
+ // Strip ANSI codes before prompt detection
213
+ const cleanFullOutput = (0, cli_patterns_1.stripAnsi)(fullOutput);
214
+ const promptDetection = (0, prompt_detector_1.detectPrompt)(cleanFullOutput);
215
+ if (promptDetection.isPrompt) {
216
+ // Return the full output as a complete interactive prompt
217
+ // Use the cleaned output without ANSI codes
218
+ return {
219
+ response: cleanFullOutput,
220
+ isComplete: true,
221
+ lineCount: totalLines,
222
+ };
223
+ }
224
+ }
225
+ // Strip ANSI codes before pattern matching
226
+ const cleanOutputToCheck = (0, cli_patterns_1.stripAnsi)(outputToCheck);
227
+ const hasPrompt = promptPattern.test(cleanOutputToCheck);
228
+ const hasSeparator = separatorPattern.test(cleanOutputToCheck);
229
+ const isThinking = thinkingPattern.test(cleanOutputToCheck);
230
+ // Codex/Gemini completion logic: prompt detected and not thinking (separator optional)
231
+ // - Codex: Interactive TUI, detects › prompt
232
+ // - Gemini: Non-interactive one-shot, detects shell prompt (%, $)
233
+ // Claude: require both prompt and separator
234
+ const isCodexOrGeminiComplete = (cliToolId === 'codex' || cliToolId === 'gemini') && hasPrompt && !isThinking;
235
+ const isClaudeComplete = cliToolId === 'claude' && hasPrompt && hasSeparator && !isThinking;
236
+ if (isCodexOrGeminiComplete || isClaudeComplete) {
237
+ // CLI tool has completed response
238
+ // Extract the response content from lastCapturedLine to the separator (not just last 20 lines)
239
+ const responseLines = [];
240
+ // Handle tmux buffer scrolling: if lastCapturedLine >= totalLines, the buffer has scrolled
241
+ // In this case, we need to find the response in the current visible buffer
242
+ let startIndex;
243
+ // For all tools: check if buffer has been reset/cleared (startIndex would be >= totalLines)
244
+ // This happens when a session is restarted or buffer is cleared
245
+ const bufferWasReset = lastCapturedLine >= totalLines || bufferReset;
246
+ if (bufferWasReset) {
247
+ // Buffer was reset - find the most recent user prompt
248
+ const foundUserPrompt = findRecentUserPromptIndex(40);
249
+ startIndex = foundUserPrompt >= 0 ? foundUserPrompt + 1 : 0;
250
+ }
251
+ else if (cliToolId === 'codex') {
252
+ // Normal case for Codex: use lastCapturedLine
253
+ startIndex = Math.max(0, lastCapturedLine);
254
+ }
255
+ else if (lastCapturedLine >= totalLines - 5) {
256
+ // Buffer may have scrolled - look for the start of the new response
257
+ // Find the last user input prompt to identify where the response starts
258
+ const foundUserPrompt = findRecentUserPromptIndex(50);
259
+ // Start extraction from after the user prompt, or from a safe earlier point
260
+ startIndex = foundUserPrompt >= 0 ? foundUserPrompt + 1 : Math.max(0, totalLines - 40);
261
+ }
262
+ else {
263
+ // Normal case: start from lastCapturedLine
264
+ startIndex = Math.max(0, lastCapturedLine);
265
+ }
266
+ let endIndex = totalLines; // Track where extraction actually ended
267
+ for (let i = startIndex; i < totalLines; i++) {
268
+ const line = lines[i];
269
+ const cleanLine = (0, cli_patterns_1.stripAnsi)(line);
270
+ // For Codex: stop at any prompt line (which indicates end of response OR we're already past it)
271
+ if (cliToolId === 'codex' && /^›\s+/.test(cleanLine)) {
272
+ endIndex = i;
273
+ break;
274
+ }
275
+ // For Gemini: stop at shell prompt (indicates command completion)
276
+ if (cliToolId === 'gemini' && /^(%|\$|.*@.*[%$#])\s*$/.test(cleanLine)) {
277
+ endIndex = i;
278
+ break;
279
+ }
280
+ // Skip lines matching any skip pattern (check against clean line)
281
+ const shouldSkip = skipPatterns.some(pattern => pattern.test(cleanLine));
282
+ if (shouldSkip) {
283
+ continue;
284
+ }
285
+ responseLines.push(line);
286
+ }
287
+ const response = responseLines.join('\n').trim();
288
+ // Additional check: ensure response doesn't contain thinking indicators
289
+ // This prevents saving intermediate states as final responses
290
+ if (thinkingPattern.test(response)) {
291
+ return {
292
+ response: '',
293
+ isComplete: false,
294
+ lineCount: totalLines,
295
+ };
296
+ }
297
+ // CRITICAL FIX: Detect and skip Claude Code startup banner/screen
298
+ // The startup screen contains: ASCII art logo, version info, prompt, separator
299
+ // But no actual response content from the user's query
300
+ if (cliToolId === 'claude') {
301
+ const cleanResponse = (0, cli_patterns_1.stripAnsi)(response);
302
+ // Check for Claude Code banner patterns
303
+ const hasBannerArt = /[╭╮╰╯│]/.test(cleanResponse) || /░{3,}/.test(cleanResponse) || /▓{3,}/.test(cleanResponse);
304
+ const hasVersionInfo = /Claude Code|claude\/|v\d+\.\d+/.test(cleanResponse);
305
+ const hasStartupTips = /Tip:|for shortcuts|\?\s*for help/.test(cleanResponse);
306
+ const hasProjectInit = /^\s*\/Users\/.*$/m.test(cleanResponse) && cleanResponse.split('\n').length < 30;
307
+ // Check if this looks like just a startup screen (no actual response content)
308
+ // A real response would have content AFTER the user's prompt line (> or ❯ message)
309
+ const userPromptMatch = cleanResponse.match(/^[>❯]\s+(\S.*)$/m);
310
+ if (userPromptMatch) {
311
+ // Found user prompt - check if there's actual response content after it
312
+ const userPromptIndex = cleanResponse.indexOf(userPromptMatch[0]);
313
+ const contentAfterPrompt = cleanResponse.substring(userPromptIndex + userPromptMatch[0].length).trim();
314
+ // Filter out just separators, tips, and empty lines
315
+ const contentLines = contentAfterPrompt.split('\n').filter(line => {
316
+ const trimmed = line.trim();
317
+ return trimmed &&
318
+ !skipPatterns.some(p => p.test(trimmed)) &&
319
+ !/^─+$/.test(trimmed);
320
+ });
321
+ if (contentLines.length === 0) {
322
+ return {
323
+ response: '',
324
+ isComplete: false,
325
+ lineCount: totalLines,
326
+ };
327
+ }
328
+ }
329
+ else if ((hasBannerArt || hasVersionInfo || hasStartupTips || hasProjectInit) && response.length < 2000) {
330
+ // No user prompt found, but has banner characteristics - likely initial startup
331
+ return {
332
+ response: '',
333
+ isComplete: false,
334
+ lineCount: totalLines,
335
+ };
336
+ }
337
+ }
338
+ // Gemini-specific check: ensure response contains actual content (✦ marker)
339
+ if (cliToolId === 'gemini') {
340
+ // Check for banner/UI characters (banner should be filtered by skipPatterns, but double-check)
341
+ const bannerCharCount = (response.match(/[░███]/g) || []).length;
342
+ const totalChars = response.length;
343
+ if (bannerCharCount > totalChars * 0.3) {
344
+ return {
345
+ response: '',
346
+ isComplete: false,
347
+ lineCount: totalLines,
348
+ };
349
+ }
350
+ // Check for auth/loading states that should not be treated as complete responses
351
+ if (response.includes('Waiting for auth') ||
352
+ response.includes('⠋') ||
353
+ response.includes('⠙') ||
354
+ response.includes('⠹') ||
355
+ response.includes('⠸') ||
356
+ response.includes('⠼') ||
357
+ response.includes('⠴') ||
358
+ response.includes('⠦') ||
359
+ response.includes('⠧') ||
360
+ response.includes('⠇') ||
361
+ response.includes('⠏')) {
362
+ return {
363
+ response: '',
364
+ isComplete: false,
365
+ lineCount: totalLines,
366
+ };
367
+ }
368
+ if (!response.includes('✦') && response.length < 10) {
369
+ return {
370
+ response: '',
371
+ isComplete: false,
372
+ lineCount: totalLines,
373
+ };
374
+ }
375
+ }
376
+ return {
377
+ response,
378
+ isComplete: true,
379
+ lineCount: endIndex, // Use endIndex instead of totalLines to track where we actually stopped
380
+ };
381
+ }
382
+ // Check if this is an interactive prompt (yes/no or multiple choice)
383
+ // Interactive prompts don't have the ">" prompt and separator, so we need to detect them separately
384
+ const fullOutput = lines.join('\n');
385
+ const promptDetection = (0, prompt_detector_1.detectPrompt)(fullOutput);
386
+ if (promptDetection.isPrompt) {
387
+ // This is an interactive prompt - consider it complete
388
+ return {
389
+ response: fullOutput,
390
+ isComplete: true,
391
+ lineCount: totalLines,
392
+ };
393
+ }
394
+ // Not a prompt, but we may have a partial response in progress (even if Claude shows a spinner)
395
+ const responseLines = [];
396
+ const endIndex = totalLines;
397
+ const partialBufferReset = bufferReset || lastCapturedLine >= endIndex - 5;
398
+ const recentPromptIndex = partialBufferReset ? findRecentUserPromptIndex(80) : -1;
399
+ const startIndex = partialBufferReset
400
+ ? (recentPromptIndex >= 0 ? recentPromptIndex + 1 : Math.max(0, endIndex - 80))
401
+ : Math.max(0, lastCapturedLine);
402
+ for (let i = startIndex; i < endIndex; i++) {
403
+ const line = lines[i];
404
+ const cleanLine = (0, cli_patterns_1.stripAnsi)(line);
405
+ // Skip lines matching any skip pattern
406
+ const shouldSkip = skipPatterns.some(pattern => pattern.test(cleanLine));
407
+ if (shouldSkip) {
408
+ continue;
409
+ }
410
+ responseLines.push(line);
411
+ }
412
+ const partialResponse = responseLines.join('\n').trim();
413
+ if (partialResponse) {
414
+ return {
415
+ response: partialResponse,
416
+ isComplete: false,
417
+ lineCount: endIndex,
418
+ };
419
+ }
420
+ // Response not yet complete (or is in thinking state)
421
+ return {
422
+ response: '',
423
+ isComplete: false,
424
+ lineCount: totalLines,
425
+ };
426
+ }
427
+ /**
428
+ * Check for CLI tool response once
429
+ *
430
+ * @param worktreeId - Worktree ID
431
+ * @returns True if response was found and processed
432
+ */
433
+ async function checkForResponse(worktreeId, cliToolId) {
434
+ const db = (0, db_instance_1.getDbInstance)();
435
+ try {
436
+ // Get worktree to verify it exists
437
+ const worktree = (0, db_1.getWorktreeById)(db, worktreeId);
438
+ if (!worktree) {
439
+ console.error(`Worktree ${worktreeId} not found, stopping poller`);
440
+ stopPolling(worktreeId, cliToolId);
441
+ return false;
442
+ }
443
+ // Check if CLI tool session is running
444
+ const running = await (0, cli_session_1.isSessionRunning)(worktreeId, cliToolId);
445
+ if (!running) {
446
+ stopPolling(worktreeId, cliToolId);
447
+ return false;
448
+ }
449
+ // Get session state (last captured line count)
450
+ const sessionState = (0, db_1.getSessionState)(db, worktreeId, cliToolId);
451
+ const lastCapturedLine = sessionState?.lastCapturedLine || 0;
452
+ // Capture current output
453
+ const output = await (0, cli_session_1.captureSessionOutput)(worktreeId, cliToolId, 10000);
454
+ // Extract response
455
+ const result = extractResponse(output, lastCapturedLine, cliToolId);
456
+ if (!result || !result.isComplete) {
457
+ // No new output or response not yet complete
458
+ // But if Claude is processing (thinking), mark any pending prompts as answered
459
+ // This handles cases where user responded to prompts directly via terminal
460
+ const { thinkingPattern } = (0, cli_patterns_1.getCliToolPatterns)(cliToolId);
461
+ const cleanOutput = (0, cli_patterns_1.stripAnsi)(output);
462
+ if (thinkingPattern.test(cleanOutput)) {
463
+ const answeredCount = (0, db_1.markPendingPromptsAsAnswered)(db, worktreeId, cliToolId);
464
+ if (answeredCount > 0) {
465
+ console.log(`Marked ${answeredCount} pending prompt(s) as answered (thinking detected) for ${worktreeId}`);
466
+ }
467
+ }
468
+ return false;
469
+ }
470
+ // CRITICAL FIX: If lineCount == lastCapturedLine AND there's no in-progress message,
471
+ // this response has already been saved. Skip to prevent duplicates.
472
+ if (result.lineCount === lastCapturedLine && !sessionState?.inProgressMessageId) {
473
+ return false;
474
+ }
475
+ // Additional duplicate prevention: check if savePendingAssistantResponse
476
+ // already saved this content by comparing line counts
477
+ if (result.lineCount <= lastCapturedLine) {
478
+ console.log(`[checkForResponse] Already saved up to line ${lastCapturedLine}, skipping (result: ${result.lineCount})`);
479
+ return false;
480
+ }
481
+ // Response is complete! Check if it's a prompt
482
+ const promptDetection = (0, prompt_detector_1.detectPrompt)(result.response);
483
+ if (promptDetection.isPrompt) {
484
+ // This is a prompt - save as prompt message
485
+ (0, db_1.clearInProgressMessageId)(db, worktreeId, cliToolId);
486
+ const message = (0, db_1.createMessage)(db, {
487
+ worktreeId,
488
+ role: 'assistant',
489
+ content: promptDetection.cleanContent,
490
+ messageType: 'prompt',
491
+ promptData: promptDetection.promptData,
492
+ timestamp: new Date(),
493
+ cliToolId,
494
+ });
495
+ (0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
496
+ (0, ws_server_1.broadcastMessage)('message', { worktreeId, message });
497
+ stopPolling(worktreeId, cliToolId);
498
+ return true;
499
+ }
500
+ // Validate response content is not empty
501
+ if (!result.response || result.response.trim() === '') {
502
+ (0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
503
+ return false;
504
+ }
505
+ // Parse Claude-specific metadata (summary, log filename, request id)
506
+ const claudeMetadata = cliToolId === 'claude'
507
+ ? (0, claude_output_1.parseClaudeOutput)(result.response)
508
+ : undefined;
509
+ // Clean up responses (remove shell prompts, setup commands, and errors)
510
+ let cleanedResponse = result.response;
511
+ if (cliToolId === 'gemini') {
512
+ cleanedResponse = cleanGeminiResponse(result.response);
513
+ }
514
+ else if (cliToolId === 'claude') {
515
+ cleanedResponse = cleanClaudeResponse(result.response);
516
+ }
517
+ // If cleaned response is empty or just "[No content]", skip saving
518
+ // This prevents creating messages for shell setup commands that get filtered out
519
+ if (!cleanedResponse || cleanedResponse.trim() === '' || cleanedResponse === '[No content]') {
520
+ (0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
521
+ (0, db_1.clearInProgressMessageId)(db, worktreeId, cliToolId);
522
+ return false;
523
+ }
524
+ // Create Markdown log file for the conversation pair
525
+ if (cleanedResponse) {
526
+ await (0, conversation_logger_1.recordClaudeConversation)(db, worktreeId, cleanedResponse, cliToolId);
527
+ }
528
+ // Mark any pending prompts as answered since Claude has started processing
529
+ // This handles cases where user responded to prompts directly via terminal
530
+ const answeredCount = (0, db_1.markPendingPromptsAsAnswered)(db, worktreeId, cliToolId);
531
+ if (answeredCount > 0) {
532
+ console.log(`Marked ${answeredCount} pending prompt(s) as answered for ${worktreeId}`);
533
+ }
534
+ // Race condition prevention: re-check session state before saving
535
+ // savePendingAssistantResponse may have already saved this content concurrently
536
+ const currentSessionState = (0, db_1.getSessionState)(db, worktreeId, cliToolId);
537
+ if (currentSessionState && result.lineCount <= currentSessionState.lastCapturedLine) {
538
+ console.log(`[checkForResponse] Race condition detected, skipping save (result: ${result.lineCount}, current: ${currentSessionState.lastCapturedLine})`);
539
+ return false;
540
+ }
541
+ // Create new CLI tool message in database
542
+ // (No longer using in-progress messages - frontend shows realtime output instead)
543
+ const message = (0, db_1.createMessage)(db, {
544
+ worktreeId,
545
+ role: 'assistant',
546
+ content: cleanedResponse,
547
+ messageType: 'normal',
548
+ timestamp: new Date(),
549
+ cliToolId,
550
+ summary: claudeMetadata?.summary,
551
+ logFileName: claudeMetadata?.logFileName,
552
+ requestId: claudeMetadata?.requestId,
553
+ });
554
+ // Broadcast message to WebSocket clients
555
+ (0, ws_server_1.broadcastMessage)('message', { worktreeId, message });
556
+ // Update session state
557
+ (0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
558
+ return true;
559
+ }
560
+ catch (error) {
561
+ const errorMessage = error instanceof Error ? error.message : String(error);
562
+ console.error(`Error checking for response (${worktreeId}):`, errorMessage);
563
+ return false;
564
+ }
565
+ }
566
+ /**
567
+ * Start polling for CLI tool response
568
+ *
569
+ * @param worktreeId - Worktree ID
570
+ * @param cliToolId - CLI tool ID (claude, codex, gemini)
571
+ *
572
+ * @example
573
+ * ```typescript
574
+ * startPolling('feature-foo', 'claude');
575
+ * ```
576
+ */
577
+ function startPolling(worktreeId, cliToolId) {
578
+ const pollerKey = getPollerKey(worktreeId, cliToolId);
579
+ // Stop existing poller if any
580
+ stopPolling(worktreeId, cliToolId);
581
+ // Record start time
582
+ pollingStartTimes.set(pollerKey, Date.now());
583
+ // Start polling
584
+ const interval = setInterval(async () => {
585
+ const startTime = pollingStartTimes.get(pollerKey);
586
+ // Check if max duration exceeded
587
+ if (startTime && Date.now() - startTime > MAX_POLLING_DURATION) {
588
+ stopPolling(worktreeId, cliToolId);
589
+ return;
590
+ }
591
+ // Check for response
592
+ try {
593
+ await checkForResponse(worktreeId, cliToolId);
594
+ }
595
+ catch (error) {
596
+ console.error(`[Poller] Error:`, error);
597
+ }
598
+ }, POLLING_INTERVAL);
599
+ activePollers.set(pollerKey, interval);
600
+ }
601
+ /**
602
+ * Stop polling for a worktree and CLI tool combination
603
+ *
604
+ * @param worktreeId - Worktree ID
605
+ * @param cliToolId - CLI tool ID (claude, codex, gemini)
606
+ *
607
+ * @example
608
+ * ```typescript
609
+ * stopPolling('feature-foo', 'claude');
610
+ * ```
611
+ */
612
+ function stopPolling(worktreeId, cliToolId) {
613
+ const pollerKey = getPollerKey(worktreeId, cliToolId);
614
+ const interval = activePollers.get(pollerKey);
615
+ if (interval) {
616
+ clearInterval(interval);
617
+ activePollers.delete(pollerKey);
618
+ pollingStartTimes.delete(pollerKey);
619
+ }
620
+ }
621
+ /**
622
+ * Stop all active pollers
623
+ * Used for cleanup on server shutdown
624
+ */
625
+ function stopAllPolling() {
626
+ for (const pollerKey of activePollers.keys()) {
627
+ const [worktreeId, cliToolId] = pollerKey.split(':');
628
+ stopPolling(worktreeId, cliToolId);
629
+ }
630
+ }
631
+ /**
632
+ * Get list of active pollers
633
+ *
634
+ * @returns Array of worktree IDs currently being polled
635
+ */
636
+ function getActivePollers() {
637
+ return Array.from(activePollers.keys());
638
+ }