ralph-cli-sandboxed 0.2.0 → 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/docker.js +65 -9
- package/dist/commands/init.js +4 -0
- package/dist/commands/once.d.ts +1 -1
- package/dist/commands/once.js +9 -4
- package/dist/commands/prd.js +2 -2
- package/dist/commands/run.js +35 -3
- package/dist/config/cli-providers.json +12 -5
- package/dist/templates/prompts.d.ts +1 -0
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.js +22 -1
- package/dist/utils/prompt.js +2 -0
- package/docs/run-state-machine.md +68 -0
- package/package.json +4 -3
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/docker.js
CHANGED
|
@@ -29,7 +29,7 @@ function getCliProviderSnippet(cliProvider) {
|
|
|
29
29
|
const provider = cliProvidersJson.providers[providerKey];
|
|
30
30
|
if (!provider || !provider.docker) {
|
|
31
31
|
// Default to Claude Code CLI if provider not found
|
|
32
|
-
return "# Install Claude Code CLI\nRUN curl -fsSL https://claude.ai/install.sh | bash";
|
|
32
|
+
return "# Install Claude Code CLI (as node user so it installs to /home/node/.local/bin)\nRUN su - node -c 'curl -fsSL https://claude.ai/install.sh | bash' \\\n && echo 'export PATH=\"$HOME/.local/bin:$PATH\"' >> /home/node/.zshrc";
|
|
33
33
|
}
|
|
34
34
|
return provider.docker.install;
|
|
35
35
|
}
|
|
@@ -291,9 +291,13 @@ async function buildImage(ralphDir) {
|
|
|
291
291
|
process.exit(1);
|
|
292
292
|
}
|
|
293
293
|
console.log("Building Docker image...\n");
|
|
294
|
+
// Get image name for compose project name
|
|
295
|
+
const config = loadConfig();
|
|
296
|
+
const imageName = config.imageName || `ralph-${basename(process.cwd()).toLowerCase().replace(/[^a-z0-9-]/g, "-")}`;
|
|
294
297
|
return new Promise((resolve, reject) => {
|
|
295
298
|
// Use --no-cache and --pull to ensure we always get the latest CLI versions
|
|
296
|
-
|
|
299
|
+
// Use -p to set unique project name per ralph project
|
|
300
|
+
const proc = spawn("docker", ["compose", "-p", imageName, "build", "--no-cache", "--pull"], {
|
|
297
301
|
cwd: dockerDir,
|
|
298
302
|
stdio: "inherit",
|
|
299
303
|
});
|
|
@@ -329,6 +333,27 @@ async function imageExists(imageName) {
|
|
|
329
333
|
});
|
|
330
334
|
});
|
|
331
335
|
}
|
|
336
|
+
// Get CLI provider configuration
|
|
337
|
+
function getCliProviderConfig(cliProvider) {
|
|
338
|
+
const cliProvidersJson = getCliProvidersJson();
|
|
339
|
+
const providerKey = cliProvider || "claude";
|
|
340
|
+
const provider = cliProvidersJson.providers[providerKey];
|
|
341
|
+
if (!provider) {
|
|
342
|
+
// Default to Claude Code CLI if provider not found
|
|
343
|
+
return {
|
|
344
|
+
name: "Claude Code",
|
|
345
|
+
command: "claude",
|
|
346
|
+
yoloArgs: ["--dangerously-skip-permissions"],
|
|
347
|
+
envVars: ["ANTHROPIC_API_KEY"],
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
name: provider.name,
|
|
352
|
+
command: provider.command,
|
|
353
|
+
yoloArgs: provider.yoloArgs || [],
|
|
354
|
+
envVars: provider.envVars || [],
|
|
355
|
+
};
|
|
356
|
+
}
|
|
332
357
|
async function runContainer(ralphDir, imageName, language, javaVersion, cliProvider) {
|
|
333
358
|
const dockerDir = join(ralphDir, DOCKER_DIR);
|
|
334
359
|
const dockerfileExists = existsSync(join(dockerDir, "Dockerfile"));
|
|
@@ -346,9 +371,37 @@ async function runContainer(ralphDir, imageName, language, javaVersion, cliProvi
|
|
|
346
371
|
console.log("");
|
|
347
372
|
}
|
|
348
373
|
}
|
|
374
|
+
// Get CLI provider info for the startup note
|
|
375
|
+
const cliConfig = getCliProviderConfig(cliProvider);
|
|
376
|
+
const yoloCommand = cliConfig.yoloArgs.length > 0
|
|
377
|
+
? `${cliConfig.command} ${cliConfig.yoloArgs.join(" ")}`
|
|
378
|
+
: cliConfig.command;
|
|
349
379
|
console.log("Starting Docker container...\n");
|
|
380
|
+
// Show note about yolo mode and credentials
|
|
381
|
+
console.log("IMPORTANT: Getting Started");
|
|
382
|
+
console.log("-".repeat(40));
|
|
383
|
+
console.log("");
|
|
384
|
+
console.log("To run ralph automation, you might need to activate YOLO mode");
|
|
385
|
+
console.log("which allows the AI to execute commands without prompts.");
|
|
386
|
+
console.log("");
|
|
387
|
+
console.log(`CLI Provider: ${cliConfig.name}`);
|
|
388
|
+
console.log(`Yolo command: ${yoloCommand}`);
|
|
389
|
+
console.log("");
|
|
390
|
+
console.log("Before running 'ralph run' or 'ralph once', ensure your");
|
|
391
|
+
console.log("credentials are configured:");
|
|
392
|
+
console.log("");
|
|
393
|
+
if (cliConfig.envVars.length > 0) {
|
|
394
|
+
console.log("Required environment variables:");
|
|
395
|
+
for (const envVar of cliConfig.envVars) {
|
|
396
|
+
console.log(` - ${envVar}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
console.log("");
|
|
400
|
+
console.log("Set them in docker-compose.yml or export before running.");
|
|
401
|
+
console.log("");
|
|
350
402
|
return new Promise((resolve, reject) => {
|
|
351
|
-
|
|
403
|
+
// Use -p to set unique project name per ralph project
|
|
404
|
+
const proc = spawn("docker", ["compose", "-p", imageName, "run", "--rm", "ralph"], {
|
|
352
405
|
cwd: dockerDir,
|
|
353
406
|
stdio: "inherit",
|
|
354
407
|
});
|
|
@@ -371,8 +424,9 @@ async function cleanImage(imageName, ralphDir) {
|
|
|
371
424
|
// First, stop any running containers via docker compose
|
|
372
425
|
if (existsSync(join(dockerDir, "docker-compose.yml"))) {
|
|
373
426
|
// Stop running containers first
|
|
427
|
+
// Use -p to target only this project's resources
|
|
374
428
|
await new Promise((resolve) => {
|
|
375
|
-
const proc = spawn("docker", ["compose", "stop", "--timeout", "5"], {
|
|
429
|
+
const proc = spawn("docker", ["compose", "-p", imageName, "stop", "--timeout", "5"], {
|
|
376
430
|
cwd: dockerDir,
|
|
377
431
|
stdio: "inherit",
|
|
378
432
|
});
|
|
@@ -384,8 +438,9 @@ async function cleanImage(imageName, ralphDir) {
|
|
|
384
438
|
});
|
|
385
439
|
});
|
|
386
440
|
// Remove containers, volumes, networks, and local images
|
|
441
|
+
// Use -p to target only this project's resources
|
|
387
442
|
await new Promise((resolve) => {
|
|
388
|
-
const proc = spawn("docker", ["compose", "down", "--rmi", "local", "-v", "--remove-orphans", "--timeout", "5"], {
|
|
443
|
+
const proc = spawn("docker", ["compose", "-p", imageName, "down", "--rmi", "local", "-v", "--remove-orphans", "--timeout", "5"], {
|
|
389
444
|
cwd: dockerDir,
|
|
390
445
|
stdio: "inherit",
|
|
391
446
|
});
|
|
@@ -398,9 +453,10 @@ async function cleanImage(imageName, ralphDir) {
|
|
|
398
453
|
});
|
|
399
454
|
});
|
|
400
455
|
}
|
|
401
|
-
// Find and forcibly remove any containers using volumes with our
|
|
456
|
+
// Find and forcibly remove any containers using volumes with our project name pattern
|
|
402
457
|
// This handles orphaned containers from previous runs or pods
|
|
403
|
-
|
|
458
|
+
// Project name is now imageName (via -p flag), so volumes are named ${imageName}_*
|
|
459
|
+
const volumePattern = imageName;
|
|
404
460
|
await new Promise((resolve) => {
|
|
405
461
|
// List all containers (including stopped) and filter by volume name pattern
|
|
406
462
|
const proc = spawn("docker", ["ps", "-aq", "--filter", `volume=${volumePattern}`], {
|
|
@@ -510,8 +566,8 @@ async function cleanImage(imageName, ralphDir) {
|
|
|
510
566
|
resolve();
|
|
511
567
|
});
|
|
512
568
|
});
|
|
513
|
-
// Clean up project-specific network (
|
|
514
|
-
const networkName =
|
|
569
|
+
// Clean up project-specific network (project name is imageName via -p flag)
|
|
570
|
+
const networkName = `${imageName}_default`;
|
|
515
571
|
await new Promise((resolve) => {
|
|
516
572
|
const proc = spawn("docker", ["network", "rm", networkName], {
|
|
517
573
|
stdio: ["ignore", "ignore", "ignore"], // Suppress output - network may not exist
|
package/dist/commands/init.js
CHANGED
|
@@ -45,10 +45,13 @@ export async function init(_args) {
|
|
|
45
45
|
const customArgs = customArgsInput.trim() ? customArgsInput.trim().split(/\s+/) : [];
|
|
46
46
|
const customYoloArgsInput = await promptInput("Enter yolo/auto-approve arguments (space-separated): ");
|
|
47
47
|
const customYoloArgs = customYoloArgsInput.trim() ? customYoloArgsInput.trim().split(/\s+/) : [];
|
|
48
|
+
const customPromptArgsInput = await promptInput("Enter prompt arguments (e.g., -p for flag-based, leave empty for positional): ");
|
|
49
|
+
const customPromptArgs = customPromptArgsInput.trim() ? customPromptArgsInput.trim().split(/\s+/) : [];
|
|
48
50
|
cliConfig = {
|
|
49
51
|
command: customCommand || "claude",
|
|
50
52
|
args: customArgs,
|
|
51
53
|
yoloArgs: customYoloArgs.length > 0 ? customYoloArgs : undefined,
|
|
54
|
+
promptArgs: customPromptArgs,
|
|
52
55
|
};
|
|
53
56
|
}
|
|
54
57
|
else {
|
|
@@ -56,6 +59,7 @@ export async function init(_args) {
|
|
|
56
59
|
command: selectedProvider.command,
|
|
57
60
|
args: selectedProvider.defaultArgs,
|
|
58
61
|
yoloArgs: selectedProvider.yoloArgs.length > 0 ? selectedProvider.yoloArgs : undefined,
|
|
62
|
+
promptArgs: selectedProvider.promptArgs ?? [],
|
|
59
63
|
};
|
|
60
64
|
}
|
|
61
65
|
console.log(`\nSelected CLI provider: ${CLI_PROVIDERS[selectedCliProviderKey].name}`);
|
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();
|
|
@@ -18,16 +19,20 @@ export async function once(_args) {
|
|
|
18
19
|
// Build CLI arguments: config args + yolo args + prompt args
|
|
19
20
|
// Use yoloArgs from config if available, otherwise default to Claude's --dangerously-skip-permissions
|
|
20
21
|
const yoloArgs = cliConfig.yoloArgs ?? ["--dangerously-skip-permissions"];
|
|
22
|
+
const promptArgs = cliConfig.promptArgs ?? ["-p"];
|
|
23
|
+
const promptValue = `@${paths.prd} @${paths.progress} ${prompt}`;
|
|
21
24
|
const cliArgs = [
|
|
22
25
|
...(cliConfig.args ?? []),
|
|
23
26
|
...yoloArgs,
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
...promptArgs,
|
|
28
|
+
promptValue,
|
|
26
29
|
];
|
|
30
|
+
if (debug) {
|
|
31
|
+
console.log(`[debug] ${cliConfig.command} ${cliArgs.map(a => a.includes(" ") ? `"${a}"` : a).join(" ")}\n`);
|
|
32
|
+
}
|
|
27
33
|
return new Promise((resolve, reject) => {
|
|
28
34
|
const proc = spawn(cliConfig.command, cliArgs, {
|
|
29
35
|
stdio: "inherit",
|
|
30
|
-
shell: true,
|
|
31
36
|
});
|
|
32
37
|
proc.on("close", (code) => {
|
|
33
38
|
if (code !== 0) {
|
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
|
|
@@ -39,7 +39,13 @@ async function runIteration(prompt, paths, sandboxed, filteredPrdPath, cliConfig
|
|
|
39
39
|
cliArgs.push(...yoloArgs);
|
|
40
40
|
}
|
|
41
41
|
// Use the filtered PRD (only incomplete items) for the prompt
|
|
42
|
-
|
|
42
|
+
// promptArgs specifies flags to use (e.g., ["-p"] for Claude, [] for positional)
|
|
43
|
+
const promptArgs = cliConfig.promptArgs ?? ["-p"];
|
|
44
|
+
const promptValue = `@${filteredPrdPath} @${paths.progress} ${prompt}`;
|
|
45
|
+
cliArgs.push(...promptArgs, promptValue);
|
|
46
|
+
if (debug) {
|
|
47
|
+
console.log(`[debug] ${cliConfig.command} ${cliArgs.map(a => a.includes(" ") ? `"${a}"` : a).join(" ")}\n`);
|
|
48
|
+
}
|
|
43
49
|
const proc = spawn(cliConfig.command, cliArgs, {
|
|
44
50
|
stdio: ["inherit", "pipe", "inherit"],
|
|
45
51
|
});
|
|
@@ -104,6 +110,7 @@ export async function run(args) {
|
|
|
104
110
|
let category;
|
|
105
111
|
let loopMode = false;
|
|
106
112
|
let allModeExplicit = false;
|
|
113
|
+
let debug = false;
|
|
107
114
|
const filteredArgs = [];
|
|
108
115
|
for (let i = 0; i < args.length; i++) {
|
|
109
116
|
if (args[i] === "--category" || args[i] === "-c") {
|
|
@@ -123,6 +130,9 @@ export async function run(args) {
|
|
|
123
130
|
else if (args[i] === "--all" || args[i] === "-a") {
|
|
124
131
|
allModeExplicit = true;
|
|
125
132
|
}
|
|
133
|
+
else if (args[i] === "--debug" || args[i] === "-d") {
|
|
134
|
+
debug = true;
|
|
135
|
+
}
|
|
126
136
|
else {
|
|
127
137
|
filteredArgs.push(args[i]);
|
|
128
138
|
}
|
|
@@ -173,7 +183,10 @@ export async function run(args) {
|
|
|
173
183
|
// Track temp file for cleanup
|
|
174
184
|
let filteredPrdPath = null;
|
|
175
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
|
|
176
187
|
const startTime = Date.now();
|
|
188
|
+
let consecutiveFailures = 0;
|
|
189
|
+
let lastExitCode = 0;
|
|
177
190
|
try {
|
|
178
191
|
for (let i = 1; i <= iterations; i++) {
|
|
179
192
|
console.log(`\n${"=".repeat(50)}`);
|
|
@@ -246,7 +259,7 @@ export async function run(args) {
|
|
|
246
259
|
break;
|
|
247
260
|
}
|
|
248
261
|
}
|
|
249
|
-
const { exitCode, output } = await runIteration(prompt, paths, sandboxed, filteredPrdPath, cliConfig);
|
|
262
|
+
const { exitCode, output } = await runIteration(prompt, paths, sandboxed, filteredPrdPath, cliConfig, debug);
|
|
250
263
|
// Clean up temp file after each iteration
|
|
251
264
|
try {
|
|
252
265
|
unlinkSync(filteredPrdPath);
|
|
@@ -257,8 +270,27 @@ export async function run(args) {
|
|
|
257
270
|
filteredPrdPath = null;
|
|
258
271
|
if (exitCode !== 0) {
|
|
259
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
|
+
}
|
|
260
287
|
console.log("Continuing to next iteration...");
|
|
261
288
|
}
|
|
289
|
+
else {
|
|
290
|
+
// Reset failure tracking on success
|
|
291
|
+
consecutiveFailures = 0;
|
|
292
|
+
lastExitCode = 0;
|
|
293
|
+
}
|
|
262
294
|
// Check for completion signal
|
|
263
295
|
if (output.includes("<promise>COMPLETE</promise>")) {
|
|
264
296
|
if (loopMode) {
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
"command": "claude",
|
|
7
7
|
"defaultArgs": ["--permission-mode", "acceptEdits"],
|
|
8
8
|
"yoloArgs": ["--dangerously-skip-permissions"],
|
|
9
|
+
"promptArgs": ["-p"],
|
|
9
10
|
"docker": {
|
|
10
|
-
"install": "# Install Claude Code CLI\nRUN curl -fsSL https://claude.ai/install.sh | bash"
|
|
11
|
+
"install": "# Install Claude Code CLI (as node user so it installs to /home/node/.local/bin)\nRUN su - node -c 'curl -fsSL https://claude.ai/install.sh | bash' \\\n && echo 'export PATH=\"$HOME/.local/bin:$PATH\"' >> /home/node/.zshrc"
|
|
11
12
|
},
|
|
12
13
|
"envVars": ["ANTHROPIC_API_KEY"],
|
|
13
14
|
"credentialMount": "~/.claude:/home/node/.claude"
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
"command": "aider",
|
|
19
20
|
"defaultArgs": ["--yes"],
|
|
20
21
|
"yoloArgs": ["--yes-always"],
|
|
22
|
+
"promptArgs": ["--message"],
|
|
21
23
|
"docker": {
|
|
22
24
|
"install": "# Install Aider (requires Python)\nRUN apt-get update && apt-get install -y python3 python3-pip && rm -rf /var/lib/apt/lists/* \\\n && pip3 install --break-system-packages --no-cache-dir aider-chat",
|
|
23
25
|
"note": "Check 'aider --help' for available flags"
|
|
@@ -31,6 +33,7 @@
|
|
|
31
33
|
"command": "codex",
|
|
32
34
|
"defaultArgs": ["--approval-mode", "suggest"],
|
|
33
35
|
"yoloArgs": ["--approval-mode", "full-auto"],
|
|
36
|
+
"promptArgs": [],
|
|
34
37
|
"docker": {
|
|
35
38
|
"install": "# Install OpenAI Codex CLI\nRUN npm install -g @openai/codex",
|
|
36
39
|
"note": "Check 'codex --help' for available flags"
|
|
@@ -44,11 +47,12 @@
|
|
|
44
47
|
"command": "gemini",
|
|
45
48
|
"defaultArgs": [],
|
|
46
49
|
"yoloArgs": ["-y"],
|
|
50
|
+
"promptArgs": [],
|
|
47
51
|
"docker": {
|
|
48
52
|
"install": "# Install Google Gemini CLI\nRUN npm install -g @google/gemini-cli",
|
|
49
53
|
"note": "Check 'gemini --help' for available flags"
|
|
50
54
|
},
|
|
51
|
-
"envVars": ["
|
|
55
|
+
"envVars": ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
|
|
52
56
|
"credentialMount": "~/.gemini:/home/node/.gemini"
|
|
53
57
|
},
|
|
54
58
|
"opencode": {
|
|
@@ -57,11 +61,12 @@
|
|
|
57
61
|
"command": "opencode",
|
|
58
62
|
"defaultArgs": [],
|
|
59
63
|
"yoloArgs": ["--yolo"],
|
|
64
|
+
"promptArgs": ["run"],
|
|
60
65
|
"docker": {
|
|
61
|
-
"install": "# Install OpenCode\nRUN curl -fsSL https://opencode.ai/install | bash \\\n && echo 'export PATH=\"$HOME/.opencode/bin:$PATH\"' >> /home/node/.zshrc",
|
|
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",
|
|
62
67
|
"note": "Check 'opencode --help' for available flags"
|
|
63
68
|
},
|
|
64
|
-
"envVars": ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "
|
|
69
|
+
"envVars": ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_GENERATIVE_AI_API_KEY"],
|
|
65
70
|
"credentialMount": null
|
|
66
71
|
},
|
|
67
72
|
"amp": {
|
|
@@ -70,8 +75,9 @@
|
|
|
70
75
|
"command": "amp",
|
|
71
76
|
"defaultArgs": [],
|
|
72
77
|
"yoloArgs": ["--yolo"],
|
|
78
|
+
"promptArgs": [],
|
|
73
79
|
"docker": {
|
|
74
|
-
"install": "# Install AMP CLI\nRUN curl -fsSL https://ampcode.com/install.sh | bash \\\n && echo 'export PATH=\"$HOME/.amp/bin:$PATH\"' >> /home/node/.zshrc",
|
|
80
|
+
"install": "# Install AMP CLI (as node user)\nRUN su - node -c 'curl -fsSL https://ampcode.com/install.sh | bash' \\\n && echo 'export PATH=\"$HOME/.amp/bin:$PATH\"' >> /home/node/.zshrc",
|
|
75
81
|
"note": "Check 'amp --help' for available flags"
|
|
76
82
|
},
|
|
77
83
|
"envVars": ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
|
|
@@ -83,6 +89,7 @@
|
|
|
83
89
|
"command": "",
|
|
84
90
|
"defaultArgs": [],
|
|
85
91
|
"yoloArgs": [],
|
|
92
|
+
"promptArgs": [],
|
|
86
93
|
"docker": {
|
|
87
94
|
"install": "# Custom CLI - add your installation commands here"
|
|
88
95
|
},
|
package/dist/utils/config.d.ts
CHANGED
package/dist/utils/config.js
CHANGED
|
@@ -3,9 +3,30 @@ import { join } from "path";
|
|
|
3
3
|
export const DEFAULT_CLI_CONFIG = {
|
|
4
4
|
command: "claude",
|
|
5
5
|
args: ["--permission-mode", "acceptEdits"],
|
|
6
|
+
promptArgs: ["-p"],
|
|
6
7
|
};
|
|
8
|
+
// Lazy import to avoid circular dependency
|
|
9
|
+
let _getCliProviders = null;
|
|
7
10
|
export function getCliConfig(config) {
|
|
8
|
-
|
|
11
|
+
const cliConfig = config.cli ?? DEFAULT_CLI_CONFIG;
|
|
12
|
+
// If promptArgs is already set, use it
|
|
13
|
+
if (cliConfig.promptArgs !== undefined) {
|
|
14
|
+
return cliConfig;
|
|
15
|
+
}
|
|
16
|
+
// Look up promptArgs from cliProvider if available
|
|
17
|
+
if (config.cliProvider) {
|
|
18
|
+
if (!_getCliProviders) {
|
|
19
|
+
// Dynamic import to avoid circular dependency
|
|
20
|
+
_getCliProviders = require("../templates/prompts.js").getCliProviders;
|
|
21
|
+
}
|
|
22
|
+
const providers = _getCliProviders();
|
|
23
|
+
const provider = providers[config.cliProvider];
|
|
24
|
+
if (provider?.promptArgs !== undefined) {
|
|
25
|
+
return { ...cliConfig, promptArgs: provider.promptArgs };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Default to -p for backwards compatibility
|
|
29
|
+
return { ...cliConfig, promptArgs: ["-p"] };
|
|
9
30
|
}
|
|
10
31
|
const RALPH_DIR = ".ralph";
|
|
11
32
|
const CONFIG_FILE = "config.json";
|
package/dist/utils/prompt.js
CHANGED
|
@@ -68,6 +68,7 @@ export async function promptSelectWithArrows(message, options) {
|
|
|
68
68
|
if (process.stdin.isTTY) {
|
|
69
69
|
process.stdin.setRawMode(false);
|
|
70
70
|
}
|
|
71
|
+
process.stdin.pause();
|
|
71
72
|
process.stdout.write("\x1B[?25h"); // Show cursor
|
|
72
73
|
};
|
|
73
74
|
process.stdin.on("data", onKeypress);
|
|
@@ -230,6 +231,7 @@ export async function promptMultiSelectWithArrows(message, options) {
|
|
|
230
231
|
if (process.stdin.isTTY) {
|
|
231
232
|
process.stdin.setRawMode(false);
|
|
232
233
|
}
|
|
234
|
+
process.stdin.pause();
|
|
233
235
|
process.stdout.write("\x1B[?25h"); // Show cursor
|
|
234
236
|
};
|
|
235
237
|
process.stdin.on("data", onKeypress);
|
|
@@ -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": [
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"copy-config": "mkdir -p dist/config && cp src/config/*.json dist/config/",
|
|
18
18
|
"dev": "npx tsx src/index.ts",
|
|
19
19
|
"typecheck": "tsc --noEmit",
|
|
20
|
-
"prepublishOnly": "npm run build"
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"prepare": "npm run build"
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
24
|
"readline": "^1.3.0"
|