ralph-cli-sandboxed 0.2.0 → 0.2.1

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.
@@ -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
- const proc = spawn("docker", ["compose", "build", "--no-cache", "--pull"], {
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
- const proc = spawn("docker", ["compose", "run", "--rm", "ralph"], {
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 image name pattern
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
- const volumePattern = `docker_${imageName}`;
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 (named after imageName, not generic docker_default)
514
- const networkName = `docker_${imageName}_default`;
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
@@ -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}`);
@@ -18,16 +18,17 @@ export async function once(_args) {
18
18
  // Build CLI arguments: config args + yolo args + prompt args
19
19
  // Use yoloArgs from config if available, otherwise default to Claude's --dangerously-skip-permissions
20
20
  const yoloArgs = cliConfig.yoloArgs ?? ["--dangerously-skip-permissions"];
21
+ const promptArgs = cliConfig.promptArgs ?? ["-p"];
22
+ const promptValue = `@${paths.prd} @${paths.progress} ${prompt}`;
21
23
  const cliArgs = [
22
24
  ...(cliConfig.args ?? []),
23
25
  ...yoloArgs,
24
- "-p",
25
- `@${paths.prd} @${paths.progress} ${prompt}`,
26
+ ...promptArgs,
27
+ promptValue,
26
28
  ];
27
29
  return new Promise((resolve, reject) => {
28
30
  const proc = spawn(cliConfig.command, cliArgs, {
29
31
  stdio: "inherit",
30
- shell: true,
31
32
  });
32
33
  proc.on("close", (code) => {
33
34
  if (code !== 0) {
@@ -39,7 +39,10 @@ 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
- cliArgs.push("-p", `@${filteredPrdPath} @${paths.progress} ${prompt}`);
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);
43
46
  const proc = spawn(cliConfig.command, cliArgs, {
44
47
  stdio: ["inherit", "pipe", "inherit"],
45
48
  });
@@ -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": ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
55
+ "envVars": ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
52
56
  "credentialMount": "~/.gemini:/home/node/.gemini"
53
57
  },
54
58
  "opencode": {
@@ -57,8 +61,9 @@
57
61
  "command": "opencode",
58
62
  "defaultArgs": [],
59
63
  "yoloArgs": ["--yolo"],
64
+ "promptArgs": [],
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
69
  "envVars": ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_API_KEY"],
@@ -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
  },
@@ -33,6 +33,7 @@ export interface CliProviderConfig {
33
33
  command: string;
34
34
  defaultArgs: string[];
35
35
  yoloArgs: string[];
36
+ promptArgs: string[];
36
37
  docker: {
37
38
  install: string;
38
39
  };
@@ -2,6 +2,7 @@ export interface CliConfig {
2
2
  command: string;
3
3
  args?: string[];
4
4
  yoloArgs?: string[];
5
+ promptArgs?: string[];
5
6
  }
6
7
  export interface RalphConfig {
7
8
  language: string;
@@ -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
- return config.cli ?? DEFAULT_CLI_CONFIG;
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";
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-cli-sandboxed",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "AI-driven development automation CLI for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,11 +13,12 @@
13
13
  "README.md"
14
14
  ],
15
15
  "scripts": {
16
- "build": "tsc && npm run copy-config",
16
+ "build": "./node_modules/.bin/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": "tsc --noEmit",
20
- "prepublishOnly": "npm run build"
19
+ "typecheck": "./node_modules/.bin/tsc --noEmit",
20
+ "prepublishOnly": "npm run build",
21
+ "prepare": "npm run build"
21
22
  },
22
23
  "dependencies": {
23
24
  "readline": "^1.3.0"