ralph-cli-claude 0.1.0

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 ADDED
@@ -0,0 +1,149 @@
1
+ # ralph-cli-claude
2
+
3
+ AI-driven development automation CLI for [Claude Code](https://github.com/anthropics/claude-code).
4
+
5
+ Ralph automates iterative development by having Claude work through a PRD (Product Requirements Document), implementing features one at a time, running tests, and committing changes.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ # Use directly with npx
11
+ npx ralph-cli-claude init
12
+
13
+ # Or install globally
14
+ npm install -g ralph-cli-claude
15
+ ralph init
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ```bash
21
+ # 1. Initialize ralph in your project
22
+ ralph init
23
+
24
+ # 2. Add requirements to your PRD
25
+ ralph prd add
26
+
27
+ # 3. Run a single iteration
28
+ ralph once
29
+
30
+ # 4. Or run multiple iterations
31
+ ralph run 5
32
+ ```
33
+
34
+ ## Commands
35
+
36
+ | Command | Description |
37
+ |---------|-------------|
38
+ | `ralph init` | Initialize ralph in current project |
39
+ | `ralph once` | Run a single automation iteration |
40
+ | `ralph run <n>` | Run n automation iterations |
41
+ | `ralph prd add` | Add a new PRD entry (interactive) |
42
+ | `ralph prd list` | List all PRD entries |
43
+ | `ralph prd status` | Show PRD completion status |
44
+ | `ralph prd toggle <n>` | Toggle passes status for entry n |
45
+ | `ralph scripts` | Generate shell scripts for sandboxed environments |
46
+ | `ralph docker` | Generate Docker sandbox environment |
47
+ | `ralph help` | Show help message |
48
+
49
+ ## Configuration
50
+
51
+ After running `ralph init`, you'll have:
52
+
53
+ ```
54
+ .ralph/
55
+ ├── config.json # Project configuration
56
+ ├── prompt.md # Shared prompt template
57
+ ├── prd.json # Product requirements document
58
+ └── progress.txt # Progress tracking file
59
+ ```
60
+
61
+ ### Supported Languages
62
+
63
+ - **Bun** (TypeScript) - `bun check`, `bun test`
64
+ - **Node.js** (TypeScript) - `npm run typecheck`, `npm test`
65
+ - **Python** - `mypy .`, `pytest`
66
+ - **Go** - `go build ./...`, `go test ./...`
67
+ - **Rust** - `cargo check`, `cargo test`
68
+ - **Custom** - Define your own commands
69
+
70
+ ## PRD Format
71
+
72
+ The PRD (`prd.json`) is an array of requirements:
73
+
74
+ ```json
75
+ [
76
+ {
77
+ "category": "feature",
78
+ "description": "Add user authentication",
79
+ "steps": [
80
+ "Create login form",
81
+ "Implement JWT tokens",
82
+ "Add protected routes"
83
+ ],
84
+ "passes": false
85
+ }
86
+ ]
87
+ ```
88
+
89
+ Categories: `ui`, `feature`, `bugfix`, `setup`, `development`, `testing`, `docs`
90
+
91
+ ## Docker Sandbox
92
+
93
+ Run ralph in an isolated Docker container:
94
+
95
+ ```bash
96
+ # Generate Docker files
97
+ ralph docker
98
+
99
+ # Build the image
100
+ ralph docker --build
101
+
102
+ # Run container
103
+ ralph docker --run
104
+ ```
105
+
106
+ Features:
107
+ - Based on [Claude Code devcontainer](https://github.com/anthropics/claude-code/tree/main/.devcontainer)
108
+ - Network sandboxing (firewall allows only GitHub, npm, Anthropic API)
109
+ - Your `~/.claude` credentials mounted automatically (Pro/Max OAuth)
110
+ - Language-specific tooling pre-installed
111
+
112
+ ### Installing packages in container
113
+
114
+ ```bash
115
+ # Run as root to install packages
116
+ docker compose run -u root ralph apt-get update
117
+ docker compose run -u root ralph apt-get install <package>
118
+ ```
119
+
120
+ ## Shell Scripts
121
+
122
+ For environments where the CLI isn't available:
123
+
124
+ ```bash
125
+ ralph scripts
126
+ ```
127
+
128
+ Generates `ralph.sh` and `ralph-once.sh` in your project root.
129
+
130
+ ## How It Works
131
+
132
+ 1. **Read PRD**: Claude reads your requirements from `prd.json`
133
+ 2. **Implement**: Works on the highest priority incomplete feature
134
+ 3. **Verify**: Runs your check and test commands
135
+ 4. **Update**: Marks the feature as complete in the PRD
136
+ 5. **Commit**: Creates a git commit for the feature
137
+ 6. **Repeat**: Continues to the next feature (in `run` mode)
138
+
139
+ When all PRD items pass, Claude outputs `<promise>COMPLETE</promise>` and stops.
140
+
141
+ ## Requirements
142
+
143
+ - Node.js 18+
144
+ - [Claude Code CLI](https://github.com/anthropics/claude-code) installed
145
+ - Claude Pro/Max subscription or Anthropic API key
146
+
147
+ ## License
148
+
149
+ MIT
@@ -0,0 +1 @@
1
+ export declare function docker(args: string[]): Promise<void>;
@@ -0,0 +1,406 @@
1
+ import { existsSync, writeFileSync, mkdirSync, chmodSync } from "fs";
2
+ import { join, basename } from "path";
3
+ import { spawn } from "child_process";
4
+ import { loadConfig, getRalphDir } from "../utils/config.js";
5
+ import { promptConfirm } from "../utils/prompt.js";
6
+ const DOCKER_DIR = "docker";
7
+ // Language-specific Dockerfile snippets
8
+ const LANGUAGE_SNIPPETS = {
9
+ bun: `
10
+ # Install Bun
11
+ RUN curl -fsSL https://bun.sh/install | bash
12
+ ENV PATH="/home/node/.bun/bin:$PATH"
13
+ `,
14
+ node: `
15
+ # Node.js already included in base image
16
+ `,
17
+ python: `
18
+ # Install Python and tools
19
+ RUN apt-get update && apt-get install -y \\
20
+ python3 \\
21
+ python3-pip \\
22
+ python3-venv \\
23
+ && rm -rf /var/lib/apt/lists/*
24
+ RUN pip3 install --break-system-packages mypy pytest
25
+ `,
26
+ go: `
27
+ # Install Go
28
+ RUN curl -fsSL https://go.dev/dl/go1.22.0.linux-amd64.tar.gz | tar -C /usr/local -xzf -
29
+ ENV PATH="/usr/local/go/bin:/home/node/go/bin:$PATH"
30
+ ENV GOPATH="/home/node/go"
31
+ `,
32
+ rust: `
33
+ # Install Rust
34
+ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
35
+ ENV PATH="/home/node/.cargo/bin:$PATH"
36
+ `,
37
+ none: `
38
+ # Custom language - add your dependencies here
39
+ `,
40
+ };
41
+ function generateDockerfile(language) {
42
+ const languageSnippet = LANGUAGE_SNIPPETS[language] || LANGUAGE_SNIPPETS.none;
43
+ return `# Ralph CLI Sandbox Environment
44
+ # Based on Claude Code devcontainer
45
+ # Generated by ralph-cli
46
+
47
+ FROM node:20-bookworm
48
+
49
+ ARG DEBIAN_FRONTEND=noninteractive
50
+ ARG TZ=UTC
51
+ ARG CLAUDE_CODE_VERSION="latest"
52
+ ARG ZSH_IN_DOCKER_VERSION="1.2.1"
53
+
54
+ # Set timezone
55
+ ENV TZ=\${TZ}
56
+ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
57
+
58
+ # Install system dependencies
59
+ RUN apt-get update && apt-get install -y \\
60
+ git \\
61
+ curl \\
62
+ wget \\
63
+ nano \\
64
+ vim \\
65
+ less \\
66
+ procps \\
67
+ sudo \\
68
+ man-db \\
69
+ unzip \\
70
+ gnupg2 \\
71
+ jq \\
72
+ fzf \\
73
+ iptables \\
74
+ ipset \\
75
+ iproute2 \\
76
+ dnsutils \\
77
+ zsh \\
78
+ && rm -rf /var/lib/apt/lists/*
79
+
80
+ # Setup zsh with oh-my-zsh and plugins (no theme, we set custom prompt)
81
+ RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v\${ZSH_IN_DOCKER_VERSION}/zsh-in-docker.sh)" -- \\
82
+ -t "" \\
83
+ -p git \\
84
+ -p fzf \\
85
+ -a "source /usr/share/doc/fzf/examples/key-bindings.zsh 2>/dev/null || true" \\
86
+ -a "source /usr/share/doc/fzf/examples/completion.zsh 2>/dev/null || true" \\
87
+ -a "export HISTFILE=/commandhistory/.zsh_history" \\
88
+ -a 'alias ll="ls -la"'
89
+
90
+ # Set custom prompt for node user (after oh-my-zsh to avoid override)
91
+ RUN cp -r /root/.oh-my-zsh /home/node/.oh-my-zsh && chown -R node:node /home/node/.oh-my-zsh && \\
92
+ cp /root/.zshrc /home/node/.zshrc && chown node:node /home/node/.zshrc && \\
93
+ sed -i 's|/root/.oh-my-zsh|/home/node/.oh-my-zsh|g' /home/node/.zshrc && \\
94
+ echo 'PROMPT="%K{yellow}%F{black}[ralph]%f%k%K{yellow}%F{black}%d%f%k\\$ "' >> /home/node/.zshrc
95
+
96
+ # Install Claude Code CLI
97
+ RUN npm install -g @anthropic-ai/claude-code@\${CLAUDE_CODE_VERSION}
98
+
99
+ # Install ralph-cli-claude
100
+ RUN npm install -g ralph-cli-claude || echo "ralph-cli-claude not yet published, will use local"
101
+ ${languageSnippet}
102
+ # Setup sudo only for firewall script (no general sudo for security)
103
+ RUN echo "node ALL=(ALL) NOPASSWD: /usr/local/bin/init-firewall.sh" >> /etc/sudoers.d/node-firewall
104
+
105
+ # Create directories
106
+ RUN mkdir -p /workspace && chown node:node /workspace
107
+ RUN mkdir -p /home/node/.claude && chown node:node /home/node/.claude
108
+ RUN mkdir -p /commandhistory && chown node:node /commandhistory
109
+
110
+ # Copy firewall script
111
+ COPY init-firewall.sh /usr/local/bin/init-firewall.sh
112
+ RUN chmod +x /usr/local/bin/init-firewall.sh
113
+
114
+ # Set environment variables
115
+ ENV DEVCONTAINER=true
116
+ ENV NODE_OPTIONS="--max-old-space-size=4096"
117
+ ENV CLAUDE_CONFIG_DIR="/home/node/.claude"
118
+ ENV SHELL=/bin/zsh
119
+ ENV EDITOR=nano
120
+
121
+ # Add bash aliases and prompt (fallback if using bash)
122
+ RUN echo 'alias ll="ls -la"' >> /etc/bash.bashrc && \\
123
+ echo 'PS1="\\[\\033[43;30m\\][ralph]\\w\\[\\033[0m\\]\\$ "' >> /etc/bash.bashrc
124
+
125
+ # Switch to non-root user
126
+ USER node
127
+ WORKDIR /workspace
128
+
129
+ # Default to zsh
130
+ CMD ["zsh"]
131
+ `;
132
+ }
133
+ const FIREWALL_SCRIPT = `#!/bin/bash
134
+ # Firewall initialization script for Ralph sandbox
135
+ # Based on Claude Code devcontainer firewall
136
+
137
+ set -e
138
+
139
+ echo "Initializing sandbox firewall..."
140
+
141
+ # Get Docker DNS before flushing
142
+ DOCKER_DNS=$(cat /etc/resolv.conf | grep nameserver | head -1 | awk '{print $2}')
143
+
144
+ # Flush existing rules
145
+ iptables -F
146
+ iptables -X
147
+ iptables -t nat -F
148
+ iptables -t nat -X
149
+ iptables -t mangle -F
150
+ iptables -t mangle -X
151
+
152
+ # Create ipset for allowed IPs
153
+ ipset destroy allowed_ips 2>/dev/null || true
154
+ ipset create allowed_ips hash:net
155
+
156
+ # Allow localhost
157
+ iptables -A OUTPUT -o lo -j ACCEPT
158
+ iptables -A INPUT -i lo -j ACCEPT
159
+
160
+ # Allow established connections
161
+ iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
162
+ iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
163
+
164
+ # Allow DNS
165
+ iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
166
+ iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
167
+ if [ -n "$DOCKER_DNS" ]; then
168
+ iptables -A OUTPUT -d $DOCKER_DNS -j ACCEPT
169
+ fi
170
+
171
+ # Allow SSH (for git)
172
+ iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT
173
+
174
+ # Add allowed domains to ipset
175
+ # GitHub
176
+ for ip in $(dig +short github.com api.github.com raw.githubusercontent.com); do
177
+ ipset add allowed_ips $ip 2>/dev/null || true
178
+ done
179
+
180
+ # npm registry
181
+ for ip in $(dig +short registry.npmjs.org); do
182
+ ipset add allowed_ips $ip 2>/dev/null || true
183
+ done
184
+
185
+ # Anthropic API
186
+ for ip in $(dig +short api.anthropic.com); do
187
+ ipset add allowed_ips $ip 2>/dev/null || true
188
+ done
189
+
190
+ # Allow host network (for mounted volumes, etc.)
191
+ HOST_NETWORK=$(ip route | grep default | awk '{print $3}' | head -1)
192
+ if [ -n "$HOST_NETWORK" ]; then
193
+ HOST_SUBNET=$(echo $HOST_NETWORK | sed 's/\\.[0-9]*$/.0\\/24/')
194
+ ipset add allowed_ips $HOST_SUBNET 2>/dev/null || true
195
+ fi
196
+
197
+ # Allow traffic to allowed IPs
198
+ iptables -A OUTPUT -m set --match-set allowed_ips dst -j ACCEPT
199
+
200
+ # Set default policies to DROP
201
+ iptables -P INPUT DROP
202
+ iptables -P FORWARD DROP
203
+ iptables -P OUTPUT DROP
204
+
205
+ # Allow HTTPS to allowed IPs
206
+ iptables -I OUTPUT -p tcp --dport 443 -m set --match-set allowed_ips dst -j ACCEPT
207
+ iptables -I OUTPUT -p tcp --dport 80 -m set --match-set allowed_ips dst -j ACCEPT
208
+
209
+ echo "Firewall initialized. Only allowed destinations are accessible."
210
+ echo "Allowed: GitHub, npm, Anthropic API, local network"
211
+ `;
212
+ function generateDockerCompose(imageName) {
213
+ return `# Ralph CLI Docker Compose
214
+ # Generated by ralph-cli
215
+
216
+ services:
217
+ ralph:
218
+ image: ${imageName}
219
+ build:
220
+ context: .
221
+ dockerfile: Dockerfile
222
+ volumes:
223
+ # Mount project root (two levels up from .ralph/docker/)
224
+ - ../..:/workspace
225
+ # Mount host's ~/.claude for Pro/Max OAuth credentials
226
+ - \${HOME}/.claude:/home/node/.claude
227
+ - ${imageName}-history:/commandhistory
228
+ # Uncomment to use API key instead of OAuth:
229
+ # environment:
230
+ # - ANTHROPIC_API_KEY=\${ANTHROPIC_API_KEY}
231
+ working_dir: /workspace
232
+ stdin_open: true
233
+ tty: true
234
+ cap_add:
235
+ - NET_ADMIN # Required for firewall
236
+ # Uncomment to enable firewall sandboxing:
237
+ # command: bash -c "sudo /usr/local/bin/init-firewall.sh && zsh"
238
+
239
+ volumes:
240
+ ${imageName}-history:
241
+ `;
242
+ }
243
+ const DOCKERIGNORE = `# Docker ignore file
244
+ node_modules
245
+ dist
246
+ .git
247
+ *.log
248
+ `;
249
+ async function generateFiles(ralphDir, language, imageName) {
250
+ const dockerDir = join(ralphDir, DOCKER_DIR);
251
+ // Create docker directory
252
+ if (!existsSync(dockerDir)) {
253
+ mkdirSync(dockerDir, { recursive: true });
254
+ console.log(`Created ${DOCKER_DIR}/`);
255
+ }
256
+ const files = [
257
+ { name: "Dockerfile", content: generateDockerfile(language) },
258
+ { name: "init-firewall.sh", content: FIREWALL_SCRIPT },
259
+ { name: "docker-compose.yml", content: generateDockerCompose(imageName) },
260
+ { name: ".dockerignore", content: DOCKERIGNORE },
261
+ ];
262
+ for (const file of files) {
263
+ const filePath = join(dockerDir, file.name);
264
+ if (existsSync(filePath)) {
265
+ const overwrite = await promptConfirm(`${DOCKER_DIR}/${file.name} already exists. Overwrite?`);
266
+ if (!overwrite) {
267
+ console.log(`Skipped ${file.name}`);
268
+ continue;
269
+ }
270
+ }
271
+ writeFileSync(filePath, file.content);
272
+ if (file.name.endsWith(".sh")) {
273
+ chmodSync(filePath, 0o755);
274
+ }
275
+ console.log(`Created ${DOCKER_DIR}/${file.name}`);
276
+ }
277
+ }
278
+ async function buildImage(ralphDir) {
279
+ const dockerDir = join(ralphDir, DOCKER_DIR);
280
+ if (!existsSync(join(dockerDir, "Dockerfile"))) {
281
+ console.error("Dockerfile not found. Run 'ralph docker' first.");
282
+ process.exit(1);
283
+ }
284
+ console.log("Building Docker image...\n");
285
+ return new Promise((resolve, reject) => {
286
+ const proc = spawn("docker", ["compose", "build"], {
287
+ cwd: dockerDir,
288
+ stdio: "inherit",
289
+ });
290
+ proc.on("close", (code) => {
291
+ if (code === 0) {
292
+ console.log("\nDocker image built successfully!");
293
+ resolve();
294
+ }
295
+ else {
296
+ reject(new Error(`Docker build failed with code ${code}`));
297
+ }
298
+ });
299
+ proc.on("error", (err) => {
300
+ reject(new Error(`Failed to run docker: ${err.message}`));
301
+ });
302
+ });
303
+ }
304
+ async function runContainer(ralphDir) {
305
+ const dockerDir = join(ralphDir, DOCKER_DIR);
306
+ if (!existsSync(join(dockerDir, "Dockerfile"))) {
307
+ console.error("Dockerfile not found. Run 'ralph docker' first.");
308
+ process.exit(1);
309
+ }
310
+ console.log("Starting Docker container...\n");
311
+ return new Promise((resolve, reject) => {
312
+ const proc = spawn("docker", ["compose", "run", "--rm", "ralph"], {
313
+ cwd: dockerDir,
314
+ stdio: "inherit",
315
+ });
316
+ proc.on("close", (code) => {
317
+ if (code === 0) {
318
+ resolve();
319
+ }
320
+ else {
321
+ reject(new Error(`Docker run failed with code ${code}`));
322
+ }
323
+ });
324
+ proc.on("error", (err) => {
325
+ reject(new Error(`Failed to run docker: ${err.message}`));
326
+ });
327
+ });
328
+ }
329
+ export async function docker(args) {
330
+ const flag = args[0];
331
+ // Show help without requiring init
332
+ if (flag === "--help" || flag === "-h") {
333
+ console.log(`
334
+ ralph docker - Generate and manage Docker sandbox environment
335
+
336
+ USAGE:
337
+ ralph docker Generate Dockerfile and scripts
338
+ ralph docker --build Build the Docker image
339
+ ralph docker --run Run container with project mounted
340
+
341
+ FILES GENERATED:
342
+ .ralph/docker/
343
+ ├── Dockerfile Based on Claude Code devcontainer
344
+ ├── init-firewall.sh Sandbox firewall script
345
+ ├── docker-compose.yml Container orchestration
346
+ └── .dockerignore Build exclusions
347
+
348
+ AUTHENTICATION:
349
+ Pro/Max users: Your ~/.claude credentials are mounted automatically.
350
+ API key users: Uncomment ANTHROPIC_API_KEY in docker-compose.yml.
351
+
352
+ EXAMPLES:
353
+ ralph docker # Generate files
354
+ ralph docker --build # Build image
355
+ ralph docker --run # Start interactive shell
356
+
357
+ # Or use docker compose directly:
358
+ cd .ralph/docker && docker compose run --rm ralph
359
+
360
+ # Run ralph automation in container:
361
+ docker compose run --rm ralph ralph once
362
+
363
+ INSTALLING PACKAGES (works with Docker & Podman):
364
+ # 1. Run as root to install packages:
365
+ docker compose run -u root ralph apt-get update
366
+ docker compose run -u root ralph apt-get install <package>
367
+
368
+ # 2. Or commit changes to a new image:
369
+ docker run -it --name temp -u root <image> bash
370
+ # inside: apt-get update && apt-get install <package>
371
+ # exit, then:
372
+ docker commit temp <image>:custom
373
+ docker rm temp
374
+ `);
375
+ return;
376
+ }
377
+ const ralphDir = getRalphDir();
378
+ if (!existsSync(ralphDir)) {
379
+ console.error("Error: .ralph/ directory not found. Run 'ralph init' first.");
380
+ process.exit(1);
381
+ }
382
+ const config = loadConfig();
383
+ // Get image name from config or generate default
384
+ const imageName = config.imageName || `ralph-${basename(process.cwd()).toLowerCase().replace(/[^a-z0-9-]/g, "-")}`;
385
+ if (flag === "--build") {
386
+ await buildImage(ralphDir);
387
+ }
388
+ else if (flag === "--run") {
389
+ await runContainer(ralphDir);
390
+ }
391
+ else {
392
+ console.log(`Generating Docker files for: ${config.language}`);
393
+ console.log(`Image name: ${imageName}\n`);
394
+ await generateFiles(ralphDir, config.language, imageName);
395
+ console.log(`
396
+ Docker files generated in .ralph/docker/
397
+
398
+ Next steps:
399
+ 1. Build the image: ralph docker --build
400
+ 2. Run container: ralph docker --run
401
+
402
+ Or use docker compose directly:
403
+ cd .ralph/docker && docker compose run --rm ralph
404
+ `);
405
+ }
406
+ }
@@ -0,0 +1 @@
1
+ export declare function help(_args: string[]): void;
@@ -0,0 +1,44 @@
1
+ const HELP_TEXT = `
2
+ ralph - AI-driven development automation CLI
3
+
4
+ USAGE:
5
+ ralph <command> [options]
6
+
7
+ COMMANDS:
8
+ init Initialize ralph in current project
9
+ once Run a single automation iteration
10
+ run <n> Run n automation iterations
11
+ prd <subcommand> Manage PRD entries
12
+ scripts Generate shell scripts (for sandboxed environments)
13
+ docker Generate Docker sandbox environment
14
+ help Show this help message
15
+
16
+ PRD SUBCOMMANDS:
17
+ prd add Add a new PRD entry (interactive)
18
+ prd list List all PRD entries
19
+ prd status Show PRD completion status
20
+ prd toggle <n> Toggle passes status for entry n
21
+
22
+ EXAMPLES:
23
+ ralph init # Initialize ralph for your project
24
+ ralph once # Run single iteration
25
+ ralph run 5 # Run 5 iterations
26
+ ralph prd add # Add new PRD entry
27
+ ralph prd list # Show all entries
28
+ ralph prd status # Show completion summary
29
+ ralph scripts # Generate ralph.sh and ralph-once.sh
30
+ ralph docker # Generate Dockerfile for sandboxed env
31
+ ralph docker --build # Build Docker image
32
+ ralph docker --run # Run container interactively
33
+
34
+ CONFIGURATION:
35
+ After running 'ralph init', you'll have:
36
+ .ralph/
37
+ ├── config.json Project configuration
38
+ ├── prompt.md Shared prompt template
39
+ ├── prd.json Product requirements document
40
+ └── progress.txt Progress tracking file
41
+ `;
42
+ export function help(_args) {
43
+ console.log(HELP_TEXT.trim());
44
+ }
@@ -0,0 +1 @@
1
+ export declare function init(_args: string[]): Promise<void>;
@@ -0,0 +1,98 @@
1
+ import { existsSync, writeFileSync, mkdirSync } from "fs";
2
+ import { join, basename } from "path";
3
+ import { LANGUAGES, generatePrompt, DEFAULT_PRD, DEFAULT_PROGRESS } from "../templates/prompts.js";
4
+ import { promptSelect, promptConfirm, promptInput } from "../utils/prompt.js";
5
+ const RALPH_DIR = ".ralph";
6
+ const CONFIG_FILE = "config.json";
7
+ const PROMPT_FILE = "prompt.md";
8
+ const PRD_FILE = "prd.json";
9
+ const PROGRESS_FILE = "progress.txt";
10
+ export async function init(_args) {
11
+ const cwd = process.cwd();
12
+ const ralphDir = join(cwd, RALPH_DIR);
13
+ console.log("Initializing ralph in current directory...\n");
14
+ // Check for existing .ralph directory
15
+ if (existsSync(ralphDir)) {
16
+ const reinit = await promptConfirm(".ralph/ directory already exists. Re-initialize?");
17
+ if (!reinit) {
18
+ console.log("Aborted.");
19
+ return;
20
+ }
21
+ }
22
+ else {
23
+ mkdirSync(ralphDir, { recursive: true });
24
+ console.log(`Created ${RALPH_DIR}/`);
25
+ }
26
+ // Select language
27
+ const languageKeys = Object.keys(LANGUAGES);
28
+ const languageNames = languageKeys.map(k => `${LANGUAGES[k].name} - ${LANGUAGES[k].description}`);
29
+ const selectedName = await promptSelect("Select your project language/runtime:", languageNames);
30
+ const selectedIndex = languageNames.indexOf(selectedName);
31
+ const selectedKey = languageKeys[selectedIndex];
32
+ const config = LANGUAGES[selectedKey];
33
+ // Allow custom commands
34
+ let checkCommand = config.checkCommand;
35
+ let testCommand = config.testCommand;
36
+ if (selectedKey === "none") {
37
+ checkCommand = await promptInput("\nEnter your type/build check command: ") || checkCommand;
38
+ testCommand = await promptInput("Enter your test command: ") || testCommand;
39
+ }
40
+ const finalConfig = {
41
+ ...config,
42
+ checkCommand,
43
+ testCommand,
44
+ };
45
+ // Generate image name from directory name
46
+ const projectName = basename(cwd).toLowerCase().replace(/[^a-z0-9-]/g, "-");
47
+ const imageName = `ralph-${projectName}`;
48
+ // Write config file
49
+ const configData = {
50
+ language: selectedKey,
51
+ checkCommand: finalConfig.checkCommand,
52
+ testCommand: finalConfig.testCommand,
53
+ imageName,
54
+ };
55
+ const configPath = join(ralphDir, CONFIG_FILE);
56
+ writeFileSync(configPath, JSON.stringify(configData, null, 2) + "\n");
57
+ console.log(`\nCreated ${RALPH_DIR}/${CONFIG_FILE}`);
58
+ // Write prompt file (ask if exists)
59
+ const prompt = generatePrompt(finalConfig);
60
+ const promptPath = join(ralphDir, PROMPT_FILE);
61
+ if (existsSync(promptPath)) {
62
+ const overwritePrompt = await promptConfirm(`${RALPH_DIR}/${PROMPT_FILE} already exists. Overwrite?`);
63
+ if (overwritePrompt) {
64
+ writeFileSync(promptPath, prompt + "\n");
65
+ console.log(`Updated ${RALPH_DIR}/${PROMPT_FILE}`);
66
+ }
67
+ else {
68
+ console.log(`Skipped ${RALPH_DIR}/${PROMPT_FILE}`);
69
+ }
70
+ }
71
+ else {
72
+ writeFileSync(promptPath, prompt + "\n");
73
+ console.log(`Created ${RALPH_DIR}/${PROMPT_FILE}`);
74
+ }
75
+ // Create PRD if not exists
76
+ const prdPath = join(ralphDir, PRD_FILE);
77
+ if (!existsSync(prdPath)) {
78
+ writeFileSync(prdPath, DEFAULT_PRD + "\n");
79
+ console.log(`Created ${RALPH_DIR}/${PRD_FILE}`);
80
+ }
81
+ else {
82
+ console.log(`Skipped ${RALPH_DIR}/${PRD_FILE} (already exists)`);
83
+ }
84
+ // Create progress file if not exists
85
+ const progressPath = join(ralphDir, PROGRESS_FILE);
86
+ if (!existsSync(progressPath)) {
87
+ writeFileSync(progressPath, DEFAULT_PROGRESS);
88
+ console.log(`Created ${RALPH_DIR}/${PROGRESS_FILE}`);
89
+ }
90
+ else {
91
+ console.log(`Skipped ${RALPH_DIR}/${PROGRESS_FILE} (already exists)`);
92
+ }
93
+ console.log("\nRalph initialized successfully!");
94
+ console.log("\nNext steps:");
95
+ console.log(" 1. Edit .ralph/prd.json to add your project requirements");
96
+ console.log(" 2. Run 'ralph once' to start the first iteration");
97
+ console.log(" 3. Or run 'ralph run 5' for 5 automated iterations");
98
+ }