@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.
- package/dist/index.js +2401 -6
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
- package/templates/core-prompt.md +3 -3
- package/templates/workflow.md +1 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -102
- package/dist/cli.js.map +0 -1
- package/dist/components/App.d.ts +0 -17
- package/dist/components/App.d.ts.map +0 -1
- package/dist/components/App.js +0 -18
- package/dist/components/App.js.map +0 -1
- package/dist/components/EnhancedTextInput.d.ts +0 -22
- package/dist/components/EnhancedTextInput.d.ts.map +0 -1
- package/dist/components/EnhancedTextInput.js +0 -130
- package/dist/components/EnhancedTextInput.js.map +0 -1
- package/dist/components/EnhancedTextInput.test.d.ts +0 -2
- package/dist/components/EnhancedTextInput.test.d.ts.map +0 -1
- package/dist/components/EnhancedTextInput.test.js +0 -99
- package/dist/components/EnhancedTextInput.test.js.map +0 -1
- package/dist/components/EventDisplay.d.ts +0 -14
- package/dist/components/EventDisplay.d.ts.map +0 -1
- package/dist/components/EventDisplay.js +0 -91
- package/dist/components/EventDisplay.js.map +0 -1
- package/dist/components/EventDisplay.replay.test.d.ts +0 -2
- package/dist/components/EventDisplay.replay.test.d.ts.map +0 -1
- package/dist/components/EventDisplay.replay.test.js +0 -379
- package/dist/components/EventDisplay.replay.test.js.map +0 -1
- package/dist/components/EventDisplay.test.d.ts +0 -2
- package/dist/components/EventDisplay.test.d.ts.map +0 -1
- package/dist/components/EventDisplay.test.js +0 -349
- package/dist/components/EventDisplay.test.js.map +0 -1
- package/dist/components/FullScreenLayout.d.ts +0 -15
- package/dist/components/FullScreenLayout.d.ts.map +0 -1
- package/dist/components/FullScreenLayout.js +0 -20
- package/dist/components/FullScreenLayout.js.map +0 -1
- package/dist/components/Header.d.ts +0 -10
- package/dist/components/Header.d.ts.map +0 -1
- package/dist/components/Header.js +0 -22
- package/dist/components/Header.js.map +0 -1
- package/dist/components/Header.test.d.ts +0 -2
- package/dist/components/Header.test.d.ts.map +0 -1
- package/dist/components/Header.test.js +0 -27
- package/dist/components/Header.test.js.map +0 -1
- package/dist/components/InitRalph.d.ts +0 -8
- package/dist/components/InitRalph.d.ts.map +0 -1
- package/dist/components/InitRalph.js +0 -136
- package/dist/components/InitRalph.js.map +0 -1
- package/dist/components/IterationRunner.d.ts +0 -10
- package/dist/components/IterationRunner.d.ts.map +0 -1
- package/dist/components/IterationRunner.js +0 -536
- package/dist/components/IterationRunner.js.map +0 -1
- package/dist/components/IterationRunner.test.d.ts +0 -46
- package/dist/components/IterationRunner.test.d.ts.map +0 -1
- package/dist/components/IterationRunner.test.js +0 -69
- package/dist/components/IterationRunner.test.js.map +0 -1
- package/dist/components/IterationRunner.types.d.ts +0 -30
- package/dist/components/IterationRunner.types.d.ts.map +0 -1
- package/dist/components/IterationRunner.types.js +0 -2
- package/dist/components/IterationRunner.types.js.map +0 -1
- package/dist/components/JsonOutput.d.ts +0 -13
- package/dist/components/JsonOutput.d.ts.map +0 -1
- package/dist/components/JsonOutput.js +0 -269
- package/dist/components/JsonOutput.js.map +0 -1
- package/dist/components/JsonOutput.test.d.ts +0 -2
- package/dist/components/JsonOutput.test.d.ts.map +0 -1
- package/dist/components/JsonOutput.test.js +0 -39
- package/dist/components/JsonOutput.test.js.map +0 -1
- package/dist/components/ProgressBar.d.ts +0 -18
- package/dist/components/ProgressBar.d.ts.map +0 -1
- package/dist/components/ProgressBar.js +0 -30
- package/dist/components/ProgressBar.js.map +0 -1
- package/dist/components/ProgressBar.test.d.ts +0 -2
- package/dist/components/ProgressBar.test.d.ts.map +0 -1
- package/dist/components/ProgressBar.test.js +0 -55
- package/dist/components/ProgressBar.test.js.map +0 -1
- package/dist/components/ReplayLog.d.ts +0 -7
- package/dist/components/ReplayLog.d.ts.map +0 -1
- package/dist/components/ReplayLog.js +0 -57
- package/dist/components/ReplayLog.js.map +0 -1
- package/dist/components/StreamingText.d.ts +0 -11
- package/dist/components/StreamingText.d.ts.map +0 -1
- package/dist/components/StreamingText.js +0 -37
- package/dist/components/StreamingText.js.map +0 -1
- package/dist/components/StreamingText.test.d.ts +0 -2
- package/dist/components/StreamingText.test.d.ts.map +0 -1
- package/dist/components/StreamingText.test.js +0 -125
- package/dist/components/StreamingText.test.js.map +0 -1
- package/dist/components/ToolUse.d.ts +0 -16
- package/dist/components/ToolUse.d.ts.map +0 -1
- package/dist/components/ToolUse.js +0 -11
- package/dist/components/ToolUse.js.map +0 -1
- package/dist/components/ToolUse.test.d.ts +0 -2
- package/dist/components/ToolUse.test.d.ts.map +0 -1
- package/dist/components/ToolUse.test.js +0 -43
- package/dist/components/ToolUse.test.js.map +0 -1
- package/dist/components/blocksToLines.d.ts +0 -6
- package/dist/components/blocksToLines.d.ts.map +0 -1
- package/dist/components/blocksToLines.js +0 -15
- package/dist/components/blocksToLines.js.map +0 -1
- package/dist/components/eventToBlocks.d.ts +0 -22
- package/dist/components/eventToBlocks.d.ts.map +0 -1
- package/dist/components/eventToBlocks.js +0 -166
- package/dist/components/eventToBlocks.js.map +0 -1
- package/dist/components/eventToBlocks.test.d.ts +0 -2
- package/dist/components/eventToBlocks.test.d.ts.map +0 -1
- package/dist/components/eventToBlocks.test.js +0 -466
- package/dist/components/eventToBlocks.test.js.map +0 -1
- package/dist/components/findNextWordBoundary.d.ts +0 -10
- package/dist/components/findNextWordBoundary.d.ts.map +0 -1
- package/dist/components/findNextWordBoundary.js +0 -23
- package/dist/components/findNextWordBoundary.js.map +0 -1
- package/dist/components/findPreviousWordBoundary.d.ts +0 -10
- package/dist/components/findPreviousWordBoundary.d.ts.map +0 -1
- package/dist/components/findPreviousWordBoundary.js +0 -23
- package/dist/components/findPreviousWordBoundary.js.map +0 -1
- package/dist/components/processEvents.d.ts +0 -11
- package/dist/components/processEvents.d.ts.map +0 -1
- package/dist/components/processEvents.js +0 -87
- package/dist/components/processEvents.js.map +0 -1
- package/dist/components/renderStaticItem.d.ts +0 -7
- package/dist/components/renderStaticItem.d.ts.map +0 -1
- package/dist/components/renderStaticItem.js +0 -23
- package/dist/components/renderStaticItem.js.map +0 -1
- package/dist/components/replay.d.ts +0 -5
- package/dist/components/replay.d.ts.map +0 -1
- package/dist/components/replay.js +0 -49
- package/dist/components/replay.js.map +0 -1
- package/dist/components/test-helpers/ControlledInput.d.ts +0 -14
- package/dist/components/test-helpers/ControlledInput.d.ts.map +0 -1
- package/dist/components/test-helpers/ControlledInput.js +0 -15
- package/dist/components/test-helpers/ControlledInput.js.map +0 -1
- package/dist/components/useContentHeight.d.ts +0 -9
- package/dist/components/useContentHeight.d.ts.map +0 -1
- package/dist/components/useContentHeight.js +0 -16
- package/dist/components/useContentHeight.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/lib/MessageQueue.d.ts +0 -33
- package/dist/lib/MessageQueue.d.ts.map +0 -1
- package/dist/lib/MessageQueue.js +0 -96
- package/dist/lib/MessageQueue.js.map +0 -1
- package/dist/lib/MessageQueue.test.d.ts +0 -2
- package/dist/lib/MessageQueue.test.d.ts.map +0 -1
- package/dist/lib/MessageQueue.test.js +0 -224
- package/dist/lib/MessageQueue.test.js.map +0 -1
- package/dist/lib/StdinCommandHandler.d.ts +0 -48
- package/dist/lib/StdinCommandHandler.d.ts.map +0 -1
- package/dist/lib/StdinCommandHandler.js +0 -95
- package/dist/lib/StdinCommandHandler.js.map +0 -1
- package/dist/lib/StdinCommandHandler.test.d.ts +0 -2
- package/dist/lib/StdinCommandHandler.test.d.ts.map +0 -1
- package/dist/lib/StdinCommandHandler.test.js +0 -93
- package/dist/lib/StdinCommandHandler.test.js.map +0 -1
- package/dist/lib/WorktreeManager.d.ts +0 -114
- package/dist/lib/WorktreeManager.d.ts.map +0 -1
- package/dist/lib/WorktreeManager.js +0 -350
- package/dist/lib/WorktreeManager.js.map +0 -1
- package/dist/lib/WorktreeManager.test.d.ts +0 -2
- package/dist/lib/WorktreeManager.test.d.ts.map +0 -1
- package/dist/lib/WorktreeManager.test.js +0 -261
- package/dist/lib/WorktreeManager.test.js.map +0 -1
- package/dist/lib/addTodo.d.ts +0 -11
- package/dist/lib/addTodo.d.ts.map +0 -1
- package/dist/lib/addTodo.js +0 -51
- package/dist/lib/addTodo.js.map +0 -1
- package/dist/lib/beadsClient.d.ts +0 -46
- package/dist/lib/beadsClient.d.ts.map +0 -1
- package/dist/lib/beadsClient.js +0 -172
- package/dist/lib/beadsClient.js.map +0 -1
- package/dist/lib/captureBeadsSnapshot.d.ts +0 -4
- package/dist/lib/captureBeadsSnapshot.d.ts.map +0 -1
- package/dist/lib/captureBeadsSnapshot.js +0 -24
- package/dist/lib/captureBeadsSnapshot.js.map +0 -1
- package/dist/lib/captureStartupSnapshot.d.ts +0 -10
- package/dist/lib/captureStartupSnapshot.d.ts.map +0 -1
- package/dist/lib/captureStartupSnapshot.js +0 -29
- package/dist/lib/captureStartupSnapshot.js.map +0 -1
- package/dist/lib/captureTodoSnapshot.d.ts +0 -4
- package/dist/lib/captureTodoSnapshot.d.ts.map +0 -1
- package/dist/lib/captureTodoSnapshot.js +0 -24
- package/dist/lib/captureTodoSnapshot.js.map +0 -1
- package/dist/lib/copyTemplates.d.ts +0 -22
- package/dist/lib/copyTemplates.d.ts.map +0 -1
- package/dist/lib/copyTemplates.js +0 -32
- package/dist/lib/copyTemplates.js.map +0 -1
- package/dist/lib/createStdinCommandHandler.d.ts +0 -28
- package/dist/lib/createStdinCommandHandler.d.ts.map +0 -1
- package/dist/lib/createStdinCommandHandler.js +0 -67
- package/dist/lib/createStdinCommandHandler.js.map +0 -1
- package/dist/lib/createUserMessage.d.ts +0 -6
- package/dist/lib/createUserMessage.d.ts.map +0 -1
- package/dist/lib/createUserMessage.js +0 -13
- package/dist/lib/createUserMessage.js.map +0 -1
- package/dist/lib/debug.d.ts +0 -19
- package/dist/lib/debug.d.ts.map +0 -1
- package/dist/lib/debug.js +0 -54
- package/dist/lib/debug.js.map +0 -1
- package/dist/lib/findMaxLogNumber.d.ts +0 -6
- package/dist/lib/findMaxLogNumber.d.ts.map +0 -1
- package/dist/lib/findMaxLogNumber.js +0 -26
- package/dist/lib/findMaxLogNumber.js.map +0 -1
- package/dist/lib/findMaxLogNumber.test.d.ts +0 -2
- package/dist/lib/findMaxLogNumber.test.d.ts.map +0 -1
- package/dist/lib/findMaxLogNumber.test.js +0 -39
- package/dist/lib/findMaxLogNumber.test.js.map +0 -1
- package/dist/lib/formatContentBlock.d.ts +0 -6
- package/dist/lib/formatContentBlock.d.ts.map +0 -1
- package/dist/lib/formatContentBlock.js +0 -18
- package/dist/lib/formatContentBlock.js.map +0 -1
- package/dist/lib/formatIterationHeader.d.ts +0 -5
- package/dist/lib/formatIterationHeader.d.ts.map +0 -1
- package/dist/lib/formatIterationHeader.js +0 -8
- package/dist/lib/formatIterationHeader.js.map +0 -1
- package/dist/lib/formatText.d.ts +0 -5
- package/dist/lib/formatText.d.ts.map +0 -1
- package/dist/lib/formatText.js +0 -33
- package/dist/lib/formatText.js.map +0 -1
- package/dist/lib/formatToolUse.d.ts +0 -7
- package/dist/lib/formatToolUse.d.ts.map +0 -1
- package/dist/lib/formatToolUse.js +0 -14
- package/dist/lib/formatToolUse.js.map +0 -1
- package/dist/lib/formatUserMessage.d.ts +0 -5
- package/dist/lib/formatUserMessage.d.ts.map +0 -1
- package/dist/lib/formatUserMessage.js +0 -8
- package/dist/lib/formatUserMessage.js.map +0 -1
- package/dist/lib/getBaseCwd.d.ts +0 -6
- package/dist/lib/getBaseCwd.d.ts.map +0 -1
- package/dist/lib/getBaseCwd.js +0 -6
- package/dist/lib/getBaseCwd.js.map +0 -1
- package/dist/lib/getBeadsProgress.d.ts +0 -8
- package/dist/lib/getBeadsProgress.d.ts.map +0 -1
- package/dist/lib/getBeadsProgress.js +0 -35
- package/dist/lib/getBeadsProgress.js.map +0 -1
- package/dist/lib/getClaudeVersion.d.ts +0 -6
- package/dist/lib/getClaudeVersion.d.ts.map +0 -1
- package/dist/lib/getClaudeVersion.js +0 -21
- package/dist/lib/getClaudeVersion.js.map +0 -1
- package/dist/lib/getClaudeVersion.test.d.ts +0 -2
- package/dist/lib/getClaudeVersion.test.d.ts.map +0 -1
- package/dist/lib/getClaudeVersion.test.js +0 -39
- package/dist/lib/getClaudeVersion.test.js.map +0 -1
- package/dist/lib/getDefaultIterations.d.ts +0 -6
- package/dist/lib/getDefaultIterations.d.ts.map +0 -1
- package/dist/lib/getDefaultIterations.js +0 -14
- package/dist/lib/getDefaultIterations.js.map +0 -1
- package/dist/lib/getDefaultIterations.test.d.ts +0 -2
- package/dist/lib/getDefaultIterations.test.d.ts.map +0 -1
- package/dist/lib/getDefaultIterations.test.js +0 -39
- package/dist/lib/getDefaultIterations.test.js.map +0 -1
- package/dist/lib/getLatestLogFile.d.ts +0 -6
- package/dist/lib/getLatestLogFile.d.ts.map +0 -1
- package/dist/lib/getLatestLogFile.js +0 -15
- package/dist/lib/getLatestLogFile.js.map +0 -1
- package/dist/lib/getNextLogFile.d.ts +0 -7
- package/dist/lib/getNextLogFile.d.ts.map +0 -1
- package/dist/lib/getNextLogFile.js +0 -18
- package/dist/lib/getNextLogFile.js.map +0 -1
- package/dist/lib/getNextLogFile.test.d.ts +0 -2
- package/dist/lib/getNextLogFile.test.d.ts.map +0 -1
- package/dist/lib/getNextLogFile.test.js +0 -66
- package/dist/lib/getNextLogFile.test.js.map +0 -1
- package/dist/lib/getOpenIssueCount.d.ts +0 -6
- package/dist/lib/getOpenIssueCount.d.ts.map +0 -1
- package/dist/lib/getOpenIssueCount.js +0 -19
- package/dist/lib/getOpenIssueCount.js.map +0 -1
- package/dist/lib/getOpenIssueCount.test.d.ts +0 -2
- package/dist/lib/getOpenIssueCount.test.d.ts.map +0 -1
- package/dist/lib/getOpenIssueCount.test.js +0 -31
- package/dist/lib/getOpenIssueCount.test.js.map +0 -1
- package/dist/lib/getProgress.d.ts +0 -14
- package/dist/lib/getProgress.d.ts.map +0 -1
- package/dist/lib/getProgress.js +0 -30
- package/dist/lib/getProgress.js.map +0 -1
- package/dist/lib/getProgress.test.d.ts +0 -2
- package/dist/lib/getProgress.test.d.ts.map +0 -1
- package/dist/lib/getProgress.test.js +0 -218
- package/dist/lib/getProgress.test.js.map +0 -1
- package/dist/lib/getPromptContent.d.ts +0 -8
- package/dist/lib/getPromptContent.d.ts.map +0 -1
- package/dist/lib/getPromptContent.js +0 -31
- package/dist/lib/getPromptContent.js.map +0 -1
- package/dist/lib/getTerminalSize.d.ts +0 -8
- package/dist/lib/getTerminalSize.d.ts.map +0 -1
- package/dist/lib/getTerminalSize.js +0 -10
- package/dist/lib/getTerminalSize.js.map +0 -1
- package/dist/lib/getTodoProgress.d.ts +0 -4
- package/dist/lib/getTodoProgress.d.ts.map +0 -1
- package/dist/lib/getTodoProgress.js +0 -22
- package/dist/lib/getTodoProgress.js.map +0 -1
- package/dist/lib/insertTodo.d.ts +0 -7
- package/dist/lib/insertTodo.d.ts.map +0 -1
- package/dist/lib/insertTodo.js +0 -22
- package/dist/lib/insertTodo.js.map +0 -1
- package/dist/lib/outputEvent.d.ts +0 -5
- package/dist/lib/outputEvent.d.ts.map +0 -1
- package/dist/lib/outputEvent.js +0 -7
- package/dist/lib/outputEvent.js.map +0 -1
- package/dist/lib/parseStdinCommand.d.ts +0 -25
- package/dist/lib/parseStdinCommand.d.ts.map +0 -1
- package/dist/lib/parseStdinCommand.js +0 -43
- package/dist/lib/parseStdinCommand.js.map +0 -1
- package/dist/lib/parseStdinCommand.test.d.ts +0 -2
- package/dist/lib/parseStdinCommand.test.d.ts.map +0 -1
- package/dist/lib/parseStdinCommand.test.js +0 -93
- package/dist/lib/parseStdinCommand.test.js.map +0 -1
- package/dist/lib/parseTaskLifecycle.d.ts +0 -18
- package/dist/lib/parseTaskLifecycle.d.ts.map +0 -1
- package/dist/lib/parseTaskLifecycle.js +0 -30
- package/dist/lib/parseTaskLifecycle.js.map +0 -1
- package/dist/lib/parseTaskLifecycle.test.d.ts +0 -2
- package/dist/lib/parseTaskLifecycle.test.d.ts.map +0 -1
- package/dist/lib/parseTaskLifecycle.test.js +0 -122
- package/dist/lib/parseTaskLifecycle.test.js.map +0 -1
- package/dist/lib/parseWorktreeFromBranch.d.ts +0 -13
- package/dist/lib/parseWorktreeFromBranch.d.ts.map +0 -1
- package/dist/lib/parseWorktreeFromBranch.js +0 -19
- package/dist/lib/parseWorktreeFromBranch.js.map +0 -1
- package/dist/lib/processEvents.d.ts +0 -12
- package/dist/lib/processEvents.d.ts.map +0 -1
- package/dist/lib/processEvents.js +0 -113
- package/dist/lib/processEvents.js.map +0 -1
- package/dist/lib/rel.d.ts +0 -8
- package/dist/lib/rel.d.ts.map +0 -1
- package/dist/lib/rel.js +0 -19
- package/dist/lib/rel.js.map +0 -1
- package/dist/lib/rel.test.d.ts +0 -2
- package/dist/lib/rel.test.d.ts.map +0 -1
- package/dist/lib/rel.test.js +0 -51
- package/dist/lib/rel.test.js.map +0 -1
- package/dist/lib/sdkMessageToEvent.d.ts +0 -6
- package/dist/lib/sdkMessageToEvent.d.ts.map +0 -1
- package/dist/lib/sdkMessageToEvent.js +0 -12
- package/dist/lib/sdkMessageToEvent.js.map +0 -1
- package/dist/lib/shortenTempPaths.d.ts +0 -8
- package/dist/lib/shortenTempPaths.d.ts.map +0 -1
- package/dist/lib/shortenTempPaths.js +0 -12
- package/dist/lib/shortenTempPaths.js.map +0 -1
- package/dist/lib/shortenTempPaths.test.d.ts +0 -2
- package/dist/lib/shortenTempPaths.test.d.ts.map +0 -1
- package/dist/lib/shortenTempPaths.test.js +0 -46
- package/dist/lib/shortenTempPaths.test.js.map +0 -1
- package/dist/lib/types.d.ts +0 -19
- package/dist/lib/types.d.ts.map +0 -1
- package/dist/lib/types.js +0 -2
- package/dist/lib/types.js.map +0 -1
- package/dist/lib/useTerminalSize.d.ts +0 -6
- package/dist/lib/useTerminalSize.d.ts.map +0 -1
- package/dist/lib/useTerminalSize.js +0 -19
- package/dist/lib/useTerminalSize.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,2405 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
2400
|
+
run();
|
|
9
2401
|
}
|
|
2402
|
+
export {
|
|
2403
|
+
run
|
|
2404
|
+
};
|
|
10
2405
|
//# sourceMappingURL=index.js.map
|