ralph-cli-sandboxed 0.2.1 → 0.2.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/README.md +16 -2
- package/dist/commands/once.d.ts +1 -1
- package/dist/commands/once.js +5 -1
- package/dist/commands/prd.js +2 -2
- package/dist/commands/run.js +31 -2
- package/dist/config/cli-providers.json +2 -2
- package/docs/run-state-machine.md +68 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -71,6 +71,20 @@ After running `ralph init`, you'll have:
|
|
|
71
71
|
- **Rust** - `cargo check`, `cargo test`
|
|
72
72
|
- **Custom** - Define your own commands
|
|
73
73
|
|
|
74
|
+
### Supported CLI Providers
|
|
75
|
+
|
|
76
|
+
Ralph supports multiple AI CLI tools. Select your provider during `ralph init`:
|
|
77
|
+
|
|
78
|
+
| CLI | Status | Environment Variables | Notes |
|
|
79
|
+
|-----|--------|----------------------|-------|
|
|
80
|
+
| [Claude Code](https://github.com/anthropics/claude-code) | Working | `ANTHROPIC_API_KEY` | Default provider. Also supports ~/.claude OAuth credentials |
|
|
81
|
+
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | Working | `GEMINI_API_KEY`, `GOOGLE_API_KEY` | |
|
|
82
|
+
| [OpenCode](https://github.com/anomalyco/opencode) | Working | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY` | Requires [PR #9073](https://github.com/anomalyco/opencode/pull/9073) |
|
|
83
|
+
| [Aider](https://github.com/paul-gauthier/aider) | Untested | `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` | |
|
|
84
|
+
| [Codex CLI](https://github.com/openai/codex) | Untested | `OPENAI_API_KEY` | |
|
|
85
|
+
| [AMP](https://ampcode.com/) | Untested | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` | |
|
|
86
|
+
| Custom | - | User-defined | Configure your own CLI |
|
|
87
|
+
|
|
74
88
|
### CLI Configuration
|
|
75
89
|
|
|
76
90
|
Ralph can be configured to use different AI CLI tools. By default, it uses Claude Code. Configure in `.ralph/config.json`:
|
|
@@ -221,8 +235,8 @@ podman run -v $(pwd):/workspace -v /workspace/node_modules your-image
|
|
|
221
235
|
## Requirements
|
|
222
236
|
|
|
223
237
|
- Node.js 18+
|
|
224
|
-
- [
|
|
225
|
-
-
|
|
238
|
+
- A supported AI CLI tool installed (see [Supported CLI Providers](#supported-cli-providers))
|
|
239
|
+
- API key or subscription for your chosen provider
|
|
226
240
|
|
|
227
241
|
## License
|
|
228
242
|
|
package/dist/commands/once.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function once(
|
|
1
|
+
export declare function once(args: string[]): Promise<void>;
|
package/dist/commands/once.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
2
|
import { checkFilesExist, loadConfig, loadPrompt, getPaths, getCliConfig, requireContainer } from "../utils/config.js";
|
|
3
3
|
import { resolvePromptVariables } from "../templates/prompts.js";
|
|
4
|
-
export async function once(
|
|
4
|
+
export async function once(args) {
|
|
5
|
+
const debug = args.includes("--debug") || args.includes("-d");
|
|
5
6
|
requireContainer("once");
|
|
6
7
|
checkFilesExist();
|
|
7
8
|
const config = loadConfig();
|
|
@@ -26,6 +27,9 @@ export async function once(_args) {
|
|
|
26
27
|
...promptArgs,
|
|
27
28
|
promptValue,
|
|
28
29
|
];
|
|
30
|
+
if (debug) {
|
|
31
|
+
console.log(`[debug] ${cliConfig.command} ${cliArgs.map(a => a.includes(" ") ? `"${a}"` : a).join(" ")}\n`);
|
|
32
|
+
}
|
|
29
33
|
return new Promise((resolve, reject) => {
|
|
30
34
|
const proc = spawn(cliConfig.command, cliArgs, {
|
|
31
35
|
stdio: "inherit",
|
package/dist/commands/prd.js
CHANGED
|
@@ -216,10 +216,10 @@ export function parseListArgs(args) {
|
|
|
216
216
|
process.exit(1);
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
|
-
else if (args[i] === "--passes") {
|
|
219
|
+
else if (args[i] === "--passes" || args[i] === "--passed") {
|
|
220
220
|
passesFilter = true;
|
|
221
221
|
}
|
|
222
|
-
else if (args[i] === "--no-passes") {
|
|
222
|
+
else if (args[i] === "--no-passes" || args[i] === "--no-passed" || args[i] === "--not-passed" || args[i] === "--not-passes") {
|
|
223
223
|
passesFilter = false;
|
|
224
224
|
}
|
|
225
225
|
}
|
package/dist/commands/run.js
CHANGED
|
@@ -25,7 +25,7 @@ function createFilteredPrd(prdPath, category) {
|
|
|
25
25
|
hasIncomplete: filteredItems.length > 0
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
async function runIteration(prompt, paths, sandboxed, filteredPrdPath, cliConfig) {
|
|
28
|
+
async function runIteration(prompt, paths, sandboxed, filteredPrdPath, cliConfig, debug) {
|
|
29
29
|
return new Promise((resolve, reject) => {
|
|
30
30
|
let output = "";
|
|
31
31
|
// Build CLI arguments: config args + yolo args + prompt args
|
|
@@ -43,6 +43,9 @@ async function runIteration(prompt, paths, sandboxed, filteredPrdPath, cliConfig
|
|
|
43
43
|
const promptArgs = cliConfig.promptArgs ?? ["-p"];
|
|
44
44
|
const promptValue = `@${filteredPrdPath} @${paths.progress} ${prompt}`;
|
|
45
45
|
cliArgs.push(...promptArgs, promptValue);
|
|
46
|
+
if (debug) {
|
|
47
|
+
console.log(`[debug] ${cliConfig.command} ${cliArgs.map(a => a.includes(" ") ? `"${a}"` : a).join(" ")}\n`);
|
|
48
|
+
}
|
|
46
49
|
const proc = spawn(cliConfig.command, cliArgs, {
|
|
47
50
|
stdio: ["inherit", "pipe", "inherit"],
|
|
48
51
|
});
|
|
@@ -107,6 +110,7 @@ export async function run(args) {
|
|
|
107
110
|
let category;
|
|
108
111
|
let loopMode = false;
|
|
109
112
|
let allModeExplicit = false;
|
|
113
|
+
let debug = false;
|
|
110
114
|
const filteredArgs = [];
|
|
111
115
|
for (let i = 0; i < args.length; i++) {
|
|
112
116
|
if (args[i] === "--category" || args[i] === "-c") {
|
|
@@ -126,6 +130,9 @@ export async function run(args) {
|
|
|
126
130
|
else if (args[i] === "--all" || args[i] === "-a") {
|
|
127
131
|
allModeExplicit = true;
|
|
128
132
|
}
|
|
133
|
+
else if (args[i] === "--debug" || args[i] === "-d") {
|
|
134
|
+
debug = true;
|
|
135
|
+
}
|
|
129
136
|
else {
|
|
130
137
|
filteredArgs.push(args[i]);
|
|
131
138
|
}
|
|
@@ -176,7 +183,10 @@ export async function run(args) {
|
|
|
176
183
|
// Track temp file for cleanup
|
|
177
184
|
let filteredPrdPath = null;
|
|
178
185
|
const POLL_INTERVAL_MS = 30000; // 30 seconds between checks when waiting for new items
|
|
186
|
+
const MAX_CONSECUTIVE_FAILURES = 3; // Stop after this many consecutive failures
|
|
179
187
|
const startTime = Date.now();
|
|
188
|
+
let consecutiveFailures = 0;
|
|
189
|
+
let lastExitCode = 0;
|
|
180
190
|
try {
|
|
181
191
|
for (let i = 1; i <= iterations; i++) {
|
|
182
192
|
console.log(`\n${"=".repeat(50)}`);
|
|
@@ -249,7 +259,7 @@ export async function run(args) {
|
|
|
249
259
|
break;
|
|
250
260
|
}
|
|
251
261
|
}
|
|
252
|
-
const { exitCode, output } = await runIteration(prompt, paths, sandboxed, filteredPrdPath, cliConfig);
|
|
262
|
+
const { exitCode, output } = await runIteration(prompt, paths, sandboxed, filteredPrdPath, cliConfig, debug);
|
|
253
263
|
// Clean up temp file after each iteration
|
|
254
264
|
try {
|
|
255
265
|
unlinkSync(filteredPrdPath);
|
|
@@ -260,8 +270,27 @@ export async function run(args) {
|
|
|
260
270
|
filteredPrdPath = null;
|
|
261
271
|
if (exitCode !== 0) {
|
|
262
272
|
console.error(`\n${cliConfig.command} exited with code ${exitCode}`);
|
|
273
|
+
// Track consecutive failures to detect persistent errors (e.g., missing API key)
|
|
274
|
+
if (exitCode === lastExitCode) {
|
|
275
|
+
consecutiveFailures++;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
consecutiveFailures = 1;
|
|
279
|
+
lastExitCode = exitCode;
|
|
280
|
+
}
|
|
281
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
282
|
+
console.error(`\nStopping: ${cliConfig.command} failed ${consecutiveFailures} times in a row with exit code ${exitCode}.`);
|
|
283
|
+
console.error("This usually indicates a configuration error (e.g., missing API key).");
|
|
284
|
+
console.error("Please check your CLI configuration and try again.");
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
263
287
|
console.log("Continuing to next iteration...");
|
|
264
288
|
}
|
|
289
|
+
else {
|
|
290
|
+
// Reset failure tracking on success
|
|
291
|
+
consecutiveFailures = 0;
|
|
292
|
+
lastExitCode = 0;
|
|
293
|
+
}
|
|
265
294
|
// Check for completion signal
|
|
266
295
|
if (output.includes("<promise>COMPLETE</promise>")) {
|
|
267
296
|
if (loopMode) {
|
|
@@ -61,12 +61,12 @@
|
|
|
61
61
|
"command": "opencode",
|
|
62
62
|
"defaultArgs": [],
|
|
63
63
|
"yoloArgs": ["--yolo"],
|
|
64
|
-
"promptArgs": [],
|
|
64
|
+
"promptArgs": ["run"],
|
|
65
65
|
"docker": {
|
|
66
66
|
"install": "# Install OpenCode (as node user)\nRUN su - node -c 'curl -fsSL https://opencode.ai/install | bash' \\\n && echo 'export PATH=\"$HOME/.opencode/bin:$PATH\"' >> /home/node/.zshrc",
|
|
67
67
|
"note": "Check 'opencode --help' for available flags"
|
|
68
68
|
},
|
|
69
|
-
"envVars": ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "
|
|
69
|
+
"envVars": ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_GENERATIVE_AI_API_KEY"],
|
|
70
70
|
"credentialMount": null
|
|
71
71
|
},
|
|
72
72
|
"amp": {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Run Command State Machine
|
|
2
|
+
|
|
3
|
+
```mermaid
|
|
4
|
+
stateDiagram-v2
|
|
5
|
+
[*] --> ParseFlags : run(args)
|
|
6
|
+
|
|
7
|
+
ParseFlags --> DetermineMode
|
|
8
|
+
|
|
9
|
+
state DetermineMode {
|
|
10
|
+
[*] --> CheckLoopFlag
|
|
11
|
+
CheckLoopFlag --> LoopMode : --loop
|
|
12
|
+
CheckLoopFlag --> CheckIterationArg : no --loop
|
|
13
|
+
CheckIterationArg --> CountMode : number provided
|
|
14
|
+
CheckIterationArg --> AllMode : no number (default)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
DetermineMode --> CheckItems
|
|
18
|
+
|
|
19
|
+
state "Check Items" as CheckItems {
|
|
20
|
+
[*] --> CreateFilteredPRD
|
|
21
|
+
CreateFilteredPRD --> HasIncomplete
|
|
22
|
+
HasIncomplete --> StartIteration : yes
|
|
23
|
+
HasIncomplete --> HandleComplete : no
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
state HandleComplete {
|
|
27
|
+
[*] --> CheckMode
|
|
28
|
+
CheckMode --> WaitForNewItems : LoopMode
|
|
29
|
+
CheckMode --> PrintComplete : AllMode/CountMode
|
|
30
|
+
WaitForNewItems --> PollLoop
|
|
31
|
+
PollLoop --> CheckNewItems : every 30s
|
|
32
|
+
CheckNewItems --> StartIteration : found
|
|
33
|
+
CheckNewItems --> PollLoop : not found
|
|
34
|
+
PrintComplete --> [*]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
state "Start Iteration" as StartIteration
|
|
38
|
+
|
|
39
|
+
StartIteration --> RunCLI
|
|
40
|
+
|
|
41
|
+
state "Run CLI" as RunCLI {
|
|
42
|
+
[*] --> SpawnProcess
|
|
43
|
+
SpawnProcess --> WaitForExit
|
|
44
|
+
WaitForExit --> ProcessOutput
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
RunCLI --> CheckResult
|
|
48
|
+
|
|
49
|
+
state "Check Result" as CheckResult {
|
|
50
|
+
[*] --> CheckExitCode
|
|
51
|
+
CheckExitCode --> TrackFailure : non-zero
|
|
52
|
+
CheckExitCode --> ResetFailures : zero
|
|
53
|
+
TrackFailure --> CheckConsecutive
|
|
54
|
+
CheckConsecutive --> StopRun : >= 3 consecutive
|
|
55
|
+
CheckConsecutive --> CheckCompletionSignal : < 3
|
|
56
|
+
ResetFailures --> CheckCompletionSignal
|
|
57
|
+
CheckCompletionSignal --> HandleLoopComplete : COMPLETE signal
|
|
58
|
+
CheckCompletionSignal --> NextIteration : no signal
|
|
59
|
+
HandleLoopComplete --> WaitForNewItems : LoopMode
|
|
60
|
+
HandleLoopComplete --> PrintFinalStatus : AllMode/CountMode
|
|
61
|
+
StopRun --> [*]
|
|
62
|
+
PrintFinalStatus --> [*]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
NextIteration --> CheckIterationLimit
|
|
66
|
+
CheckIterationLimit --> CheckItems : more iterations
|
|
67
|
+
CheckIterationLimit --> [*] : limit reached
|
|
68
|
+
```
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ralph-cli-sandboxed",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "AI-driven development automation CLI for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"ralph": "
|
|
7
|
+
"ralph": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"files": [
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "
|
|
16
|
+
"build": "tsc && npm run copy-config",
|
|
17
17
|
"copy-config": "mkdir -p dist/config && cp src/config/*.json dist/config/",
|
|
18
18
|
"dev": "npx tsx src/index.ts",
|
|
19
|
-
"typecheck": "
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
20
|
"prepublishOnly": "npm run build",
|
|
21
21
|
"prepare": "npm run build"
|
|
22
22
|
},
|