@herbcaudill/ralph 1.0.1 → 1.0.2

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 (351) hide show
  1. package/dist/index.js +2401 -6
  2. package/dist/index.js.map +1 -1
  3. package/package.json +9 -8
  4. package/templates/core-prompt.md +3 -3
  5. package/templates/workflow.md +1 -1
  6. package/dist/cli.d.ts +0 -3
  7. package/dist/cli.d.ts.map +0 -1
  8. package/dist/cli.js +0 -102
  9. package/dist/cli.js.map +0 -1
  10. package/dist/components/App.d.ts +0 -17
  11. package/dist/components/App.d.ts.map +0 -1
  12. package/dist/components/App.js +0 -18
  13. package/dist/components/App.js.map +0 -1
  14. package/dist/components/EnhancedTextInput.d.ts +0 -22
  15. package/dist/components/EnhancedTextInput.d.ts.map +0 -1
  16. package/dist/components/EnhancedTextInput.js +0 -130
  17. package/dist/components/EnhancedTextInput.js.map +0 -1
  18. package/dist/components/EnhancedTextInput.test.d.ts +0 -2
  19. package/dist/components/EnhancedTextInput.test.d.ts.map +0 -1
  20. package/dist/components/EnhancedTextInput.test.js +0 -99
  21. package/dist/components/EnhancedTextInput.test.js.map +0 -1
  22. package/dist/components/EventDisplay.d.ts +0 -14
  23. package/dist/components/EventDisplay.d.ts.map +0 -1
  24. package/dist/components/EventDisplay.js +0 -91
  25. package/dist/components/EventDisplay.js.map +0 -1
  26. package/dist/components/EventDisplay.replay.test.d.ts +0 -2
  27. package/dist/components/EventDisplay.replay.test.d.ts.map +0 -1
  28. package/dist/components/EventDisplay.replay.test.js +0 -379
  29. package/dist/components/EventDisplay.replay.test.js.map +0 -1
  30. package/dist/components/EventDisplay.test.d.ts +0 -2
  31. package/dist/components/EventDisplay.test.d.ts.map +0 -1
  32. package/dist/components/EventDisplay.test.js +0 -349
  33. package/dist/components/EventDisplay.test.js.map +0 -1
  34. package/dist/components/FullScreenLayout.d.ts +0 -15
  35. package/dist/components/FullScreenLayout.d.ts.map +0 -1
  36. package/dist/components/FullScreenLayout.js +0 -20
  37. package/dist/components/FullScreenLayout.js.map +0 -1
  38. package/dist/components/Header.d.ts +0 -10
  39. package/dist/components/Header.d.ts.map +0 -1
  40. package/dist/components/Header.js +0 -22
  41. package/dist/components/Header.js.map +0 -1
  42. package/dist/components/Header.test.d.ts +0 -2
  43. package/dist/components/Header.test.d.ts.map +0 -1
  44. package/dist/components/Header.test.js +0 -27
  45. package/dist/components/Header.test.js.map +0 -1
  46. package/dist/components/InitRalph.d.ts +0 -8
  47. package/dist/components/InitRalph.d.ts.map +0 -1
  48. package/dist/components/InitRalph.js +0 -136
  49. package/dist/components/InitRalph.js.map +0 -1
  50. package/dist/components/IterationRunner.d.ts +0 -10
  51. package/dist/components/IterationRunner.d.ts.map +0 -1
  52. package/dist/components/IterationRunner.js +0 -536
  53. package/dist/components/IterationRunner.js.map +0 -1
  54. package/dist/components/IterationRunner.test.d.ts +0 -46
  55. package/dist/components/IterationRunner.test.d.ts.map +0 -1
  56. package/dist/components/IterationRunner.test.js +0 -69
  57. package/dist/components/IterationRunner.test.js.map +0 -1
  58. package/dist/components/IterationRunner.types.d.ts +0 -30
  59. package/dist/components/IterationRunner.types.d.ts.map +0 -1
  60. package/dist/components/IterationRunner.types.js +0 -2
  61. package/dist/components/IterationRunner.types.js.map +0 -1
  62. package/dist/components/JsonOutput.d.ts +0 -13
  63. package/dist/components/JsonOutput.d.ts.map +0 -1
  64. package/dist/components/JsonOutput.js +0 -269
  65. package/dist/components/JsonOutput.js.map +0 -1
  66. package/dist/components/JsonOutput.test.d.ts +0 -2
  67. package/dist/components/JsonOutput.test.d.ts.map +0 -1
  68. package/dist/components/JsonOutput.test.js +0 -39
  69. package/dist/components/JsonOutput.test.js.map +0 -1
  70. package/dist/components/ProgressBar.d.ts +0 -18
  71. package/dist/components/ProgressBar.d.ts.map +0 -1
  72. package/dist/components/ProgressBar.js +0 -30
  73. package/dist/components/ProgressBar.js.map +0 -1
  74. package/dist/components/ProgressBar.test.d.ts +0 -2
  75. package/dist/components/ProgressBar.test.d.ts.map +0 -1
  76. package/dist/components/ProgressBar.test.js +0 -55
  77. package/dist/components/ProgressBar.test.js.map +0 -1
  78. package/dist/components/ReplayLog.d.ts +0 -7
  79. package/dist/components/ReplayLog.d.ts.map +0 -1
  80. package/dist/components/ReplayLog.js +0 -57
  81. package/dist/components/ReplayLog.js.map +0 -1
  82. package/dist/components/StreamingText.d.ts +0 -11
  83. package/dist/components/StreamingText.d.ts.map +0 -1
  84. package/dist/components/StreamingText.js +0 -37
  85. package/dist/components/StreamingText.js.map +0 -1
  86. package/dist/components/StreamingText.test.d.ts +0 -2
  87. package/dist/components/StreamingText.test.d.ts.map +0 -1
  88. package/dist/components/StreamingText.test.js +0 -125
  89. package/dist/components/StreamingText.test.js.map +0 -1
  90. package/dist/components/ToolUse.d.ts +0 -16
  91. package/dist/components/ToolUse.d.ts.map +0 -1
  92. package/dist/components/ToolUse.js +0 -11
  93. package/dist/components/ToolUse.js.map +0 -1
  94. package/dist/components/ToolUse.test.d.ts +0 -2
  95. package/dist/components/ToolUse.test.d.ts.map +0 -1
  96. package/dist/components/ToolUse.test.js +0 -43
  97. package/dist/components/ToolUse.test.js.map +0 -1
  98. package/dist/components/blocksToLines.d.ts +0 -6
  99. package/dist/components/blocksToLines.d.ts.map +0 -1
  100. package/dist/components/blocksToLines.js +0 -15
  101. package/dist/components/blocksToLines.js.map +0 -1
  102. package/dist/components/eventToBlocks.d.ts +0 -22
  103. package/dist/components/eventToBlocks.d.ts.map +0 -1
  104. package/dist/components/eventToBlocks.js +0 -166
  105. package/dist/components/eventToBlocks.js.map +0 -1
  106. package/dist/components/eventToBlocks.test.d.ts +0 -2
  107. package/dist/components/eventToBlocks.test.d.ts.map +0 -1
  108. package/dist/components/eventToBlocks.test.js +0 -466
  109. package/dist/components/eventToBlocks.test.js.map +0 -1
  110. package/dist/components/findNextWordBoundary.d.ts +0 -10
  111. package/dist/components/findNextWordBoundary.d.ts.map +0 -1
  112. package/dist/components/findNextWordBoundary.js +0 -23
  113. package/dist/components/findNextWordBoundary.js.map +0 -1
  114. package/dist/components/findPreviousWordBoundary.d.ts +0 -10
  115. package/dist/components/findPreviousWordBoundary.d.ts.map +0 -1
  116. package/dist/components/findPreviousWordBoundary.js +0 -23
  117. package/dist/components/findPreviousWordBoundary.js.map +0 -1
  118. package/dist/components/processEvents.d.ts +0 -11
  119. package/dist/components/processEvents.d.ts.map +0 -1
  120. package/dist/components/processEvents.js +0 -87
  121. package/dist/components/processEvents.js.map +0 -1
  122. package/dist/components/renderStaticItem.d.ts +0 -7
  123. package/dist/components/renderStaticItem.d.ts.map +0 -1
  124. package/dist/components/renderStaticItem.js +0 -23
  125. package/dist/components/renderStaticItem.js.map +0 -1
  126. package/dist/components/replay.d.ts +0 -5
  127. package/dist/components/replay.d.ts.map +0 -1
  128. package/dist/components/replay.js +0 -49
  129. package/dist/components/replay.js.map +0 -1
  130. package/dist/components/test-helpers/ControlledInput.d.ts +0 -14
  131. package/dist/components/test-helpers/ControlledInput.d.ts.map +0 -1
  132. package/dist/components/test-helpers/ControlledInput.js +0 -15
  133. package/dist/components/test-helpers/ControlledInput.js.map +0 -1
  134. package/dist/components/useContentHeight.d.ts +0 -9
  135. package/dist/components/useContentHeight.d.ts.map +0 -1
  136. package/dist/components/useContentHeight.js +0 -16
  137. package/dist/components/useContentHeight.js.map +0 -1
  138. package/dist/index.d.ts +0 -3
  139. package/dist/index.d.ts.map +0 -1
  140. package/dist/lib/MessageQueue.d.ts +0 -33
  141. package/dist/lib/MessageQueue.d.ts.map +0 -1
  142. package/dist/lib/MessageQueue.js +0 -96
  143. package/dist/lib/MessageQueue.js.map +0 -1
  144. package/dist/lib/MessageQueue.test.d.ts +0 -2
  145. package/dist/lib/MessageQueue.test.d.ts.map +0 -1
  146. package/dist/lib/MessageQueue.test.js +0 -224
  147. package/dist/lib/MessageQueue.test.js.map +0 -1
  148. package/dist/lib/StdinCommandHandler.d.ts +0 -48
  149. package/dist/lib/StdinCommandHandler.d.ts.map +0 -1
  150. package/dist/lib/StdinCommandHandler.js +0 -95
  151. package/dist/lib/StdinCommandHandler.js.map +0 -1
  152. package/dist/lib/StdinCommandHandler.test.d.ts +0 -2
  153. package/dist/lib/StdinCommandHandler.test.d.ts.map +0 -1
  154. package/dist/lib/StdinCommandHandler.test.js +0 -93
  155. package/dist/lib/StdinCommandHandler.test.js.map +0 -1
  156. package/dist/lib/WorktreeManager.d.ts +0 -114
  157. package/dist/lib/WorktreeManager.d.ts.map +0 -1
  158. package/dist/lib/WorktreeManager.js +0 -350
  159. package/dist/lib/WorktreeManager.js.map +0 -1
  160. package/dist/lib/WorktreeManager.test.d.ts +0 -2
  161. package/dist/lib/WorktreeManager.test.d.ts.map +0 -1
  162. package/dist/lib/WorktreeManager.test.js +0 -261
  163. package/dist/lib/WorktreeManager.test.js.map +0 -1
  164. package/dist/lib/addTodo.d.ts +0 -11
  165. package/dist/lib/addTodo.d.ts.map +0 -1
  166. package/dist/lib/addTodo.js +0 -51
  167. package/dist/lib/addTodo.js.map +0 -1
  168. package/dist/lib/beadsClient.d.ts +0 -46
  169. package/dist/lib/beadsClient.d.ts.map +0 -1
  170. package/dist/lib/beadsClient.js +0 -172
  171. package/dist/lib/beadsClient.js.map +0 -1
  172. package/dist/lib/captureBeadsSnapshot.d.ts +0 -4
  173. package/dist/lib/captureBeadsSnapshot.d.ts.map +0 -1
  174. package/dist/lib/captureBeadsSnapshot.js +0 -24
  175. package/dist/lib/captureBeadsSnapshot.js.map +0 -1
  176. package/dist/lib/captureStartupSnapshot.d.ts +0 -10
  177. package/dist/lib/captureStartupSnapshot.d.ts.map +0 -1
  178. package/dist/lib/captureStartupSnapshot.js +0 -29
  179. package/dist/lib/captureStartupSnapshot.js.map +0 -1
  180. package/dist/lib/captureTodoSnapshot.d.ts +0 -4
  181. package/dist/lib/captureTodoSnapshot.d.ts.map +0 -1
  182. package/dist/lib/captureTodoSnapshot.js +0 -24
  183. package/dist/lib/captureTodoSnapshot.js.map +0 -1
  184. package/dist/lib/copyTemplates.d.ts +0 -22
  185. package/dist/lib/copyTemplates.d.ts.map +0 -1
  186. package/dist/lib/copyTemplates.js +0 -32
  187. package/dist/lib/copyTemplates.js.map +0 -1
  188. package/dist/lib/createStdinCommandHandler.d.ts +0 -28
  189. package/dist/lib/createStdinCommandHandler.d.ts.map +0 -1
  190. package/dist/lib/createStdinCommandHandler.js +0 -67
  191. package/dist/lib/createStdinCommandHandler.js.map +0 -1
  192. package/dist/lib/createUserMessage.d.ts +0 -6
  193. package/dist/lib/createUserMessage.d.ts.map +0 -1
  194. package/dist/lib/createUserMessage.js +0 -13
  195. package/dist/lib/createUserMessage.js.map +0 -1
  196. package/dist/lib/debug.d.ts +0 -19
  197. package/dist/lib/debug.d.ts.map +0 -1
  198. package/dist/lib/debug.js +0 -54
  199. package/dist/lib/debug.js.map +0 -1
  200. package/dist/lib/findMaxLogNumber.d.ts +0 -6
  201. package/dist/lib/findMaxLogNumber.d.ts.map +0 -1
  202. package/dist/lib/findMaxLogNumber.js +0 -26
  203. package/dist/lib/findMaxLogNumber.js.map +0 -1
  204. package/dist/lib/findMaxLogNumber.test.d.ts +0 -2
  205. package/dist/lib/findMaxLogNumber.test.d.ts.map +0 -1
  206. package/dist/lib/findMaxLogNumber.test.js +0 -39
  207. package/dist/lib/findMaxLogNumber.test.js.map +0 -1
  208. package/dist/lib/formatContentBlock.d.ts +0 -6
  209. package/dist/lib/formatContentBlock.d.ts.map +0 -1
  210. package/dist/lib/formatContentBlock.js +0 -18
  211. package/dist/lib/formatContentBlock.js.map +0 -1
  212. package/dist/lib/formatIterationHeader.d.ts +0 -5
  213. package/dist/lib/formatIterationHeader.d.ts.map +0 -1
  214. package/dist/lib/formatIterationHeader.js +0 -8
  215. package/dist/lib/formatIterationHeader.js.map +0 -1
  216. package/dist/lib/formatText.d.ts +0 -5
  217. package/dist/lib/formatText.d.ts.map +0 -1
  218. package/dist/lib/formatText.js +0 -33
  219. package/dist/lib/formatText.js.map +0 -1
  220. package/dist/lib/formatToolUse.d.ts +0 -7
  221. package/dist/lib/formatToolUse.d.ts.map +0 -1
  222. package/dist/lib/formatToolUse.js +0 -14
  223. package/dist/lib/formatToolUse.js.map +0 -1
  224. package/dist/lib/formatUserMessage.d.ts +0 -5
  225. package/dist/lib/formatUserMessage.d.ts.map +0 -1
  226. package/dist/lib/formatUserMessage.js +0 -8
  227. package/dist/lib/formatUserMessage.js.map +0 -1
  228. package/dist/lib/getBaseCwd.d.ts +0 -6
  229. package/dist/lib/getBaseCwd.d.ts.map +0 -1
  230. package/dist/lib/getBaseCwd.js +0 -6
  231. package/dist/lib/getBaseCwd.js.map +0 -1
  232. package/dist/lib/getBeadsProgress.d.ts +0 -8
  233. package/dist/lib/getBeadsProgress.d.ts.map +0 -1
  234. package/dist/lib/getBeadsProgress.js +0 -35
  235. package/dist/lib/getBeadsProgress.js.map +0 -1
  236. package/dist/lib/getClaudeVersion.d.ts +0 -6
  237. package/dist/lib/getClaudeVersion.d.ts.map +0 -1
  238. package/dist/lib/getClaudeVersion.js +0 -21
  239. package/dist/lib/getClaudeVersion.js.map +0 -1
  240. package/dist/lib/getClaudeVersion.test.d.ts +0 -2
  241. package/dist/lib/getClaudeVersion.test.d.ts.map +0 -1
  242. package/dist/lib/getClaudeVersion.test.js +0 -39
  243. package/dist/lib/getClaudeVersion.test.js.map +0 -1
  244. package/dist/lib/getDefaultIterations.d.ts +0 -6
  245. package/dist/lib/getDefaultIterations.d.ts.map +0 -1
  246. package/dist/lib/getDefaultIterations.js +0 -14
  247. package/dist/lib/getDefaultIterations.js.map +0 -1
  248. package/dist/lib/getDefaultIterations.test.d.ts +0 -2
  249. package/dist/lib/getDefaultIterations.test.d.ts.map +0 -1
  250. package/dist/lib/getDefaultIterations.test.js +0 -39
  251. package/dist/lib/getDefaultIterations.test.js.map +0 -1
  252. package/dist/lib/getLatestLogFile.d.ts +0 -6
  253. package/dist/lib/getLatestLogFile.d.ts.map +0 -1
  254. package/dist/lib/getLatestLogFile.js +0 -15
  255. package/dist/lib/getLatestLogFile.js.map +0 -1
  256. package/dist/lib/getNextLogFile.d.ts +0 -7
  257. package/dist/lib/getNextLogFile.d.ts.map +0 -1
  258. package/dist/lib/getNextLogFile.js +0 -18
  259. package/dist/lib/getNextLogFile.js.map +0 -1
  260. package/dist/lib/getNextLogFile.test.d.ts +0 -2
  261. package/dist/lib/getNextLogFile.test.d.ts.map +0 -1
  262. package/dist/lib/getNextLogFile.test.js +0 -66
  263. package/dist/lib/getNextLogFile.test.js.map +0 -1
  264. package/dist/lib/getOpenIssueCount.d.ts +0 -6
  265. package/dist/lib/getOpenIssueCount.d.ts.map +0 -1
  266. package/dist/lib/getOpenIssueCount.js +0 -19
  267. package/dist/lib/getOpenIssueCount.js.map +0 -1
  268. package/dist/lib/getOpenIssueCount.test.d.ts +0 -2
  269. package/dist/lib/getOpenIssueCount.test.d.ts.map +0 -1
  270. package/dist/lib/getOpenIssueCount.test.js +0 -31
  271. package/dist/lib/getOpenIssueCount.test.js.map +0 -1
  272. package/dist/lib/getProgress.d.ts +0 -14
  273. package/dist/lib/getProgress.d.ts.map +0 -1
  274. package/dist/lib/getProgress.js +0 -30
  275. package/dist/lib/getProgress.js.map +0 -1
  276. package/dist/lib/getProgress.test.d.ts +0 -2
  277. package/dist/lib/getProgress.test.d.ts.map +0 -1
  278. package/dist/lib/getProgress.test.js +0 -218
  279. package/dist/lib/getProgress.test.js.map +0 -1
  280. package/dist/lib/getPromptContent.d.ts +0 -8
  281. package/dist/lib/getPromptContent.d.ts.map +0 -1
  282. package/dist/lib/getPromptContent.js +0 -31
  283. package/dist/lib/getPromptContent.js.map +0 -1
  284. package/dist/lib/getTerminalSize.d.ts +0 -8
  285. package/dist/lib/getTerminalSize.d.ts.map +0 -1
  286. package/dist/lib/getTerminalSize.js +0 -10
  287. package/dist/lib/getTerminalSize.js.map +0 -1
  288. package/dist/lib/getTodoProgress.d.ts +0 -4
  289. package/dist/lib/getTodoProgress.d.ts.map +0 -1
  290. package/dist/lib/getTodoProgress.js +0 -22
  291. package/dist/lib/getTodoProgress.js.map +0 -1
  292. package/dist/lib/insertTodo.d.ts +0 -7
  293. package/dist/lib/insertTodo.d.ts.map +0 -1
  294. package/dist/lib/insertTodo.js +0 -22
  295. package/dist/lib/insertTodo.js.map +0 -1
  296. package/dist/lib/outputEvent.d.ts +0 -5
  297. package/dist/lib/outputEvent.d.ts.map +0 -1
  298. package/dist/lib/outputEvent.js +0 -7
  299. package/dist/lib/outputEvent.js.map +0 -1
  300. package/dist/lib/parseStdinCommand.d.ts +0 -25
  301. package/dist/lib/parseStdinCommand.d.ts.map +0 -1
  302. package/dist/lib/parseStdinCommand.js +0 -43
  303. package/dist/lib/parseStdinCommand.js.map +0 -1
  304. package/dist/lib/parseStdinCommand.test.d.ts +0 -2
  305. package/dist/lib/parseStdinCommand.test.d.ts.map +0 -1
  306. package/dist/lib/parseStdinCommand.test.js +0 -93
  307. package/dist/lib/parseStdinCommand.test.js.map +0 -1
  308. package/dist/lib/parseTaskLifecycle.d.ts +0 -18
  309. package/dist/lib/parseTaskLifecycle.d.ts.map +0 -1
  310. package/dist/lib/parseTaskLifecycle.js +0 -30
  311. package/dist/lib/parseTaskLifecycle.js.map +0 -1
  312. package/dist/lib/parseTaskLifecycle.test.d.ts +0 -2
  313. package/dist/lib/parseTaskLifecycle.test.d.ts.map +0 -1
  314. package/dist/lib/parseTaskLifecycle.test.js +0 -122
  315. package/dist/lib/parseTaskLifecycle.test.js.map +0 -1
  316. package/dist/lib/parseWorktreeFromBranch.d.ts +0 -13
  317. package/dist/lib/parseWorktreeFromBranch.d.ts.map +0 -1
  318. package/dist/lib/parseWorktreeFromBranch.js +0 -19
  319. package/dist/lib/parseWorktreeFromBranch.js.map +0 -1
  320. package/dist/lib/processEvents.d.ts +0 -12
  321. package/dist/lib/processEvents.d.ts.map +0 -1
  322. package/dist/lib/processEvents.js +0 -113
  323. package/dist/lib/processEvents.js.map +0 -1
  324. package/dist/lib/rel.d.ts +0 -8
  325. package/dist/lib/rel.d.ts.map +0 -1
  326. package/dist/lib/rel.js +0 -19
  327. package/dist/lib/rel.js.map +0 -1
  328. package/dist/lib/rel.test.d.ts +0 -2
  329. package/dist/lib/rel.test.d.ts.map +0 -1
  330. package/dist/lib/rel.test.js +0 -51
  331. package/dist/lib/rel.test.js.map +0 -1
  332. package/dist/lib/sdkMessageToEvent.d.ts +0 -6
  333. package/dist/lib/sdkMessageToEvent.d.ts.map +0 -1
  334. package/dist/lib/sdkMessageToEvent.js +0 -12
  335. package/dist/lib/sdkMessageToEvent.js.map +0 -1
  336. package/dist/lib/shortenTempPaths.d.ts +0 -8
  337. package/dist/lib/shortenTempPaths.d.ts.map +0 -1
  338. package/dist/lib/shortenTempPaths.js +0 -12
  339. package/dist/lib/shortenTempPaths.js.map +0 -1
  340. package/dist/lib/shortenTempPaths.test.d.ts +0 -2
  341. package/dist/lib/shortenTempPaths.test.d.ts.map +0 -1
  342. package/dist/lib/shortenTempPaths.test.js +0 -46
  343. package/dist/lib/shortenTempPaths.test.js.map +0 -1
  344. package/dist/lib/types.d.ts +0 -19
  345. package/dist/lib/types.d.ts.map +0 -1
  346. package/dist/lib/types.js +0 -2
  347. package/dist/lib/types.js.map +0 -1
  348. package/dist/lib/useTerminalSize.d.ts +0 -6
  349. package/dist/lib/useTerminalSize.d.ts.map +0 -1
  350. package/dist/lib/useTerminalSize.js +0 -19
  351. package/dist/lib/useTerminalSize.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,10 +1,2405 @@
1
- import { program } from "./cli.js";
2
- /** Parse and execute the CLI program with command-line arguments. */
3
- export const run = () => {
4
- program.parse(process.argv);
1
+ // src/cli.ts
2
+ import { Command } from "commander";
3
+ import { render } from "ink";
4
+ import React12 from "react";
5
+
6
+ // src/components/App.tsx
7
+ import React10 from "react";
8
+
9
+ // src/components/SessionRunner.tsx
10
+ import React5, { useState as useState3, useEffect as useEffect3, useRef } from "react";
11
+ import { Box as Box3, Text as Text5, useApp, Static, useInput as useInput2 } from "ink";
12
+ import Spinner from "ink-spinner";
13
+
14
+ // src/components/EnhancedTextInput.tsx
15
+ import React, { useState, useEffect } from "react";
16
+ import { Text, useInput } from "ink";
17
+ import chalk from "chalk";
18
+
19
+ // src/components/findPreviousWordBoundary.ts
20
+ var findPreviousWordBoundary = (text, cursorOffset) => {
21
+ if (cursorOffset <= 0) return 0;
22
+ let pos = cursorOffset;
23
+ while (pos > 0 && /\s/.test(text[pos - 1])) {
24
+ pos--;
25
+ }
26
+ while (pos > 0 && !/\s/.test(text[pos - 1])) {
27
+ pos--;
28
+ }
29
+ return pos;
30
+ };
31
+
32
+ // src/components/findNextWordBoundary.ts
33
+ var findNextWordBoundary = (text, cursorOffset) => {
34
+ if (cursorOffset >= text.length) return text.length;
35
+ let pos = cursorOffset;
36
+ while (pos < text.length && /\s/.test(text[pos])) {
37
+ pos++;
38
+ }
39
+ while (pos < text.length && !/\s/.test(text[pos])) {
40
+ pos++;
41
+ }
42
+ return pos;
43
+ };
44
+
45
+ // src/components/EnhancedTextInput.tsx
46
+ var EnhancedTextInput = ({
47
+ value: originalValue,
48
+ placeholder = "",
49
+ focus = true,
50
+ showCursor = true,
51
+ onChange,
52
+ onSubmit
53
+ }) => {
54
+ const [cursorOffset, setCursorOffset] = useState(originalValue.length);
55
+ useEffect(() => {
56
+ if (!focus || !showCursor) {
57
+ return;
58
+ }
59
+ if (cursorOffset > originalValue.length) {
60
+ setCursorOffset(originalValue.length);
61
+ }
62
+ }, [originalValue, focus, showCursor, cursorOffset]);
63
+ let renderedValue = originalValue;
64
+ let renderedPlaceholder = placeholder ? chalk.grey(placeholder) : void 0;
65
+ if (showCursor && focus) {
66
+ renderedPlaceholder = placeholder.length > 0 ? chalk.inverse(placeholder[0]) + chalk.grey(placeholder.slice(1)) : chalk.inverse(" ");
67
+ renderedValue = originalValue.length > 0 ? "" : chalk.inverse(" ");
68
+ for (let i = 0; i < originalValue.length; i++) {
69
+ const char = originalValue[i];
70
+ renderedValue += i === cursorOffset ? chalk.inverse(char) : char;
71
+ }
72
+ if (originalValue.length > 0 && cursorOffset === originalValue.length) {
73
+ renderedValue += chalk.inverse(" ");
74
+ }
75
+ }
76
+ useInput(
77
+ (input, key) => {
78
+ if (key.upArrow || key.downArrow || key.ctrl && input === "c" || key.tab) {
79
+ return;
80
+ }
81
+ if (key.return) {
82
+ onSubmit?.(originalValue);
83
+ return;
84
+ }
85
+ let nextCursorOffset = cursorOffset;
86
+ let nextValue = originalValue;
87
+ if (key.meta && key.leftArrow) {
88
+ nextCursorOffset = findPreviousWordBoundary(originalValue, cursorOffset);
89
+ } else if (key.meta && key.rightArrow) {
90
+ nextCursorOffset = findNextWordBoundary(originalValue, cursorOffset);
91
+ } else if (key.ctrl && input === "a") {
92
+ nextCursorOffset = 0;
93
+ } else if (key.ctrl && input === "e") {
94
+ nextCursorOffset = originalValue.length;
95
+ } else if (key.ctrl && input === "k") {
96
+ nextValue = originalValue.slice(0, cursorOffset);
97
+ } else if (key.ctrl && input === "u") {
98
+ nextValue = originalValue.slice(cursorOffset);
99
+ nextCursorOffset = 0;
100
+ } else if (key.ctrl && input === "w") {
101
+ const wordStart = findPreviousWordBoundary(originalValue, cursorOffset);
102
+ nextValue = originalValue.slice(0, wordStart) + originalValue.slice(cursorOffset);
103
+ nextCursorOffset = wordStart;
104
+ } else if (key.meta && key.backspace) {
105
+ const wordStart = findPreviousWordBoundary(originalValue, cursorOffset);
106
+ nextValue = originalValue.slice(0, wordStart) + originalValue.slice(cursorOffset);
107
+ nextCursorOffset = wordStart;
108
+ } else if (key.leftArrow) {
109
+ if (showCursor && cursorOffset > 0) {
110
+ nextCursorOffset = cursorOffset - 1;
111
+ }
112
+ } else if (key.rightArrow) {
113
+ if (showCursor && cursorOffset < originalValue.length) {
114
+ nextCursorOffset = cursorOffset + 1;
115
+ }
116
+ } else if (key.backspace || key.delete) {
117
+ if (cursorOffset > 0) {
118
+ nextValue = originalValue.slice(0, cursorOffset - 1) + originalValue.slice(cursorOffset);
119
+ nextCursorOffset = cursorOffset - 1;
120
+ }
121
+ } else if (input && !key.ctrl && !key.meta) {
122
+ nextValue = originalValue.slice(0, cursorOffset) + input + originalValue.slice(cursorOffset);
123
+ nextCursorOffset = cursorOffset + input.length;
124
+ }
125
+ nextCursorOffset = Math.max(0, Math.min(nextCursorOffset, nextValue.length));
126
+ setCursorOffset(nextCursorOffset);
127
+ if (nextValue !== originalValue) {
128
+ onChange(nextValue);
129
+ }
130
+ },
131
+ { isActive: focus }
132
+ );
133
+ return /* @__PURE__ */ React.createElement(Text, null, placeholder ? originalValue.length > 0 ? renderedValue : renderedPlaceholder : renderedValue);
134
+ };
135
+
136
+ // src/components/SessionRunner.tsx
137
+ import { appendFileSync, writeFileSync as writeFileSync2, readFileSync as readFileSync5, existsSync as existsSync8 } from "fs";
138
+ import { join as join10, basename } from "path";
139
+ import { query } from "@anthropic-ai/claude-agent-sdk";
140
+
141
+ // src/lib/addTodo.ts
142
+ import { execSync } from "child_process";
143
+ import { existsSync, readFileSync, writeFileSync } from "fs";
144
+ import { join } from "path";
145
+
146
+ // src/lib/insertTodo.ts
147
+ var insertTodo = (content, description) => {
148
+ const lines = content.split("\n");
149
+ const todoHeaderIndex = lines.findIndex((line) => /^###?\s*To\s*do/i.test(line));
150
+ if (todoHeaderIndex === -1) {
151
+ return `### To do
152
+
153
+ - [ ] ${description}
154
+
155
+ ${content}`;
156
+ }
157
+ let insertIndex = todoHeaderIndex + 1;
158
+ while (insertIndex < lines.length && lines[insertIndex].trim() === "") {
159
+ insertIndex++;
160
+ }
161
+ lines.splice(insertIndex, 0, `- [ ] ${description}`);
162
+ return lines.join("\n");
163
+ };
164
+
165
+ // src/lib/addTodo.ts
166
+ var addTodo = (description, cwd = process.cwd()) => {
167
+ const todoPath = join(cwd, ".ralph", "todo.md");
168
+ const content = existsSync(todoPath) ? readFileSync(todoPath, "utf-8") : "";
169
+ const newContent = insertTodo(content, description);
170
+ writeFileSync(todoPath, newContent);
171
+ let indexContent = "";
172
+ try {
173
+ indexContent = execSync(
174
+ "git show :0:.ralph/todo.md 2>/dev/null || git show HEAD:.ralph/todo.md 2>/dev/null || echo ''",
175
+ {
176
+ cwd,
177
+ encoding: "utf-8"
178
+ }
179
+ );
180
+ } catch {
181
+ indexContent = "";
182
+ }
183
+ const indexWithTodo = insertTodo(indexContent, description);
184
+ const blobHash = execSync("git hash-object -w --stdin", {
185
+ cwd,
186
+ encoding: "utf-8",
187
+ input: indexWithTodo
188
+ }).trim();
189
+ execSync(`git update-index --add --cacheinfo 100644,${blobHash},.ralph/todo.md`, {
190
+ cwd,
191
+ stdio: "pipe"
192
+ });
193
+ const escapedDescription = description.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
194
+ execSync(`git commit -m "todo: ${escapedDescription}"`, { cwd, stdio: "pipe" });
195
+ console.log(`\u2705 added`);
196
+ };
197
+
198
+ // src/lib/getProgress.ts
199
+ import { existsSync as existsSync2 } from "fs";
200
+ import { join as join3 } from "path";
201
+
202
+ // src/lib/getBeadsProgress.ts
203
+ import { execSync as execSync2 } from "child_process";
204
+ var getBeadsProgress = (initialCount, startupTimestamp) => {
205
+ try {
206
+ const createdSinceStartup = parseInt(
207
+ execSync2(`bd count --created-after="${startupTimestamp}"`, {
208
+ encoding: "utf-8",
209
+ stdio: ["pipe", "pipe", "pipe"]
210
+ }).trim(),
211
+ 10
212
+ );
213
+ const currentOpen = parseInt(
214
+ execSync2("bd count --status=open", {
215
+ encoding: "utf-8",
216
+ stdio: ["pipe", "pipe", "pipe"]
217
+ }).trim(),
218
+ 10
219
+ );
220
+ const currentInProgress = parseInt(
221
+ execSync2("bd count --status=in_progress", {
222
+ encoding: "utf-8",
223
+ stdio: ["pipe", "pipe", "pipe"]
224
+ }).trim(),
225
+ 10
226
+ );
227
+ const currentRemaining = currentOpen + currentInProgress;
228
+ const total = initialCount + createdSinceStartup;
229
+ const completed = total - currentRemaining;
230
+ return { type: "beads", completed, total };
231
+ } catch {
232
+ return { type: "none", completed: 0, total: 0 };
233
+ }
234
+ };
235
+
236
+ // src/lib/getTodoProgress.ts
237
+ import { readFileSync as readFileSync2 } from "fs";
238
+ import { join as join2 } from "path";
239
+ var ralphDir = join2(process.cwd(), ".ralph");
240
+ var todoFile = join2(ralphDir, "todo.md");
241
+ var getTodoProgress = () => {
242
+ try {
243
+ const content = readFileSync2(todoFile, "utf-8");
244
+ const uncheckedMatches = content.match(/- \[ \]/g);
245
+ const unchecked = uncheckedMatches ? uncheckedMatches.length : 0;
246
+ const checkedMatches = content.match(/- \[[xX]\]/g);
247
+ const checked = checkedMatches ? checkedMatches.length : 0;
248
+ const total = unchecked + checked;
249
+ return { type: "todo", completed: checked, total };
250
+ } catch {
251
+ return { type: "none", completed: 0, total: 0 };
252
+ }
253
+ };
254
+
255
+ // src/lib/getProgress.ts
256
+ var beadsDir = join3(process.cwd(), ".beads");
257
+ var ralphDir2 = join3(process.cwd(), ".ralph");
258
+ var todoFile2 = join3(ralphDir2, "todo.md");
259
+ var getProgress = (initialCount, startupTimestamp) => {
260
+ if (existsSync2(beadsDir)) {
261
+ return getBeadsProgress(initialCount, startupTimestamp);
262
+ }
263
+ if (existsSync2(todoFile2)) {
264
+ return getTodoProgress();
265
+ }
266
+ return { type: "none", completed: 0, total: 0 };
267
+ };
268
+
269
+ // src/lib/captureStartupSnapshot.ts
270
+ import { existsSync as existsSync3 } from "fs";
271
+ import { join as join5 } from "path";
272
+
273
+ // src/lib/captureBeadsSnapshot.ts
274
+ import { execSync as execSync3 } from "child_process";
275
+ var captureBeadsSnapshot = () => {
276
+ try {
277
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
278
+ const openCount = parseInt(
279
+ execSync3("bd count --status=open", {
280
+ encoding: "utf-8",
281
+ stdio: ["pipe", "pipe", "pipe"]
282
+ }).trim(),
283
+ 10
284
+ );
285
+ const inProgressCount = parseInt(
286
+ execSync3("bd count --status=in_progress", {
287
+ encoding: "utf-8",
288
+ stdio: ["pipe", "pipe", "pipe"]
289
+ }).trim(),
290
+ 10
291
+ );
292
+ return {
293
+ initialCount: openCount + inProgressCount,
294
+ timestamp,
295
+ type: "beads"
296
+ };
297
+ } catch {
298
+ return void 0;
299
+ }
300
+ };
301
+
302
+ // src/lib/captureTodoSnapshot.ts
303
+ import { readFileSync as readFileSync3 } from "fs";
304
+ import { join as join4 } from "path";
305
+ var ralphDir3 = join4(process.cwd(), ".ralph");
306
+ var todoFile3 = join4(ralphDir3, "todo.md");
307
+ var captureTodoSnapshot = () => {
308
+ try {
309
+ const content = readFileSync3(todoFile3, "utf-8");
310
+ const uncheckedMatches = content.match(/- \[ \]/g);
311
+ const unchecked = uncheckedMatches ? uncheckedMatches.length : 0;
312
+ const checkedMatches = content.match(/- \[[xX]\]/g);
313
+ const checked = checkedMatches ? checkedMatches.length : 0;
314
+ return {
315
+ initialCount: unchecked + checked,
316
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
317
+ type: "todo"
318
+ };
319
+ } catch {
320
+ return void 0;
321
+ }
322
+ };
323
+
324
+ // src/lib/captureStartupSnapshot.ts
325
+ var beadsDir2 = join5(process.cwd(), ".beads");
326
+ var ralphDir4 = join5(process.cwd(), ".ralph");
327
+ var todoFile4 = join5(ralphDir4, "todo.md");
328
+ var captureStartupSnapshot = () => {
329
+ if (existsSync3(beadsDir2)) {
330
+ return captureBeadsSnapshot();
331
+ }
332
+ if (existsSync3(todoFile4)) {
333
+ return captureTodoSnapshot();
334
+ }
335
+ return void 0;
336
+ };
337
+
338
+ // src/components/ProgressBar.tsx
339
+ import React2 from "react";
340
+ import { Text as Text2 } from "ink";
341
+ var ProgressBar = ({ completed, total, width = 12, repoName: repoName3 }) => {
342
+ if (total === 0) {
343
+ return null;
344
+ }
345
+ const progress = Math.min(1, Math.max(0, completed / total));
346
+ const filledWidth = Math.round(progress * width);
347
+ const emptyWidth = width - filledWidth;
348
+ const filled = "\u25B0".repeat(filledWidth);
349
+ const empty = "\u25B1".repeat(emptyWidth);
350
+ return /* @__PURE__ */ React2.createElement(Text2, null, repoName3 && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, repoName3), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " \u2502 ")), /* @__PURE__ */ React2.createElement(Text2, { color: "yellow" }, filled), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, empty), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " ", completed, "/", total, " "));
351
+ };
352
+
353
+ // src/lib/beadsClient.ts
354
+ import { createConnection } from "net";
355
+ import { join as join6 } from "path";
356
+ import { existsSync as existsSync4 } from "fs";
357
+ var SOCKET_PATH = join6(process.cwd(), ".beads", "bd.sock");
358
+ var BeadsClient = class _BeadsClient {
359
+ socket = null;
360
+ connected = false;
361
+ /**
362
+ * Check if the beads daemon socket exists.
363
+ */
364
+ static socketExists() {
365
+ return existsSync4(SOCKET_PATH);
366
+ }
367
+ /**
368
+ * Connect to the beads daemon.
369
+ */
370
+ async connect() {
371
+ if (!_BeadsClient.socketExists()) {
372
+ return false;
373
+ }
374
+ return new Promise((resolve) => {
375
+ this.socket = createConnection(SOCKET_PATH);
376
+ const timeout = setTimeout(() => {
377
+ this.socket?.destroy();
378
+ this.socket = null;
379
+ resolve(false);
380
+ }, 2e3);
381
+ this.socket.on("connect", () => {
382
+ clearTimeout(timeout);
383
+ this.connected = true;
384
+ resolve(true);
385
+ });
386
+ this.socket.on("error", () => {
387
+ clearTimeout(timeout);
388
+ this.socket = null;
389
+ resolve(false);
390
+ });
391
+ });
392
+ }
393
+ /**
394
+ * Send an RPC request and wait for response.
395
+ */
396
+ async execute(operation, args = {}) {
397
+ if (!this.socket || !this.connected) {
398
+ return null;
399
+ }
400
+ return new Promise((resolve) => {
401
+ const request = { operation, args };
402
+ const requestLine = JSON.stringify(request) + "\n";
403
+ let responseData = "";
404
+ const onData = (chunk) => {
405
+ responseData += chunk.toString();
406
+ if (responseData.includes("\n")) {
407
+ cleanup();
408
+ try {
409
+ const response = JSON.parse(responseData.trim());
410
+ if (response.success && response.data) {
411
+ resolve(response.data);
412
+ } else {
413
+ resolve(null);
414
+ }
415
+ } catch {
416
+ resolve(null);
417
+ }
418
+ }
419
+ };
420
+ const onError = () => {
421
+ cleanup();
422
+ resolve(null);
423
+ };
424
+ const timeout = setTimeout(() => {
425
+ cleanup();
426
+ resolve(null);
427
+ }, 5e3);
428
+ const cleanup = () => {
429
+ clearTimeout(timeout);
430
+ this.socket?.off("data", onData);
431
+ this.socket?.off("error", onError);
432
+ };
433
+ this.socket.on("data", onData);
434
+ this.socket.on("error", onError);
435
+ this.socket.write(requestLine);
436
+ });
437
+ }
438
+ /**
439
+ * Get mutations since a given timestamp.
440
+ */
441
+ async getMutations(since = 0) {
442
+ const result = await this.execute("get_mutations", { since });
443
+ return result ?? [];
444
+ }
445
+ /**
446
+ * Get ready issues (no blockers).
447
+ */
448
+ async getReady() {
449
+ const result = await this.execute("ready", {});
450
+ return result ?? [];
451
+ }
452
+ /**
453
+ * Close the connection.
454
+ */
455
+ close() {
456
+ if (this.socket) {
457
+ this.socket.destroy();
458
+ this.socket = null;
459
+ this.connected = false;
460
+ }
461
+ }
462
+ };
463
+ function watchForNewIssues(onNewIssue, interval = 5e3) {
464
+ let lastTimestamp = Date.now();
465
+ let client = null;
466
+ let timeoutId = null;
467
+ let stopped = false;
468
+ const poll = async () => {
469
+ if (stopped) return;
470
+ if (!client) {
471
+ client = new BeadsClient();
472
+ const connected = await client.connect();
473
+ if (!connected) {
474
+ timeoutId = setTimeout(poll, interval);
475
+ return;
476
+ }
477
+ }
478
+ try {
479
+ const mutations = await client.getMutations(lastTimestamp);
480
+ for (const mutation of mutations) {
481
+ if (mutation.Type === "create") {
482
+ onNewIssue(mutation);
483
+ }
484
+ const mutationTime = new Date(mutation.Timestamp).getTime();
485
+ if (mutationTime > lastTimestamp) {
486
+ lastTimestamp = mutationTime;
487
+ }
488
+ }
489
+ } catch {
490
+ client?.close();
491
+ client = null;
492
+ }
493
+ if (!stopped) {
494
+ timeoutId = setTimeout(poll, interval);
495
+ }
496
+ };
497
+ poll();
498
+ return () => {
499
+ stopped = true;
500
+ if (timeoutId) {
501
+ clearTimeout(timeoutId);
502
+ }
503
+ client?.close();
504
+ };
505
+ }
506
+
507
+ // src/lib/debug.ts
508
+ var isDebugEnabled = (namespace) => {
509
+ const debugEnv = process.env.RALPH_DEBUG;
510
+ if (!debugEnv) return false;
511
+ const value = debugEnv.toLowerCase();
512
+ if (value === "1" || value === "true" || value === "all" || value === "*") {
513
+ return true;
514
+ }
515
+ if (namespace && value === namespace.toLowerCase()) {
516
+ return true;
517
+ }
518
+ if (namespace && value.includes(",")) {
519
+ return value.split(",").some((ns) => ns.trim().toLowerCase() === namespace.toLowerCase());
520
+ }
521
+ return false;
522
+ };
523
+ var debug = (namespace, message, ...args) => {
524
+ if (isDebugEnabled(namespace)) {
525
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
526
+ const prefix = `[${timestamp}] [RALPH:${namespace.toUpperCase()}]`;
527
+ console.error(prefix, message, ...args);
528
+ }
529
+ };
530
+ var createDebugLogger = (namespace) => {
531
+ return (message, ...args) => debug(namespace, message, ...args);
532
+ };
533
+
534
+ // src/lib/createUserMessage.ts
535
+ var createUserMessage = (text) => ({
536
+ type: "user",
537
+ session_id: "",
538
+ message: {
539
+ role: "user",
540
+ content: [{ type: "text", text }]
541
+ },
542
+ parent_tool_use_id: null
543
+ });
544
+
545
+ // src/lib/MessageQueue.ts
546
+ var log = createDebugLogger("messagequeue");
547
+ var MessageQueue = class {
548
+ queue = [];
549
+ resolvers = [];
550
+ closed = false;
551
+ nextCallCount = 0;
552
+ /**
553
+ * Push a message to the queue. If there are pending resolvers waiting for the next message,
554
+ * resolve immediately. Otherwise, add to queue.
555
+ */
556
+ push(message) {
557
+ const messagePreview = this.getMessagePreview(message);
558
+ log(`push() called with message: ${messagePreview}`);
559
+ if (this.closed) {
560
+ log(`push() ignored - queue is closed`);
561
+ return;
562
+ }
563
+ if (this.resolvers.length > 0) {
564
+ const resolve = this.resolvers.shift();
565
+ log(`push() resolving pending next() call (${this.resolvers.length} resolvers remaining)`);
566
+ resolve({ value: message, done: false });
567
+ } else {
568
+ this.queue.push(message);
569
+ log(`push() added to queue (queue length: ${this.queue.length})`);
570
+ }
571
+ }
572
+ /**
573
+ * Close the queue. Resolve any pending resolvers with done=true.
574
+ */
575
+ close() {
576
+ if (this.closed) {
577
+ log(`close() called but already closed - no-op`);
578
+ return;
579
+ }
580
+ log(`close() called - resolving ${this.resolvers.length} pending resolvers`);
581
+ this.closed = true;
582
+ for (const resolve of this.resolvers) {
583
+ log(`close() resolving pending resolver with done=true`);
584
+ resolve({ value: void 0, done: true });
585
+ }
586
+ this.resolvers = [];
587
+ log(`close() complete`);
588
+ }
589
+ /**
590
+ * Get a preview string of a message for debug logging.
591
+ */
592
+ getMessagePreview(message) {
593
+ const content = message.message?.content;
594
+ if (Array.isArray(content) && content.length > 0) {
595
+ const firstBlock = content[0];
596
+ if ("text" in firstBlock && typeof firstBlock.text === "string") {
597
+ const text = firstBlock.text.slice(0, 50);
598
+ return text.length < firstBlock.text.length ? `"${text}..."` : `"${text}"`;
599
+ }
600
+ }
601
+ return `[${message.type} message]`;
602
+ }
603
+ /**
604
+ * Implement the async iterable protocol to allow session with for-await-of.
605
+ */
606
+ [Symbol.asyncIterator]() {
607
+ return {
608
+ next: () => {
609
+ this.nextCallCount++;
610
+ const callId = this.nextCallCount;
611
+ if (this.queue.length > 0) {
612
+ const message = this.queue.shift();
613
+ log(`next() #${callId}: returning queued message (${this.queue.length} remaining)`);
614
+ return Promise.resolve({ value: message, done: false });
615
+ }
616
+ if (this.closed) {
617
+ log(`next() #${callId}: queue closed, returning done=true`);
618
+ return Promise.resolve({ value: void 0, done: true });
619
+ }
620
+ log(
621
+ `next() #${callId}: queue empty, creating pending resolver (${this.resolvers.length + 1} total)`
622
+ );
623
+ return new Promise((resolve) => {
624
+ this.resolvers.push(resolve);
625
+ });
626
+ }
627
+ };
628
+ }
629
+ };
630
+
631
+ // src/lib/useTerminalSize.ts
632
+ import { useState as useState2, useEffect as useEffect2 } from "react";
633
+ import { useStdout } from "ink";
634
+
635
+ // src/lib/getTerminalSize.ts
636
+ function getTerminalSize(stdout) {
637
+ return {
638
+ columns: stdout?.columns ?? 80,
639
+ rows: stdout?.rows ?? 24
640
+ };
641
+ }
642
+
643
+ // src/lib/useTerminalSize.ts
644
+ var useTerminalSize = () => {
645
+ const { stdout } = useStdout();
646
+ const [size, setSize] = useState2(() => getTerminalSize(stdout));
647
+ useEffect2(() => {
648
+ const handleResize = () => {
649
+ setSize(getTerminalSize(stdout));
650
+ };
651
+ stdout?.on("resize", handleResize);
652
+ return () => {
653
+ stdout?.off("resize", handleResize);
654
+ };
655
+ }, [stdout]);
656
+ return size;
657
+ };
658
+
659
+ // src/lib/getNextLogFile.ts
660
+ import { existsSync as existsSync6, mkdirSync } from "fs";
661
+ import { join as join8 } from "path";
662
+
663
+ // src/lib/findMaxLogNumber.ts
664
+ import { readdirSync, existsSync as existsSync5 } from "fs";
665
+ import { join as join7 } from "path";
666
+ var EVENT_LOG_PATTERN = /^events-(\d+)\.jsonl$/;
667
+ var findMaxLogNumber = () => {
668
+ const ralphDir6 = join7(process.cwd(), ".ralph");
669
+ if (!existsSync5(ralphDir6)) {
670
+ return 0;
671
+ }
672
+ const files = readdirSync(ralphDir6);
673
+ let maxNumber = 0;
674
+ for (const file of files) {
675
+ const match = file.match(EVENT_LOG_PATTERN);
676
+ if (match) {
677
+ const num = parseInt(match[1], 10);
678
+ if (num > maxNumber) {
679
+ maxNumber = num;
680
+ }
681
+ }
682
+ }
683
+ return maxNumber;
684
+ };
685
+
686
+ // src/lib/getNextLogFile.ts
687
+ var getNextLogFile = () => {
688
+ const ralphDir6 = join8(process.cwd(), ".ralph");
689
+ if (!existsSync6(ralphDir6)) {
690
+ mkdirSync(ralphDir6, { recursive: true });
691
+ }
692
+ const maxNumber = findMaxLogNumber();
693
+ return join8(ralphDir6, `events-${maxNumber + 1}.jsonl`);
694
+ };
695
+
696
+ // src/lib/parseTaskLifecycle.ts
697
+ function parseTaskLifecycleEvent(text) {
698
+ const startingMatch = text.match(/<start_task>([a-z]+-[a-z0-9]+(?:\.[a-z0-9]+)*)<\/start_task>/i);
699
+ if (startingMatch) {
700
+ return {
701
+ action: "starting",
702
+ taskId: startingMatch[1]
703
+ };
704
+ }
705
+ const completedMatch = text.match(/<end_task>([a-z]+-[a-z0-9]+(?:\.[a-z0-9]+)*)<\/end_task>/i);
706
+ if (completedMatch) {
707
+ return {
708
+ action: "completed",
709
+ taskId: completedMatch[1]
710
+ };
711
+ }
712
+ return null;
713
+ }
714
+
715
+ // src/lib/getPromptContent.ts
716
+ import { readFileSync as readFileSync4, existsSync as existsSync7 } from "fs";
717
+ import { join as join9, dirname } from "path";
718
+ import { fileURLToPath } from "url";
719
+ var getPromptContent = () => {
720
+ const __dirname = dirname(fileURLToPath(import.meta.url));
721
+ const ralphDir6 = join9(process.cwd(), ".ralph");
722
+ const promptFile = join9(ralphDir6, "prompt.md");
723
+ const todoFile7 = join9(ralphDir6, "todo.md");
724
+ const beadsDir3 = join9(process.cwd(), ".beads");
725
+ const templatesDir = join9(__dirname, "..", "..", "templates");
726
+ if (existsSync7(promptFile)) {
727
+ return readFileSync4(promptFile, "utf-8");
728
+ }
729
+ const useBeadsTemplate = existsSync7(beadsDir3) || !existsSync7(todoFile7);
730
+ const templateFile = useBeadsTemplate ? "prompt-beads.md" : "prompt-todos.md";
731
+ const templatePath = join9(templatesDir, templateFile);
732
+ if (existsSync7(templatePath)) {
733
+ return readFileSync4(templatePath, "utf-8");
734
+ }
735
+ return "Work on the highest-priority task.";
736
+ };
737
+
738
+ // src/lib/sdkMessageToEvent.ts
739
+ var sdkMessageToEvent = (message) => {
740
+ if (message.type === "assistant" || message.type === "user" || message.type === "result") {
741
+ return message;
742
+ }
743
+ return null;
744
+ };
745
+
746
+ // src/lib/rel.ts
747
+ import { isAbsolute, relative } from "path";
748
+
749
+ // src/lib/getBaseCwd.ts
750
+ var getBaseCwd = () => process.env.RALPH_CWD ?? process.cwd();
751
+
752
+ // src/lib/rel.ts
753
+ var rel = (path) => {
754
+ if (!isAbsolute(path)) {
755
+ return path;
756
+ }
757
+ if (path.includes("/var/folders/") || path.includes("/tmp/")) {
758
+ return path.split("/").pop() || path;
759
+ }
760
+ return relative(getBaseCwd(), path) || path;
761
+ };
762
+
763
+ // src/lib/shortenTempPaths.ts
764
+ var shortenTempPaths = (text) => {
765
+ return text.replace(/\/var\/folders\/[^\s]+/g, (match) => match.split("/").pop() || match).replace(/\/tmp\/[^\s]+/g, (match) => match.split("/").pop() || match);
766
+ };
767
+
768
+ // src/components/eventToBlocks.ts
769
+ var eventToBlocks = (event) => {
770
+ if (event.type === "user") {
771
+ const message2 = event.message;
772
+ const content2 = message2?.content;
773
+ const messageId2 = message2?.id ?? `user-${Date.now()}`;
774
+ if (!content2) {
775
+ return [];
776
+ }
777
+ const textContent = content2.filter((block) => block.type === "text").map((block) => block.text).join("");
778
+ if (textContent) {
779
+ return [{ type: "user", content: textContent, id: messageId2 }];
780
+ }
781
+ return [];
782
+ }
783
+ if (event.type !== "assistant") {
784
+ return [];
785
+ }
786
+ const message = event.message;
787
+ const content = message?.content;
788
+ if (!content) {
789
+ return [];
790
+ }
791
+ const blocks = [];
792
+ const messageId = message?.id ?? "unknown";
793
+ let blockIndex = 0;
794
+ let textBuffer = "";
795
+ for (const block of content) {
796
+ if (block.type === "text") {
797
+ const text = block.text;
798
+ if (text) {
799
+ textBuffer += text;
800
+ }
801
+ } else if (block.type === "tool_use") {
802
+ if (textBuffer) {
803
+ blocks.push({ type: "text", content: textBuffer, id: `${messageId}-${blockIndex++}` });
804
+ textBuffer = "";
805
+ }
806
+ const input = block.input;
807
+ const name = block.name;
808
+ if (name === "Read") {
809
+ const filePath = input?.file_path;
810
+ if (filePath) {
811
+ blocks.push({
812
+ type: "tool",
813
+ name: "Read",
814
+ arg: rel(filePath),
815
+ id: `${messageId}-${blockIndex++}`
816
+ });
817
+ }
818
+ } else if (name === "Edit" || name === "Write") {
819
+ const filePath = input?.file_path;
820
+ if (filePath) {
821
+ blocks.push({
822
+ type: "tool",
823
+ name,
824
+ arg: rel(filePath),
825
+ id: `${messageId}-${blockIndex++}`
826
+ });
827
+ }
828
+ } else if (name === "Bash") {
829
+ const command = input?.command;
830
+ if (command) {
831
+ blocks.push({
832
+ type: "tool",
833
+ name: "$",
834
+ arg: shortenTempPaths(command),
835
+ id: `${messageId}-${blockIndex++}`
836
+ });
837
+ }
838
+ } else if (name === "Grep") {
839
+ const pattern = input?.pattern;
840
+ const path = input?.path;
841
+ blocks.push({
842
+ type: "tool",
843
+ name: "Grep",
844
+ arg: `${pattern}${path ? ` in ${rel(path)}` : ""}`,
845
+ id: `${messageId}-${blockIndex++}`
846
+ });
847
+ } else if (name === "Glob") {
848
+ const pattern = input?.pattern;
849
+ const path = input?.path;
850
+ blocks.push({
851
+ type: "tool",
852
+ name: "Glob",
853
+ arg: `${pattern}${path ? ` in ${rel(path)}` : ""}`,
854
+ id: `${messageId}-${blockIndex++}`
855
+ });
856
+ } else if (name === "TodoWrite") {
857
+ const todos = input?.todos;
858
+ if (todos?.length) {
859
+ const summary = todos.map(
860
+ (t) => `[${t.status === "completed" ? "x" : t.status === "in_progress" ? "~" : " "}] ${t.content}`
861
+ ).join("\n ");
862
+ blocks.push({
863
+ type: "tool",
864
+ name: "TodoWrite",
865
+ arg: "\n " + summary,
866
+ id: `${messageId}-${blockIndex++}`
867
+ });
868
+ } else {
869
+ blocks.push({ type: "tool", name: "TodoWrite", id: `${messageId}-${blockIndex++}` });
870
+ }
871
+ } else if (name === "WebFetch") {
872
+ const url = input?.url;
873
+ blocks.push({
874
+ type: "tool",
875
+ name: "WebFetch",
876
+ arg: url,
877
+ id: `${messageId}-${blockIndex++}`
878
+ });
879
+ } else if (name === "WebSearch") {
880
+ const query3 = input?.query;
881
+ blocks.push({
882
+ type: "tool",
883
+ name: "WebSearch",
884
+ arg: query3,
885
+ id: `${messageId}-${blockIndex++}`
886
+ });
887
+ } else if (name === "Task") {
888
+ const description = input?.description;
889
+ blocks.push({
890
+ type: "tool",
891
+ name: "Task",
892
+ arg: description,
893
+ id: `${messageId}-${blockIndex++}`
894
+ });
895
+ } else if (name === "Skill") {
896
+ const skill = input?.skill;
897
+ blocks.push({ type: "tool", name: "Skill", arg: skill, id: `${messageId}-${blockIndex++}` });
898
+ }
899
+ }
900
+ }
901
+ if (textBuffer) {
902
+ blocks.push({ type: "text", content: textBuffer, id: `${messageId}-${blockIndex++}` });
903
+ }
904
+ return blocks;
905
+ };
906
+
907
+ // src/lib/processEvents.ts
908
+ var processEvents = (events) => {
909
+ const blocks = [];
910
+ const assistantEvents = events.filter((event) => event.type === "assistant");
911
+ const messageMap = /* @__PURE__ */ new Map();
912
+ for (const event of assistantEvents) {
913
+ const message = event.message;
914
+ const messageId = message?.id;
915
+ const content = message?.content;
916
+ if (messageId && content) {
917
+ if (!messageMap.has(messageId)) {
918
+ messageMap.set(messageId, []);
919
+ }
920
+ messageMap.get(messageId).push(...content);
921
+ }
922
+ }
923
+ const mergedEvents = Array.from(messageMap.entries()).map(([messageId, allContent]) => {
924
+ const seenBlocks = /* @__PURE__ */ new Set();
925
+ const uniqueContent = [];
926
+ for (const block of allContent) {
927
+ const blockType = block.type;
928
+ let blockKey;
929
+ if (blockType === "tool_use") {
930
+ blockKey = `tool:${block.id}`;
931
+ } else if (blockType === "text") {
932
+ const text = block.text;
933
+ let isDuplicate = false;
934
+ for (const seenKey of seenBlocks) {
935
+ if (seenKey.startsWith("text:")) {
936
+ const seenText = seenKey.substring(5);
937
+ if (seenText.startsWith(text)) {
938
+ isDuplicate = true;
939
+ break;
940
+ } else if (text.startsWith(seenText)) {
941
+ seenBlocks.delete(seenKey);
942
+ const idx = uniqueContent.findIndex((b) => b.type === "text" && b.text === seenText);
943
+ if (idx >= 0) uniqueContent.splice(idx, 1);
944
+ break;
945
+ }
946
+ }
947
+ }
948
+ if (isDuplicate) continue;
949
+ blockKey = `text:${text}`;
950
+ } else {
951
+ blockKey = JSON.stringify(block);
952
+ }
953
+ if (!seenBlocks.has(blockKey)) {
954
+ seenBlocks.add(blockKey);
955
+ uniqueContent.push(block);
956
+ }
957
+ }
958
+ return {
959
+ type: "assistant",
960
+ message: {
961
+ id: messageId,
962
+ content: uniqueContent
963
+ }
964
+ };
965
+ });
966
+ const assistantBlocks = mergedEvents.flatMap((event) => eventToBlocks(event));
967
+ const processedUserIds = /* @__PURE__ */ new Set();
968
+ const processedAssistantIds = /* @__PURE__ */ new Set();
969
+ for (const event of events) {
970
+ if (event.type === "user") {
971
+ const message = event.message;
972
+ const messageId = message?.id ?? `user-${Date.now()}`;
973
+ if (!processedUserIds.has(messageId)) {
974
+ processedUserIds.add(messageId);
975
+ blocks.push(...eventToBlocks(event));
976
+ }
977
+ } else if (event.type === "assistant") {
978
+ const message = event.message;
979
+ const messageId = message?.id;
980
+ if (messageId && !processedAssistantIds.has(messageId)) {
981
+ processedAssistantIds.add(messageId);
982
+ const merged = assistantBlocks.filter((b) => b.id.startsWith(messageId));
983
+ blocks.push(...merged);
984
+ }
985
+ }
986
+ }
987
+ return blocks;
988
+ };
989
+
990
+ // src/components/renderStaticItem.tsx
991
+ import React4 from "react";
992
+ import { Box as Box2, Text as Text4 } from "ink";
993
+ import BigText2 from "ink-big-text";
994
+ import Gradient2 from "ink-gradient";
995
+
996
+ // src/components/Header.tsx
997
+ import React3 from "react";
998
+ import { Box, Text as Text3 } from "ink";
999
+ import BigText from "ink-big-text";
1000
+ import Gradient from "ink-gradient";
1001
+ var Header = ({
1002
+ /** The Claude CLI version */
1003
+ claudeVersion,
1004
+ /** The Ralph version */
1005
+ ralphVersion,
1006
+ /** Optional box width */
1007
+ width
1008
+ }) => {
1009
+ return /* @__PURE__ */ React3.createElement(
1010
+ Box,
1011
+ {
1012
+ flexDirection: "column",
1013
+ marginBottom: 1,
1014
+ borderStyle: "single",
1015
+ alignItems: "center",
1016
+ width,
1017
+ paddingX: 2
1018
+ },
1019
+ /* @__PURE__ */ React3.createElement(Gradient, { colors: ["#30A6E4", "#EBC635"] }, /* @__PURE__ */ React3.createElement(BigText, { text: "Ralph", font: "tiny" })),
1020
+ /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "@herbcaudill/ralph v", ralphVersion, " \u2022 Claude Code v", claudeVersion)
1021
+ );
1022
+ };
1023
+
1024
+ // src/lib/formatText.ts
1025
+ import chalk2 from "chalk";
1026
+ var formatText = (content) => {
1027
+ let result = "";
1028
+ let i = 0;
1029
+ let inBold = false;
1030
+ let inCode = false;
1031
+ while (i < content.length) {
1032
+ if (content[i] === "*" && content[i + 1] === "*") {
1033
+ inBold = !inBold;
1034
+ i += 2;
1035
+ } else if (content[i] === "`") {
1036
+ inCode = !inCode;
1037
+ i++;
1038
+ } else {
1039
+ let char = content[i];
1040
+ if (inCode) {
1041
+ char = chalk2.yellow(char);
1042
+ } else if (inBold) {
1043
+ char = chalk2.bold(char);
1044
+ }
1045
+ result += char;
1046
+ i++;
1047
+ }
1048
+ }
1049
+ return result;
1050
+ };
1051
+
1052
+ // src/lib/formatToolUse.ts
1053
+ import chalk3 from "chalk";
1054
+ var formatToolUse = (name, arg) => {
1055
+ const formattedName = chalk3.blue(name);
1056
+ if (arg) {
1057
+ return ` ${formattedName} ${chalk3.dim(arg)}`;
1058
+ }
1059
+ return ` ${formattedName}`;
1060
+ };
1061
+
1062
+ // src/lib/formatUserMessage.ts
1063
+ import chalk4 from "chalk";
1064
+ var formatUserMessage = (content) => {
1065
+ return chalk4.green(content);
1066
+ };
1067
+
1068
+ // src/lib/formatContentBlock.ts
1069
+ var formatContentBlock = (block) => {
1070
+ if (block.type === "text") {
1071
+ const formatted = formatText(block.content);
1072
+ return formatted.split("\n");
1073
+ }
1074
+ if (block.type === "user") {
1075
+ return [formatUserMessage(block.content)];
1076
+ }
1077
+ return [formatToolUse(block.name, block.arg)];
1078
+ };
1079
+
1080
+ // src/components/renderStaticItem.tsx
1081
+ var renderStaticItem = (item) => {
1082
+ if (item.type === "header") {
1083
+ return /* @__PURE__ */ React4.createElement(Header, { claudeVersion: item.claudeVersion, ralphVersion: item.ralphVersion });
1084
+ }
1085
+ if (item.type === "session") {
1086
+ return /* @__PURE__ */ React4.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Gradient2, { colors: ["#30A6E4", "#EBC635"] }, /* @__PURE__ */ React4.createElement(BigText2, { text: `R${item.session}`, font: "tiny" })));
1087
+ }
1088
+ const lines = formatContentBlock(item.block);
1089
+ return /* @__PURE__ */ React4.createElement(Box2, { flexDirection: "column", marginBottom: 1 }, lines.map((line, i) => /* @__PURE__ */ React4.createElement(Text4, { key: i }, line || " ")));
1090
+ };
1091
+
1092
+ // src/components/SessionRunner.tsx
1093
+ var log2 = createDebugLogger("session");
1094
+ var ralphDir5 = join10(process.cwd(), ".ralph");
1095
+ var todoFile5 = join10(ralphDir5, "todo.md");
1096
+ var repoName = basename(process.cwd());
1097
+ var SessionRunner = ({
1098
+ totalSessions,
1099
+ claudeVersion,
1100
+ ralphVersion,
1101
+ watch,
1102
+ agent
1103
+ }) => {
1104
+ const { exit } = useApp();
1105
+ const { columns } = useTerminalSize();
1106
+ const [currentSession, setCurrentSession] = useState3(1);
1107
+ const [events, setEvents] = useState3([]);
1108
+ const eventsRef = useRef([]);
1109
+ const [error, setError] = useState3();
1110
+ const [isRunning, setIsRunning] = useState3(false);
1111
+ const [isAddingTodo, setIsAddingTodo] = useState3(false);
1112
+ const [todoText, setTodoText] = useState3("");
1113
+ const [todoMessage, setTodoMessage] = useState3(null);
1114
+ const [userMessageText, setUserMessageText] = useState3("");
1115
+ const [userMessageStatus, setUserMessageStatus] = useState3(null);
1116
+ const [hasTodoFile, setHasTodoFile] = useState3(false);
1117
+ const [startupSnapshot] = useState3(() => captureStartupSnapshot());
1118
+ const [progressData, setProgressData] = useState3(() => {
1119
+ const snapshot = captureStartupSnapshot();
1120
+ if (!snapshot) return { type: "none", completed: 0, total: 0 };
1121
+ return { type: snapshot.type, completed: 0, total: snapshot.initialCount };
1122
+ });
1123
+ const [isWatching, setIsWatching] = useState3(false);
1124
+ const [detectedIssue, setDetectedIssue] = useState3(null);
1125
+ const watchCleanupRef = useRef(null);
1126
+ const [watchCycle, setWatchCycle] = useState3(0);
1127
+ const [stopAfterCurrent, setStopAfterCurrent] = useState3(false);
1128
+ const stopAfterCurrentRef = useRef(false);
1129
+ const [isPaused, setIsPaused] = useState3(false);
1130
+ const isPausedRef = useRef(false);
1131
+ const [currentTaskId, setCurrentTaskId] = useState3(null);
1132
+ const [currentTaskTitle, setCurrentTaskTitle] = useState3(null);
1133
+ const [staticItems, setStaticItems] = useState3([
1134
+ { type: "header", claudeVersion, ralphVersion, key: "header" }
1135
+ ]);
1136
+ const renderedBlocksRef = useRef(/* @__PURE__ */ new Set());
1137
+ const lastSessionRef = useRef(0);
1138
+ const messageQueueRef = useRef(null);
1139
+ const logFileRef = useRef(null);
1140
+ const stdinSupportsRawMode = process.stdin.isTTY === true;
1141
+ const handleTodoSubmit = (text) => {
1142
+ const trimmed = text.trim();
1143
+ if (!trimmed) {
1144
+ setIsAddingTodo(false);
1145
+ setTodoText("");
1146
+ return;
1147
+ }
1148
+ try {
1149
+ addTodo(trimmed);
1150
+ setTodoMessage({ type: "success", text: "\u2705 added" });
1151
+ setTodoText("");
1152
+ setIsAddingTodo(false);
1153
+ setTimeout(() => setTodoMessage(null), 2e3);
1154
+ } catch (err) {
1155
+ setTodoMessage({
1156
+ type: "error",
1157
+ text: `Failed to add todo: ${err instanceof Error ? err.message : String(err)}`
1158
+ });
1159
+ setTodoText("");
1160
+ setIsAddingTodo(false);
1161
+ setTimeout(() => setTodoMessage(null), 5e3);
1162
+ }
1163
+ };
1164
+ const handleUserMessageSubmit = (text) => {
1165
+ const trimmed = text.trim();
1166
+ if (!trimmed) {
1167
+ setUserMessageText("");
1168
+ return;
1169
+ }
1170
+ if (messageQueueRef.current && isRunning) {
1171
+ const userMessage = createUserMessage(trimmed);
1172
+ messageQueueRef.current.push(userMessage);
1173
+ const displayEvent = {
1174
+ type: "user",
1175
+ message: {
1176
+ id: `user-injected-${Date.now()}`,
1177
+ role: "user",
1178
+ content: [{ type: "text", text: trimmed }]
1179
+ }
1180
+ };
1181
+ setEvents((prev) => [...prev, displayEvent]);
1182
+ } else {
1183
+ setUserMessageStatus({
1184
+ type: "error",
1185
+ text: "Unable to send message - Claude is not running"
1186
+ });
1187
+ }
1188
+ setUserMessageText("");
1189
+ setTimeout(() => setUserMessageStatus(null), 3e3);
1190
+ };
1191
+ useInput2(
1192
+ (input, key) => {
1193
+ if (key.ctrl && input === "t" && hasTodoFile) {
1194
+ setIsAddingTodo(true);
1195
+ setTodoText("");
1196
+ setTodoMessage(null);
1197
+ }
1198
+ if (key.ctrl && input === "s" && isRunning && !stopAfterCurrent) {
1199
+ setStopAfterCurrent(true);
1200
+ }
1201
+ if (key.ctrl && input === "p") {
1202
+ if (isPaused) {
1203
+ setIsPaused(false);
1204
+ if (!isRunning) {
1205
+ setTimeout(() => setCurrentSession((i) => i + 1), 100);
1206
+ }
1207
+ } else if (isRunning) {
1208
+ setIsPaused(true);
1209
+ }
1210
+ }
1211
+ if (key.escape) {
1212
+ if (isAddingTodo) {
1213
+ setIsAddingTodo(false);
1214
+ setTodoText("");
1215
+ }
1216
+ }
1217
+ },
1218
+ { isActive: stdinSupportsRawMode }
1219
+ );
1220
+ useEffect3(() => {
1221
+ eventsRef.current = events;
1222
+ }, [events]);
1223
+ useEffect3(() => {
1224
+ stopAfterCurrentRef.current = stopAfterCurrent;
1225
+ }, [stopAfterCurrent]);
1226
+ useEffect3(() => {
1227
+ isPausedRef.current = isPaused;
1228
+ }, [isPaused]);
1229
+ useEffect3(() => {
1230
+ if (!isRunning && startupSnapshot) {
1231
+ setProgressData(getProgress(startupSnapshot.initialCount, startupSnapshot.timestamp));
1232
+ }
1233
+ }, [currentSession, isRunning, startupSnapshot]);
1234
+ useEffect3(() => {
1235
+ if (!isRunning || !startupSnapshot) return;
1236
+ const pollInterval = setInterval(() => {
1237
+ setProgressData(getProgress(startupSnapshot.initialCount, startupSnapshot.timestamp));
1238
+ }, 5e3);
1239
+ return () => clearInterval(pollInterval);
1240
+ }, [isRunning, startupSnapshot]);
1241
+ useEffect3(() => {
1242
+ if (!isWatching) return;
1243
+ const cleanup = watchForNewIssues((issue) => {
1244
+ setDetectedIssue(issue);
1245
+ if (startupSnapshot) {
1246
+ setProgressData(getProgress(startupSnapshot.initialCount, startupSnapshot.timestamp));
1247
+ }
1248
+ setTimeout(() => {
1249
+ setIsWatching(false);
1250
+ setDetectedIssue(null);
1251
+ renderedBlocksRef.current.clear();
1252
+ setCurrentSession((i) => i + 1);
1253
+ setWatchCycle((c) => c + 1);
1254
+ }, 1500);
1255
+ });
1256
+ watchCleanupRef.current = cleanup;
1257
+ return () => {
1258
+ cleanup();
1259
+ watchCleanupRef.current = null;
1260
+ };
1261
+ }, [isWatching, startupSnapshot]);
1262
+ useEffect3(() => {
1263
+ const newItems = [];
1264
+ if (currentSession > lastSessionRef.current) {
1265
+ newItems.push({
1266
+ type: "session",
1267
+ session: currentSession,
1268
+ key: `session-${currentSession}`
1269
+ });
1270
+ lastSessionRef.current = currentSession;
1271
+ }
1272
+ const blocks = processEvents(events);
1273
+ for (const block of blocks) {
1274
+ const blockKey = block.id;
1275
+ if (!renderedBlocksRef.current.has(blockKey)) {
1276
+ renderedBlocksRef.current.add(blockKey);
1277
+ newItems.push({ type: "block", block, key: blockKey });
1278
+ }
1279
+ }
1280
+ if (newItems.length > 0) {
1281
+ setStaticItems((prev) => [...prev, ...newItems]);
1282
+ }
1283
+ }, [events, currentSession]);
1284
+ useEffect3(() => {
1285
+ if (currentSession > totalSessions) {
1286
+ exit();
1287
+ return;
1288
+ }
1289
+ const currentProgress = startupSnapshot ? getProgress(startupSnapshot.initialCount, startupSnapshot.timestamp) : { type: "none", completed: 0, total: 0 };
1290
+ if (currentProgress.completed >= currentProgress.total && currentProgress.type !== "none") {
1291
+ if (watch) {
1292
+ setIsWatching(true);
1293
+ } else {
1294
+ exit();
1295
+ process.exit(0);
1296
+ }
1297
+ return;
1298
+ }
1299
+ if (!logFileRef.current) {
1300
+ logFileRef.current = getNextLogFile();
1301
+ writeFileSync2(logFileRef.current, "");
1302
+ }
1303
+ const logFile = logFileRef.current;
1304
+ setEvents([]);
1305
+ const promptContent = getPromptContent();
1306
+ const todoExists = existsSync8(todoFile5);
1307
+ setHasTodoFile(todoExists);
1308
+ const todoContent = todoExists ? readFileSync5(todoFile5, "utf-8") : "";
1309
+ const roundHeader = `# Ralph, round ${currentSession}
1310
+
1311
+ `;
1312
+ const fullPrompt = todoContent ? `${roundHeader}${promptContent}
1313
+
1314
+ ## Current Todo List
1315
+
1316
+ ${todoContent}` : `${roundHeader}${promptContent}`;
1317
+ const abortController = new AbortController();
1318
+ setIsRunning(true);
1319
+ const messageQueue = new MessageQueue();
1320
+ messageQueueRef.current = messageQueue;
1321
+ messageQueue.push(createUserMessage(fullPrompt));
1322
+ const runQuery = async () => {
1323
+ let finalResult = "";
1324
+ log2(`Starting session ${currentSession}`);
1325
+ try {
1326
+ log2(`Beginning query() loop`);
1327
+ for await (const message of query({
1328
+ prompt: messageQueue,
1329
+ options: {
1330
+ abortController,
1331
+ permissionMode: "bypassPermissions",
1332
+ allowDangerouslySkipPermissions: true,
1333
+ includePartialMessages: true,
1334
+ env: {
1335
+ ...process.env,
1336
+ // Disable LSP plugins to avoid crashes when TypeScript LSP server errors
1337
+ ENABLE_LSP_TOOL: "0",
1338
+ // Signal that tests should use minimal reporters (dots)
1339
+ RALPH_QUIET: "1"
1340
+ }
1341
+ }
1342
+ })) {
1343
+ log2(`Received message type: ${message.type}`);
1344
+ appendFileSync(logFile, JSON.stringify(message) + "\n");
1345
+ const event = sdkMessageToEvent(message);
1346
+ if (event) {
1347
+ setEvents((prev) => [...prev, event]);
1348
+ }
1349
+ if (message.type === "assistant") {
1350
+ const assistantMessage = message.message;
1351
+ const content = assistantMessage?.content;
1352
+ if (content) {
1353
+ for (const block of content) {
1354
+ if (block.type === "text" && typeof block.text === "string") {
1355
+ const taskInfo = parseTaskLifecycleEvent(block.text);
1356
+ if (taskInfo) {
1357
+ if (taskInfo.action === "starting") {
1358
+ setCurrentTaskId(taskInfo.taskId ?? null);
1359
+ setCurrentTaskTitle(taskInfo.taskTitle ?? null);
1360
+ log2(
1361
+ `Task started: ${taskInfo.taskId}${taskInfo.taskTitle ? ` - ${taskInfo.taskTitle}` : ""}`
1362
+ );
1363
+ const taskStartedEvent = {
1364
+ type: "ralph_task_started",
1365
+ taskId: taskInfo.taskId,
1366
+ taskTitle: taskInfo.taskTitle,
1367
+ session: currentSession
1368
+ };
1369
+ appendFileSync(logFile, JSON.stringify(taskStartedEvent) + "\n");
1370
+ } else if (taskInfo.action === "completed") {
1371
+ log2(
1372
+ `Task completed: ${taskInfo.taskId}${taskInfo.taskTitle ? ` - ${taskInfo.taskTitle}` : ""}`
1373
+ );
1374
+ const taskCompletedEvent = {
1375
+ type: "ralph_task_completed",
1376
+ taskId: taskInfo.taskId,
1377
+ taskTitle: taskInfo.taskTitle,
1378
+ session: currentSession
1379
+ };
1380
+ appendFileSync(logFile, JSON.stringify(taskCompletedEvent) + "\n");
1381
+ }
1382
+ }
1383
+ }
1384
+ }
1385
+ }
1386
+ }
1387
+ if (message.type === "result" && "result" in message && typeof message.result === "string") {
1388
+ log2(`Received result message`);
1389
+ finalResult = message.result;
1390
+ log2(`Closing message queue on result`);
1391
+ messageQueue.close();
1392
+ }
1393
+ }
1394
+ log2(`query() loop completed normally`);
1395
+ setIsRunning(false);
1396
+ log2(`Ensuring message queue is closed`);
1397
+ messageQueue.close();
1398
+ messageQueueRef.current = null;
1399
+ if (stopAfterCurrentRef.current) {
1400
+ log2(`Stop after current requested - exiting gracefully`);
1401
+ exit();
1402
+ process.exit(0);
1403
+ return;
1404
+ }
1405
+ if (finalResult.includes("<promise>COMPLETE</promise>")) {
1406
+ if (watch) {
1407
+ setIsWatching(true);
1408
+ } else {
1409
+ exit();
1410
+ process.exit(0);
1411
+ }
1412
+ return;
1413
+ }
1414
+ if (isPausedRef.current) {
1415
+ log2(`Paused after session ${currentSession}`);
1416
+ return;
1417
+ }
1418
+ setTimeout(() => setCurrentSession((i) => i + 1), 500);
1419
+ } catch (err) {
1420
+ log2(`query() loop error: ${err instanceof Error ? err.message : String(err)}`);
1421
+ setIsRunning(false);
1422
+ log2(`Closing message queue after error`);
1423
+ messageQueue.close();
1424
+ messageQueueRef.current = null;
1425
+ if (abortController.signal.aborted) {
1426
+ log2(`Abort signal detected`);
1427
+ return;
1428
+ }
1429
+ setError(`Error running Claude: ${err instanceof Error ? err.message : String(err)}`);
1430
+ setTimeout(() => {
1431
+ exit();
1432
+ process.exit(1);
1433
+ }, 100);
1434
+ }
1435
+ };
1436
+ runQuery();
1437
+ return () => {
1438
+ log2(`Cleanup: aborting and closing queue for session ${currentSession}`);
1439
+ abortController.abort();
1440
+ messageQueue.close();
1441
+ messageQueueRef.current = null;
1442
+ };
1443
+ }, [currentSession, totalSessions, exit, watch, watchCycle]);
1444
+ if (error) {
1445
+ return /* @__PURE__ */ React5.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Text5, { color: "red" }, error));
1446
+ }
1447
+ return /* @__PURE__ */ React5.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Static, { items: staticItems }, (item) => /* @__PURE__ */ React5.createElement(Box3, { key: item.key, flexDirection: "column" }, renderStaticItem(item))), isAddingTodo && /* @__PURE__ */ React5.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, "Todo:"), /* @__PURE__ */ React5.createElement(EnhancedTextInput, { value: todoText, onChange: setTodoText, onSubmit: handleTodoSubmit }), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(Enter to add, Esc to cancel)")), todoMessage && /* @__PURE__ */ React5.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, { color: todoMessage.type === "success" ? "green" : "red" }, todoMessage.text)), !isWatching && /* @__PURE__ */ React5.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "\u2500".repeat(columns)), /* @__PURE__ */ React5.createElement(Box3, null, /* @__PURE__ */ React5.createElement(Text5, { color: isRunning ? "yellow" : "gray" }, "\u276F "), /* @__PURE__ */ React5.createElement(
1448
+ EnhancedTextInput,
1449
+ {
1450
+ value: userMessageText,
1451
+ placeholder: isRunning ? "Type a message for Ralph..." : "Waiting for Ralph to start...",
1452
+ onChange: setUserMessageText,
1453
+ onSubmit: handleUserMessageSubmit,
1454
+ focus: isRunning && !isAddingTodo
1455
+ }
1456
+ )), userMessageStatus && /* @__PURE__ */ React5.createElement(
1457
+ Text5,
1458
+ {
1459
+ color: userMessageStatus.type === "success" ? "green" : userMessageStatus.type === "error" ? "red" : "yellow"
1460
+ },
1461
+ userMessageStatus.text
1462
+ ), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "\u2500".repeat(columns))), /* @__PURE__ */ React5.createElement(Box3, { marginTop: 1, justifyContent: "space-between" }, isWatching ? detectedIssue ? /* @__PURE__ */ React5.createElement(Text5, { color: "green" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" }), " New issue: ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, detectedIssue.IssueID), detectedIssue.Title ? ` - ${detectedIssue.Title}` : "") : /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, "Waiting for new issues ", /* @__PURE__ */ React5.createElement(Spinner, { type: "simpleDotsScrolling" })) : isPaused && !isRunning ? /* @__PURE__ */ React5.createElement(Text5, { color: "magenta" }, "\u23F8 Paused after round ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, currentSession), " ", /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(Ctrl-P to resume)")) : isRunning ? stopAfterCurrent ? /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" }), " Stopping after round", " ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, currentSession), " completes...", " ", /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(Ctrl-S pressed)")) : isPaused ? /* @__PURE__ */ React5.createElement(Text5, { color: "magenta" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" }), " Pausing after round", " ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, currentSession), " completes...", " ", /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(Ctrl-P pressed)")) : /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" }), " Running round ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, currentSession), " ", "(max ", totalSessions, ")") : /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "simpleDotsScrolling" }), " Waiting for Ralph to start..."), progressData.type !== "none" && progressData.total > 0 && /* @__PURE__ */ React5.createElement(
1463
+ ProgressBar,
1464
+ {
1465
+ completed: progressData.completed,
1466
+ total: progressData.total,
1467
+ repoName
1468
+ }
1469
+ )));
1470
+ };
1471
+
1472
+ // src/components/ReplayLog.tsx
1473
+ import React8, { useState as useState5, useEffect as useEffect5 } from "react";
1474
+ import { Box as Box6, Text as Text8, useApp as useApp2 } from "ink";
1475
+ import { readFileSync as readFileSync6 } from "fs";
1476
+
1477
+ // src/components/EventDisplay.tsx
1478
+ import React6, { useMemo, useState as useState4, useEffect as useEffect4, useRef as useRef2 } from "react";
1479
+ import { Box as Box4, Text as Text6, useInput as useInput3 } from "ink";
1480
+
1481
+ // src/lib/formatSessionHeader.ts
1482
+ import chalk5 from "chalk";
1483
+ var formatSessionHeader = (session) => {
1484
+ return chalk5.cyan.bold(`\u2500\u2500\u2500 Round ${session} \u2500\u2500\u2500`);
1485
+ };
1486
+
1487
+ // src/components/processEvents.ts
1488
+ var processEvents2 = (events) => {
1489
+ const assistantEvents = events.filter((event) => event.type === "assistant");
1490
+ const messageMap = /* @__PURE__ */ new Map();
1491
+ for (const event of assistantEvents) {
1492
+ const message = event.message;
1493
+ const messageId = message?.id;
1494
+ const content = message?.content;
1495
+ if (messageId && content) {
1496
+ if (!messageMap.has(messageId)) {
1497
+ messageMap.set(messageId, []);
1498
+ }
1499
+ messageMap.get(messageId).push(...content);
1500
+ }
1501
+ }
1502
+ const mergedEvents = Array.from(messageMap.entries()).map(([messageId, allContent]) => {
1503
+ const seenBlocks = /* @__PURE__ */ new Set();
1504
+ const uniqueContent = [];
1505
+ for (const block of allContent) {
1506
+ const blockType = block.type;
1507
+ let blockKey;
1508
+ if (blockType === "tool_use") {
1509
+ blockKey = `tool:${block.id}`;
1510
+ } else if (blockType === "text") {
1511
+ const text = block.text;
1512
+ let isDuplicate = false;
1513
+ for (const seenKey of seenBlocks) {
1514
+ if (seenKey.startsWith("text:")) {
1515
+ const seenText = seenKey.substring(5);
1516
+ if (seenText.startsWith(text)) {
1517
+ isDuplicate = true;
1518
+ break;
1519
+ } else if (text.startsWith(seenText)) {
1520
+ seenBlocks.delete(seenKey);
1521
+ const idx = uniqueContent.findIndex((b) => b.type === "text" && b.text === seenText);
1522
+ if (idx >= 0) uniqueContent.splice(idx, 1);
1523
+ break;
1524
+ }
1525
+ }
1526
+ }
1527
+ if (isDuplicate) continue;
1528
+ blockKey = `text:${text}`;
1529
+ } else {
1530
+ blockKey = JSON.stringify(block);
1531
+ }
1532
+ if (!seenBlocks.has(blockKey)) {
1533
+ seenBlocks.add(blockKey);
1534
+ uniqueContent.push(block);
1535
+ }
1536
+ }
1537
+ return {
1538
+ type: "assistant",
1539
+ message: {
1540
+ id: messageId,
1541
+ content: uniqueContent
1542
+ }
1543
+ };
1544
+ });
1545
+ return mergedEvents.flatMap((event) => eventToBlocks(event));
1546
+ };
1547
+
1548
+ // src/components/blocksToLines.ts
1549
+ var blocksToLines = (blocks) => {
1550
+ const lines = [];
1551
+ for (const block of blocks) {
1552
+ const blockLines = formatContentBlock(block);
1553
+ lines.push(...blockLines);
1554
+ lines.push("");
1555
+ }
1556
+ return lines;
1557
+ };
1558
+
1559
+ // src/components/EventDisplay.tsx
1560
+ var EventDisplay = ({ events, session, completedSessions, height }) => {
1561
+ const [scrollOffset, setScrollOffset] = useState4(0);
1562
+ const userScrolledRef = useRef2(false);
1563
+ const prevLineCountRef = useRef2(0);
1564
+ const allLines = useMemo(() => {
1565
+ const lines = [];
1566
+ for (const completed of completedSessions) {
1567
+ lines.push("");
1568
+ lines.push("");
1569
+ lines.push(formatSessionHeader(completed.session));
1570
+ lines.push("");
1571
+ const blocks = processEvents2(completed.events);
1572
+ lines.push(...blocksToLines(blocks));
1573
+ }
1574
+ lines.push("");
1575
+ lines.push("");
1576
+ lines.push(formatSessionHeader(session));
1577
+ lines.push("");
1578
+ const currentBlocks = processEvents2(events);
1579
+ lines.push(...blocksToLines(currentBlocks));
1580
+ return lines;
1581
+ }, [events, session, completedSessions]);
1582
+ useEffect4(() => {
1583
+ if (allLines.length > prevLineCountRef.current && !userScrolledRef.current) {
1584
+ setScrollOffset(0);
1585
+ }
1586
+ prevLineCountRef.current = allLines.length;
1587
+ }, [allLines.length]);
1588
+ useInput3((input, key) => {
1589
+ if (!height) return;
1590
+ const maxOffset = Math.max(0, allLines.length - height);
1591
+ const pageSize = Math.max(1, height - 2);
1592
+ if (key.upArrow || input === "k") {
1593
+ userScrolledRef.current = true;
1594
+ setScrollOffset((prev) => Math.min(maxOffset, prev + 1));
1595
+ } else if (key.downArrow || input === "j") {
1596
+ const newOffset = Math.max(0, scrollOffset - 1);
1597
+ setScrollOffset(newOffset);
1598
+ if (newOffset === 0) {
1599
+ userScrolledRef.current = false;
1600
+ }
1601
+ } else if (key.pageUp) {
1602
+ userScrolledRef.current = true;
1603
+ setScrollOffset((prev) => Math.min(maxOffset, prev + pageSize));
1604
+ } else if (key.pageDown) {
1605
+ const newOffset = Math.max(0, scrollOffset - pageSize);
1606
+ setScrollOffset(newOffset);
1607
+ if (newOffset === 0) {
1608
+ userScrolledRef.current = false;
1609
+ }
1610
+ } else if (input === "g" && key.shift) {
1611
+ setScrollOffset(0);
1612
+ userScrolledRef.current = false;
1613
+ } else if (input === "g") {
1614
+ userScrolledRef.current = true;
1615
+ setScrollOffset(maxOffset);
1616
+ }
1617
+ });
1618
+ const visibleLines = useMemo(() => {
1619
+ if (!height || allLines.length <= height) {
1620
+ return allLines;
1621
+ }
1622
+ const endIndex = allLines.length - scrollOffset;
1623
+ const startIndex = Math.max(0, endIndex - height);
1624
+ return allLines.slice(startIndex, endIndex);
1625
+ }, [allLines, height, scrollOffset]);
1626
+ return /* @__PURE__ */ React6.createElement(Box4, { flexDirection: "column" }, visibleLines.map((line, index) => /* @__PURE__ */ React6.createElement(Text6, { key: index, wrap: "wrap" }, line || " ")));
1627
+ };
1628
+
1629
+ // src/components/FullScreenLayout.tsx
1630
+ import React7 from "react";
1631
+ import { Box as Box5, Text as Text7 } from "ink";
1632
+ import BigText3 from "ink-big-text";
1633
+ import Gradient3 from "ink-gradient";
1634
+
1635
+ // src/components/useContentHeight.ts
1636
+ var HEADER_HEIGHT = 5;
1637
+ var FOOTER_HEIGHT = 2;
1638
+ var BORDER_HEIGHT = 2;
1639
+ var useContentHeight = (hasFooter = true) => {
1640
+ const { rows } = useTerminalSize();
1641
+ const footerHeight = hasFooter ? FOOTER_HEIGHT : 0;
1642
+ return Math.max(1, rows - HEADER_HEIGHT - footerHeight - BORDER_HEIGHT);
1643
+ };
1644
+
1645
+ // src/components/FullScreenLayout.tsx
1646
+ var FullScreenLayout = ({ title, children, footer, version }) => {
1647
+ const { columns, rows } = useTerminalSize();
1648
+ const contentHeight = useContentHeight(!!footer);
1649
+ return /* @__PURE__ */ React7.createElement(
1650
+ Box5,
1651
+ {
1652
+ flexDirection: "column",
1653
+ width: columns,
1654
+ height: rows,
1655
+ borderStyle: "round",
1656
+ borderColor: "gray"
1657
+ },
1658
+ /* @__PURE__ */ React7.createElement(
1659
+ Box5,
1660
+ {
1661
+ flexDirection: "column",
1662
+ alignItems: "center",
1663
+ justifyContent: "center",
1664
+ height: HEADER_HEIGHT,
1665
+ borderStyle: "single",
1666
+ borderTop: false,
1667
+ borderLeft: false,
1668
+ borderRight: false,
1669
+ borderColor: "gray"
1670
+ },
1671
+ /* @__PURE__ */ React7.createElement(Gradient3, { colors: ["#30A6E4", "#EBC635"] }, /* @__PURE__ */ React7.createElement(BigText3, { text: title, font: "tiny" }))
1672
+ ),
1673
+ /* @__PURE__ */ React7.createElement(
1674
+ Box5,
1675
+ {
1676
+ flexDirection: "column",
1677
+ flexGrow: 1,
1678
+ paddingX: 1,
1679
+ height: contentHeight,
1680
+ overflowY: "hidden"
1681
+ },
1682
+ children
1683
+ ),
1684
+ footer && /* @__PURE__ */ React7.createElement(
1685
+ Box5,
1686
+ {
1687
+ paddingX: 1,
1688
+ height: FOOTER_HEIGHT,
1689
+ borderStyle: "single",
1690
+ borderTop: true,
1691
+ borderBottom: false,
1692
+ borderLeft: false,
1693
+ borderRight: false,
1694
+ borderColor: "gray",
1695
+ alignItems: "center",
1696
+ justifyContent: "space-between"
1697
+ },
1698
+ /* @__PURE__ */ React7.createElement(Box5, null, footer),
1699
+ version && /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, version)
1700
+ )
1701
+ );
1702
+ };
1703
+
1704
+ // src/components/ReplayLog.tsx
1705
+ var ReplayLog = ({
1706
+ /** The path to the replay log file */
1707
+ filePath
1708
+ }) => {
1709
+ const { exit } = useApp2();
1710
+ const [events, setEvents] = useState5([]);
1711
+ const [error, setError] = useState5();
1712
+ useEffect5(() => {
1713
+ try {
1714
+ const content = readFileSync6(filePath, "utf-8");
1715
+ const eventStrings = content.split(/\n\n+/).filter((s) => s.trim());
1716
+ const parsedEvents = [];
1717
+ for (const eventStr of eventStrings) {
1718
+ try {
1719
+ const event = JSON.parse(eventStr);
1720
+ parsedEvents.push(event);
1721
+ } catch {
1722
+ }
1723
+ }
1724
+ setEvents(parsedEvents);
1725
+ setTimeout(() => {
1726
+ exit();
1727
+ process.exit(0);
1728
+ }, 100);
1729
+ } catch (err) {
1730
+ setError(`Failed to read replay file: ${err instanceof Error ? err.message : String(err)}`);
1731
+ setTimeout(() => {
1732
+ exit();
1733
+ process.exit(1);
1734
+ }, 100);
1735
+ }
1736
+ }, [filePath, exit]);
1737
+ if (error) {
1738
+ return /* @__PURE__ */ React8.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Text8, { color: "red" }, error));
1739
+ }
1740
+ const footer = /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "Replaying: ", filePath);
1741
+ const contentHeight = useContentHeight(true);
1742
+ return /* @__PURE__ */ React8.createElement(FullScreenLayout, { title: "Ralph", footer }, /* @__PURE__ */ React8.createElement(EventDisplay, { events, session: 1, completedSessions: [], height: contentHeight }));
1743
+ };
1744
+
1745
+ // src/components/JsonOutput.tsx
1746
+ import React9, { useState as useState6, useEffect as useEffect6, useRef as useRef3 } from "react";
1747
+ import { useApp as useApp3, Text as Text9 } from "ink";
1748
+ import { writeFileSync as writeFileSync3, readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
1749
+ import { join as join11, basename as basename2 } from "path";
1750
+ import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
1751
+
1752
+ // src/lib/parseStdinCommand.ts
1753
+ var log3 = createDebugLogger("stdin-command");
1754
+ var parseStdinCommand = (line) => {
1755
+ const trimmed = line.trim();
1756
+ if (!trimmed) return null;
1757
+ try {
1758
+ const parsed = JSON.parse(trimmed);
1759
+ if (typeof parsed !== "object" || parsed === null) {
1760
+ log3(`Invalid command - not an object: ${trimmed}`);
1761
+ return null;
1762
+ }
1763
+ if (parsed.type === "message") {
1764
+ if (typeof parsed.text !== "string") {
1765
+ log3(`Invalid message command - missing or invalid text: ${trimmed}`);
1766
+ return null;
1767
+ }
1768
+ return { type: "message", text: parsed.text };
1769
+ }
1770
+ if (parsed.type === "stop") {
1771
+ return { type: "stop" };
1772
+ }
1773
+ if (parsed.type === "pause") {
1774
+ return { type: "pause" };
1775
+ }
1776
+ if (parsed.type === "resume") {
1777
+ return { type: "resume" };
1778
+ }
1779
+ log3(`Unknown command type: ${parsed.type}`);
1780
+ return null;
1781
+ } catch (err) {
1782
+ log3(`Failed to parse command: ${err instanceof Error ? err.message : String(err)}`);
1783
+ return null;
1784
+ }
1785
+ };
1786
+
1787
+ // src/lib/createStdinCommandHandler.ts
1788
+ import { createInterface } from "readline";
1789
+ var log4 = createDebugLogger("stdin-command");
1790
+ var createStdinCommandHandler = (getOptions) => {
1791
+ if (process.stdin.isTTY) {
1792
+ log4(`stdin is TTY - skipping stdin command handler`);
1793
+ return () => {
1794
+ };
1795
+ }
1796
+ log4(`Setting up stdin command handler`);
1797
+ const rl = createInterface({
1798
+ input: process.stdin,
1799
+ terminal: false
1800
+ });
1801
+ const lineHandler = (line) => {
1802
+ const command = parseStdinCommand(line);
1803
+ if (!command) return;
1804
+ const options = getOptions();
1805
+ log4(`Received command: ${command.type}`);
1806
+ if (command.type === "message") {
1807
+ if (options.messageQueue) {
1808
+ const userMessage = createUserMessage(command.text);
1809
+ options.messageQueue.push(userMessage);
1810
+ log4(`Pushed message to queue: ${command.text.slice(0, 50)}...`);
1811
+ options.onMessage?.(command.text);
1812
+ } else {
1813
+ log4(`Cannot send message - no active message queue`);
1814
+ }
1815
+ } else if (command.type === "stop") {
1816
+ log4(`Stop command received`);
1817
+ options.onStop();
1818
+ } else if (command.type === "pause") {
1819
+ log4(`Pause command received`);
1820
+ options.onPause?.();
1821
+ } else if (command.type === "resume") {
1822
+ log4(`Resume command received`);
1823
+ options.onResume?.();
1824
+ }
1825
+ };
1826
+ rl.on("line", lineHandler);
1827
+ return () => {
1828
+ log4(`Cleaning up stdin command handler`);
1829
+ rl.off("line", lineHandler);
1830
+ rl.close();
1831
+ };
1832
+ };
1833
+
1834
+ // src/lib/outputEvent.ts
1835
+ var outputEvent = (event) => {
1836
+ process.stdout.write(JSON.stringify(event) + "\n");
1837
+ };
1838
+
1839
+ // src/components/JsonOutput.tsx
1840
+ var log5 = createDebugLogger("session");
1841
+ var todoFile6 = join11(process.cwd(), ".ralph", "todo.md");
1842
+ var repoName2 = basename2(process.cwd());
1843
+ var JsonOutput = ({ totalSessions, agent }) => {
1844
+ const { exit } = useApp3();
1845
+ const [currentSession, setCurrentSession] = useState6(1);
1846
+ const [error, setError] = useState6();
1847
+ const [isRunning, setIsRunning] = useState6(false);
1848
+ const [startupSnapshot] = useState6(() => captureStartupSnapshot());
1849
+ const messageQueueRef = useRef3(null);
1850
+ const logFileRef = useRef3(null);
1851
+ const [stopAfterCurrent, setStopAfterCurrent] = useState6(false);
1852
+ const stopAfterCurrentRef = useRef3(false);
1853
+ const [isPaused, setIsPaused] = useState6(false);
1854
+ const isPausedRef = useRef3(false);
1855
+ const stdinCleanupRef = useRef3(null);
1856
+ const currentTaskIdRef = useRef3(null);
1857
+ const currentTaskTitleRef = useRef3(null);
1858
+ useEffect6(() => {
1859
+ stopAfterCurrentRef.current = stopAfterCurrent;
1860
+ }, [stopAfterCurrent]);
1861
+ useEffect6(() => {
1862
+ isPausedRef.current = isPaused;
1863
+ }, [isPaused]);
1864
+ useEffect6(() => {
1865
+ const cleanup = createStdinCommandHandler(() => ({
1866
+ messageQueue: messageQueueRef.current,
1867
+ onStop: () => {
1868
+ setStopAfterCurrent(true);
1869
+ outputEvent({ type: "ralph_stop_requested" });
1870
+ },
1871
+ onPause: () => {
1872
+ setIsPaused(true);
1873
+ outputEvent({ type: "ralph_pause_requested" });
1874
+ },
1875
+ onResume: () => {
1876
+ const wasPaused = isPausedRef.current;
1877
+ setIsPaused(false);
1878
+ outputEvent({ type: "ralph_resumed" });
1879
+ if (wasPaused && !isRunning) {
1880
+ setTimeout(() => setCurrentSession((i) => i + 1), 100);
1881
+ }
1882
+ },
1883
+ onMessage: (text) => {
1884
+ outputEvent({
1885
+ type: "ralph_message_received",
1886
+ text: text.slice(0, 100) + (text.length > 100 ? "..." : "")
1887
+ });
1888
+ }
1889
+ }));
1890
+ stdinCleanupRef.current = cleanup;
1891
+ return () => {
1892
+ cleanup();
1893
+ stdinCleanupRef.current = null;
1894
+ };
1895
+ }, []);
1896
+ useEffect6(() => {
1897
+ if (currentSession > totalSessions) {
1898
+ outputEvent({ type: "ralph_exit", reason: "max_sessions" });
1899
+ exit();
1900
+ return;
1901
+ }
1902
+ const currentProgress = startupSnapshot ? getProgress(startupSnapshot.initialCount, startupSnapshot.timestamp) : { type: "none", completed: 0, total: 0 };
1903
+ if (currentProgress.completed >= currentProgress.total && currentProgress.type !== "none") {
1904
+ outputEvent({ type: "ralph_exit", reason: "all_tasks_complete" });
1905
+ exit();
1906
+ process.exit(0);
1907
+ return;
1908
+ }
1909
+ if (!logFileRef.current) {
1910
+ logFileRef.current = getNextLogFile();
1911
+ writeFileSync3(logFileRef.current, "");
1912
+ }
1913
+ const promptContent = getPromptContent();
1914
+ const todoExists = existsSync9(todoFile6);
1915
+ const todoContent = todoExists ? readFileSync7(todoFile6, "utf-8") : "";
1916
+ const fullPrompt = todoContent ? `${promptContent}
1917
+
1918
+ ## Current Todo List
1919
+
1920
+ ${todoContent}` : promptContent;
1921
+ const abortController = new AbortController();
1922
+ setIsRunning(true);
1923
+ outputEvent({
1924
+ type: "ralph_session_start",
1925
+ session: currentSession,
1926
+ totalSessions,
1927
+ repo: repoName2,
1928
+ taskId: currentTaskIdRef.current,
1929
+ taskTitle: currentTaskTitleRef.current
1930
+ });
1931
+ const messageQueue = new MessageQueue();
1932
+ messageQueueRef.current = messageQueue;
1933
+ messageQueue.push(createUserMessage(fullPrompt));
1934
+ const runQuery = async () => {
1935
+ let finalResult = "";
1936
+ log5(`Starting session ${currentSession}`);
1937
+ try {
1938
+ log5(`Beginning query() loop`);
1939
+ for await (const message of query2({
1940
+ prompt: messageQueue,
1941
+ options: {
1942
+ abortController,
1943
+ permissionMode: "bypassPermissions",
1944
+ allowDangerouslySkipPermissions: true,
1945
+ includePartialMessages: false,
1946
+ // Only complete messages for JSON output
1947
+ env: {
1948
+ ...process.env,
1949
+ // Disable LSP plugins to avoid crashes when TypeScript LSP server errors
1950
+ ENABLE_LSP_TOOL: "0"
1951
+ }
1952
+ }
1953
+ })) {
1954
+ log5(`Received message type: ${message.type}`);
1955
+ outputEvent(message);
1956
+ if (message.type === "assistant") {
1957
+ const assistantMessage = message.message;
1958
+ const content = assistantMessage?.content;
1959
+ if (content) {
1960
+ for (const block of content) {
1961
+ if (block.type === "text" && typeof block.text === "string") {
1962
+ const taskInfo = parseTaskLifecycleEvent(block.text);
1963
+ if (taskInfo) {
1964
+ if (taskInfo.action === "starting") {
1965
+ currentTaskIdRef.current = taskInfo.taskId ?? null;
1966
+ currentTaskTitleRef.current = taskInfo.taskTitle ?? null;
1967
+ log5(
1968
+ `Task started: ${taskInfo.taskId}${taskInfo.taskTitle ? ` - ${taskInfo.taskTitle}` : ""}`
1969
+ );
1970
+ outputEvent({
1971
+ type: "ralph_task_started",
1972
+ taskId: taskInfo.taskId,
1973
+ taskTitle: taskInfo.taskTitle,
1974
+ session: currentSession
1975
+ });
1976
+ } else if (taskInfo.action === "completed") {
1977
+ log5(
1978
+ `Task completed: ${taskInfo.taskId}${taskInfo.taskTitle ? ` - ${taskInfo.taskTitle}` : ""}`
1979
+ );
1980
+ outputEvent({
1981
+ type: "ralph_task_completed",
1982
+ taskId: taskInfo.taskId,
1983
+ taskTitle: taskInfo.taskTitle,
1984
+ session: currentSession
1985
+ });
1986
+ }
1987
+ }
1988
+ }
1989
+ }
1990
+ }
1991
+ }
1992
+ if (message.type === "result" && "result" in message && typeof message.result === "string") {
1993
+ log5(`Received result message`);
1994
+ finalResult = message.result;
1995
+ log5(`Closing message queue on result`);
1996
+ messageQueue.close();
1997
+ }
1998
+ }
1999
+ log5(`query() loop completed normally`);
2000
+ setIsRunning(false);
2001
+ log5(`Ensuring message queue is closed`);
2002
+ messageQueue.close();
2003
+ messageQueueRef.current = null;
2004
+ outputEvent({
2005
+ type: "ralph_session_end",
2006
+ session: currentSession,
2007
+ taskId: currentTaskIdRef.current,
2008
+ taskTitle: currentTaskTitleRef.current
2009
+ });
2010
+ if (stopAfterCurrentRef.current) {
2011
+ log5(`Stop after current requested - exiting gracefully`);
2012
+ outputEvent({ type: "ralph_exit", reason: "stop_requested" });
2013
+ exit();
2014
+ process.exit(0);
2015
+ return;
2016
+ }
2017
+ if (finalResult.includes("<promise>COMPLETE</promise>")) {
2018
+ outputEvent({ type: "ralph_exit", reason: "task_complete" });
2019
+ exit();
2020
+ process.exit(0);
2021
+ return;
2022
+ }
2023
+ if (isPausedRef.current) {
2024
+ log5(`Paused after session ${currentSession}`);
2025
+ outputEvent({ type: "ralph_paused", session: currentSession });
2026
+ return;
2027
+ }
2028
+ setTimeout(() => setCurrentSession((i) => i + 1), 500);
2029
+ } catch (err) {
2030
+ log5(`query() loop error: ${err instanceof Error ? err.message : String(err)}`);
2031
+ setIsRunning(false);
2032
+ log5(`Closing message queue after error`);
2033
+ messageQueue.close();
2034
+ messageQueueRef.current = null;
2035
+ if (abortController.signal.aborted) {
2036
+ log5(`Abort signal detected`);
2037
+ return;
2038
+ }
2039
+ const errorMsg = `Error running Claude: ${err instanceof Error ? err.message : String(err)}`;
2040
+ setError(errorMsg);
2041
+ outputEvent({ type: "ralph_error", error: errorMsg });
2042
+ setTimeout(() => {
2043
+ exit();
2044
+ process.exit(1);
2045
+ }, 100);
2046
+ }
2047
+ };
2048
+ runQuery();
2049
+ return () => {
2050
+ log5(`Cleanup: aborting and closing queue for session ${currentSession}`);
2051
+ abortController.abort();
2052
+ messageQueue.close();
2053
+ messageQueueRef.current = null;
2054
+ };
2055
+ }, [currentSession, totalSessions, exit, startupSnapshot]);
2056
+ if (error) {
2057
+ return /* @__PURE__ */ React9.createElement(Text9, null, "");
2058
+ }
2059
+ return /* @__PURE__ */ React9.createElement(Text9, null, "");
2060
+ };
2061
+
2062
+ // src/components/App.tsx
2063
+ var App = ({
2064
+ sessions,
2065
+ replayFile,
2066
+ claudeVersion,
2067
+ ralphVersion,
2068
+ watch,
2069
+ json,
2070
+ agent
2071
+ }) => {
2072
+ if (replayFile) {
2073
+ return /* @__PURE__ */ React10.createElement(ReplayLog, { filePath: replayFile });
2074
+ }
2075
+ if (json) {
2076
+ return /* @__PURE__ */ React10.createElement(JsonOutput, { totalSessions: sessions, agent });
2077
+ }
2078
+ return /* @__PURE__ */ React10.createElement(
2079
+ SessionRunner,
2080
+ {
2081
+ totalSessions: sessions,
2082
+ claudeVersion,
2083
+ ralphVersion,
2084
+ watch,
2085
+ agent
2086
+ }
2087
+ );
2088
+ };
2089
+
2090
+ // src/components/InitRalph.tsx
2091
+ import { Text as Text10, Box as Box7 } from "ink";
2092
+ import React11, { useEffect as useEffect7, useState as useState7 } from "react";
2093
+ import { existsSync as existsSync11, readFileSync as readFileSync8, appendFileSync as appendFileSync2, writeFileSync as writeFileSync4 } from "fs";
2094
+ import { join as join13, dirname as dirname3 } from "path";
2095
+ import { fileURLToPath as fileURLToPath2 } from "url";
2096
+
2097
+ // src/lib/copyTemplates.ts
2098
+ import { existsSync as existsSync10, mkdirSync as mkdirSync2, copyFileSync } from "fs";
2099
+ import { join as join12, dirname as dirname2 } from "path";
2100
+ function copyTemplates(templatesDir, destDir, files) {
2101
+ const result = { created: [], skipped: [], errors: [] };
2102
+ for (const { src, dest } of files) {
2103
+ const srcPath = join12(templatesDir, src);
2104
+ const destPath = join12(destDir, dest);
2105
+ const destDirPath = dirname2(destPath);
2106
+ if (!existsSync10(destDirPath)) {
2107
+ mkdirSync2(destDirPath, { recursive: true });
2108
+ }
2109
+ if (existsSync10(destPath)) {
2110
+ result.skipped.push(dest);
2111
+ } else if (existsSync10(srcPath)) {
2112
+ copyFileSync(srcPath, destPath);
2113
+ result.created.push(dest);
2114
+ } else {
2115
+ result.errors.push(`Template not found: ${src}`);
2116
+ }
2117
+ }
2118
+ return result;
2119
+ }
2120
+
2121
+ // src/components/InitRalph.tsx
2122
+ function InitRalph() {
2123
+ const __dirname = dirname3(fileURLToPath2(import.meta.url));
2124
+ const [status, setStatus] = useState7("checking");
2125
+ const [createdFiles, setCreatedFiles] = useState7([]);
2126
+ const [skippedFiles, setSkippedFiles] = useState7([]);
2127
+ const [errors, setErrors] = useState7([]);
2128
+ useEffect7(() => {
2129
+ const ralphDir6 = join13(process.cwd(), ".ralph");
2130
+ const claudeDir = join13(process.cwd(), ".claude");
2131
+ if (existsSync11(join13(ralphDir6, "workflow.md"))) {
2132
+ setStatus("exists");
2133
+ return;
2134
+ }
2135
+ const initialize = async () => {
2136
+ const templatesDir = join13(__dirname, "..", "..", "templates");
2137
+ setStatus("creating");
2138
+ try {
2139
+ const allCreated = [];
2140
+ const allSkipped = [];
2141
+ const allErrors = [];
2142
+ const ralphResult = copyTemplates(templatesDir, ralphDir6, [
2143
+ { src: "workflow.md", dest: "workflow.md" }
2144
+ ]);
2145
+ allCreated.push(...ralphResult.created.map((f) => `.ralph/${f}`));
2146
+ allSkipped.push(...ralphResult.skipped.map((f) => `.ralph/${f}`));
2147
+ allErrors.push(...ralphResult.errors);
2148
+ const skillsResult = copyTemplates(templatesDir, claudeDir, [
2149
+ { src: "skills/manage-tasks/SKILL.md", dest: "skills/manage-tasks/SKILL.md" }
2150
+ ]);
2151
+ allCreated.push(...skillsResult.created.map((f) => `.claude/${f}`));
2152
+ allSkipped.push(...skillsResult.skipped.map((f) => `.claude/${f}`));
2153
+ allErrors.push(...skillsResult.errors);
2154
+ const agentsResult = copyTemplates(templatesDir, claudeDir, [
2155
+ { src: "agents/make-tests.md", dest: "agents/make-tests.md" },
2156
+ { src: "agents/write-docs.md", dest: "agents/write-docs.md" },
2157
+ { src: "agents/run-tests.md", dest: "agents/run-tests.md" }
2158
+ ]);
2159
+ allCreated.push(...agentsResult.created.map((f) => `.claude/${f}`));
2160
+ allSkipped.push(...agentsResult.skipped.map((f) => `.claude/${f}`));
2161
+ allErrors.push(...agentsResult.errors);
2162
+ const gitignorePath = join13(process.cwd(), ".gitignore");
2163
+ const eventsLogEntry = ".ralph/events-*.jsonl";
2164
+ if (existsSync11(gitignorePath)) {
2165
+ const content = readFileSync8(gitignorePath, "utf-8");
2166
+ if (!content.includes(eventsLogEntry)) {
2167
+ const newline = content.endsWith("\n") ? "" : "\n";
2168
+ appendFileSync2(gitignorePath, `${newline}${eventsLogEntry}
2169
+ `);
2170
+ allCreated.push("(added .ralph/events-*.jsonl to .gitignore)");
2171
+ }
2172
+ } else {
2173
+ writeFileSync4(gitignorePath, `${eventsLogEntry}
2174
+ `);
2175
+ allCreated.push("(created .gitignore with .ralph/events-*.jsonl)");
2176
+ }
2177
+ setCreatedFiles(allCreated);
2178
+ setSkippedFiles(allSkipped);
2179
+ setErrors(allErrors);
2180
+ setStatus("done");
2181
+ setTimeout(() => process.exit(0), 100);
2182
+ } catch (error) {
2183
+ setErrors([`Failed to initialize: ${error}`]);
2184
+ setStatus("done");
2185
+ setTimeout(() => process.exit(1), 100);
2186
+ }
2187
+ };
2188
+ initialize();
2189
+ }, []);
2190
+ if (status === "checking") {
2191
+ return /* @__PURE__ */ React11.createElement(Text10, null, "Checking directories...");
2192
+ }
2193
+ if (status === "exists") {
2194
+ return /* @__PURE__ */ React11.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React11.createElement(Text10, { color: "yellow" }, "Ralph is already initialized"), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "To reinitialize, remove the workflow first: rm .ralph/workflow.md"));
2195
+ }
2196
+ if (status === "creating") {
2197
+ return /* @__PURE__ */ React11.createElement(Text10, { color: "cyan" }, "Initializing ralph...");
2198
+ }
2199
+ return /* @__PURE__ */ React11.createElement(Box7, { flexDirection: "column" }, createdFiles.map((file) => /* @__PURE__ */ React11.createElement(Text10, { key: file }, /* @__PURE__ */ React11.createElement(Text10, { color: "green" }, "\u2713"), " Created ", /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, file))), skippedFiles.map((file) => /* @__PURE__ */ React11.createElement(Text10, { key: file }, /* @__PURE__ */ React11.createElement(Text10, { color: "yellow" }, "\u25CB"), " Skipped ", /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, file), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " (already exists)"))), errors.map((error, i) => /* @__PURE__ */ React11.createElement(Text10, { key: i }, /* @__PURE__ */ React11.createElement(Text10, { color: "red" }, "\u2717"), " ", error)), errors.length === 0 && /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(Text10, { color: "green" }, "\n", "Ralph initialized successfully!"), /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "\n", "Next steps:"), /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { color: "cyan" }, " 1. Edit .ralph/workflow.md"), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " - Customize build commands for your project")), /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { color: "cyan" }, " 2. Initialize beads"), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " - Run `bd init` to set up the issue tracker")), /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { color: "cyan" }, " 3. Create issues"), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, ' - Run `bd create --title="..." --type=task` to add work')), /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "\n", "Then run: "), /* @__PURE__ */ React11.createElement(Text10, { color: "cyan" }, "ralph"), "\n")));
2200
+ }
2201
+
2202
+ // src/lib/getClaudeVersion.ts
2203
+ import { execSync as execSync4 } from "child_process";
2204
+ var getClaudeVersion = () => {
2205
+ try {
2206
+ const output = execSync4("claude --version", {
2207
+ encoding: "utf-8",
2208
+ stdio: ["pipe", "pipe", "pipe"]
2209
+ }).trim();
2210
+ const match = output.match(/^([\d.]+)/);
2211
+ return match ? match[1] : "unknown";
2212
+ } catch {
2213
+ return "unknown";
2214
+ }
2215
+ };
2216
+
2217
+ // src/lib/getOpenIssueCount.ts
2218
+ import { execSync as execSync5 } from "child_process";
2219
+ var getOpenIssueCount = () => {
2220
+ try {
2221
+ const output = execSync5("bd list --status=open --json", {
2222
+ encoding: "utf-8",
2223
+ stdio: ["pipe", "pipe", "pipe"]
2224
+ }).trim();
2225
+ const issues = JSON.parse(output);
2226
+ return Array.isArray(issues) ? issues.length : 0;
2227
+ } catch {
2228
+ return 0;
2229
+ }
2230
+ };
2231
+
2232
+ // src/lib/getDefaultSessions.ts
2233
+ var getDefaultSessions = () => {
2234
+ const openIssues = getOpenIssueCount();
2235
+ if (openIssues === 0) {
2236
+ return 10;
2237
+ }
2238
+ const calculated = Math.ceil(openIssues * 1.2);
2239
+ return Math.max(10, Math.min(100, calculated));
2240
+ };
2241
+
2242
+ // src/lib/getLatestLogFile.ts
2243
+ import { join as join14 } from "path";
2244
+ var getLatestLogFile = () => {
2245
+ const maxNumber = findMaxLogNumber();
2246
+ if (maxNumber === 0) {
2247
+ return void 0;
2248
+ }
2249
+ const ralphDir6 = join14(process.cwd(), ".ralph");
2250
+ return join14(ralphDir6, `events-${maxNumber}.jsonl`);
2251
+ };
2252
+
2253
+ // package.json
2254
+ var package_default = {
2255
+ name: "@herbcaudill/ralph",
2256
+ version: "1.0.2",
2257
+ description: "Autonomous AI session engine for Claude CLI",
2258
+ type: "module",
2259
+ main: "./dist/index.js",
2260
+ types: "./dist/index.d.ts",
2261
+ bin: {
2262
+ ralph: "./bin/ralph.js"
2263
+ },
2264
+ files: [
2265
+ "dist",
2266
+ "bin",
2267
+ "templates"
2268
+ ],
2269
+ scripts: {
2270
+ build: "tsup",
2271
+ dev: "tsc --watch",
2272
+ typecheck: "tsc --noEmit",
2273
+ ralph: "tsx src/index.ts",
2274
+ "test:all": "pnpm typecheck && vitest run",
2275
+ test: "vitest run",
2276
+ "test:e2e": "vitest --config vitest.e2e.config.ts",
2277
+ "test:watch": "vitest --watch",
2278
+ "test:ui": "vitest --ui",
2279
+ format: "prettier --write . --log-level silent",
2280
+ prepublishOnly: "pnpm build"
2281
+ },
2282
+ keywords: [
2283
+ "claude",
2284
+ "ai",
2285
+ "automation",
2286
+ "cli",
2287
+ "session",
2288
+ "autonomous"
2289
+ ],
2290
+ author: "Herb Caudill",
2291
+ license: "MIT",
2292
+ repository: {
2293
+ type: "git",
2294
+ url: "https://github.com/HerbCaudill/ralph.git"
2295
+ },
2296
+ dependencies: {
2297
+ "@anthropic-ai/claude-agent-sdk": "^0.2.7",
2298
+ chalk: "^5.6.2",
2299
+ commander: "^14.0.2",
2300
+ ink: "^6.6.0",
2301
+ "ink-big-text": "^2.0.0",
2302
+ "ink-gradient": "^3.0.0",
2303
+ "ink-select-input": "^6.2.0",
2304
+ "ink-spinner": "^5.0.0",
2305
+ "ink-text-input": "^6.0.0",
2306
+ react: "^19.2.3"
2307
+ },
2308
+ devDependencies: {
2309
+ "@herbcaudill/ralph-shared": "workspace:*",
2310
+ "@types/node": "^24.10.1",
2311
+ "@types/react": "^19.2.8",
2312
+ "@vitest/ui": "^4.0.17",
2313
+ execa: "^9.6.1",
2314
+ "ink-testing-library": "^4.0.0",
2315
+ prettier: "^3.5.3",
2316
+ tsup: "^8.5.1",
2317
+ tsx: "^4.21.0",
2318
+ typescript: "~5.9.3",
2319
+ vitest: "^4.0.17"
2320
+ },
2321
+ engines: {
2322
+ node: ">=18.0.0"
2323
+ }
2324
+ };
2325
+
2326
+ // src/cli.ts
2327
+ var program = new Command().name("ralph").description("Autonomous AI session engine for Claude CLI").version(package_default.version).argument(
2328
+ "[sessions]",
2329
+ "number of sessions (default: 120% of open issues, min 10, max 100)",
2330
+ (val) => parseInt(val, 10)
2331
+ ).option("--replay [file]", "replay events from log file").option("--watch", "watch for new beads issues after completion").option("--json", "output events as newline-delimited JSON to stdout").option("--agent <name>", "agent to use (e.g., claude, codex)", "claude").action(
2332
+ (sessionsArg, options) => {
2333
+ const sessions = sessionsArg ?? getDefaultSessions();
2334
+ const replayFile = options.replay !== void 0 ? typeof options.replay === "string" ? options.replay : getLatestLogFile() : void 0;
2335
+ const claudeVersion = getClaudeVersion();
2336
+ const ralphVersion = package_default.version;
2337
+ const watch = options.watch === true;
2338
+ const json = options.json === true;
2339
+ const agent = options.agent;
2340
+ const validAgents = ["claude", "codex"];
2341
+ if (!validAgents.includes(agent)) {
2342
+ console.error(
2343
+ `Error: Invalid agent "${agent}". Available agents: ${validAgents.join(", ")}`
2344
+ );
2345
+ process.exit(1);
2346
+ }
2347
+ if (!json) {
2348
+ process.stdout.write("\x1B[2J\x1B[H");
2349
+ }
2350
+ render(
2351
+ React12.createElement(App, {
2352
+ sessions,
2353
+ replayFile,
2354
+ claudeVersion,
2355
+ ralphVersion,
2356
+ watch,
2357
+ json,
2358
+ agent
2359
+ })
2360
+ );
2361
+ }
2362
+ );
2363
+ program.command("init").description("initialize .ralph directory with templates").action(() => {
2364
+ render(React12.createElement(InitRalph));
2365
+ });
2366
+ program.command("todo [description...]").description("add a todo item and commit it (safe to use while ralph is running)").action(
2367
+ async (descriptionParts) => {
2368
+ let description = descriptionParts.join(" ").trim();
2369
+ if (!description) {
2370
+ const readline = await import("readline");
2371
+ const rl = readline.createInterface({
2372
+ input: process.stdin,
2373
+ output: process.stdout
2374
+ });
2375
+ description = await new Promise((resolve) => {
2376
+ rl.question("Todo: ", (answer) => {
2377
+ rl.close();
2378
+ resolve(answer.trim());
2379
+ });
2380
+ });
2381
+ if (!description) {
2382
+ console.error("No todo description provided");
2383
+ process.exit(1);
2384
+ }
2385
+ }
2386
+ try {
2387
+ addTodo(description);
2388
+ } catch (error) {
2389
+ console.error(`Failed to add todo: ${error instanceof Error ? error.message : error}`);
2390
+ process.exit(1);
2391
+ }
2392
+ }
2393
+ );
2394
+
2395
+ // src/index.ts
2396
+ var run = () => {
2397
+ program.parse(process.argv);
5
2398
  };
6
- /** Run if called directly as a script. */
7
2399
  if (import.meta.url === `file://${process.argv[1]}`) {
8
- run();
2400
+ run();
9
2401
  }
2402
+ export {
2403
+ run
2404
+ };
10
2405
  //# sourceMappingURL=index.js.map