ralph-cli-sandboxed 0.2.5 → 0.2.6

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.
@@ -14,118 +14,174 @@ const PROMPT_FILE = "prompt.md";
14
14
  const PRD_FILE = "prd.json";
15
15
  const PROGRESS_FILE = "progress.txt";
16
16
  const PRD_GUIDE_FILE = "HOW-TO-WRITE-PRDs.md";
17
- export async function init(_args) {
17
+ export async function init(args) {
18
18
  const cwd = process.cwd();
19
19
  const ralphDir = join(cwd, RALPH_DIR);
20
+ const useDefaults = args.includes("-y") || args.includes("--yes");
20
21
  console.log("Initializing ralph in current directory...\n");
21
22
  // Check for existing .ralph directory
22
23
  if (existsSync(ralphDir)) {
23
- const reinit = await promptConfirm(".ralph/ directory already exists. Re-initialize?");
24
- if (!reinit) {
25
- console.log("Aborted.");
26
- return;
24
+ if (!useDefaults) {
25
+ const reinit = await promptConfirm(".ralph/ directory already exists. Re-initialize?");
26
+ if (!reinit) {
27
+ console.log("Aborted.");
28
+ return;
29
+ }
27
30
  }
28
31
  }
29
32
  else {
30
33
  mkdirSync(ralphDir, { recursive: true });
31
34
  console.log(`Created ${RALPH_DIR}/`);
32
35
  }
33
- // Step 1: Select CLI provider (first)
34
36
  const CLI_PROVIDERS = getCliProviders();
35
- const providerKeys = Object.keys(CLI_PROVIDERS);
36
- const providerNames = providerKeys.map(k => `${CLI_PROVIDERS[k].name} - ${CLI_PROVIDERS[k].description}`);
37
- const selectedProviderName = await promptSelectWithArrows("Select your AI CLI provider:", providerNames);
38
- const selectedProviderIndex = providerNames.indexOf(selectedProviderName);
39
- const selectedCliProviderKey = providerKeys[selectedProviderIndex];
40
- const selectedProvider = CLI_PROVIDERS[selectedCliProviderKey];
37
+ const LANGUAGES = getLanguages();
38
+ let selectedCliProviderKey;
41
39
  let cliConfig;
42
- // Handle custom CLI provider
43
- if (selectedCliProviderKey === "custom") {
44
- const customCommand = await promptInput("\nEnter your CLI command: ");
45
- const customArgsInput = await promptInput("Enter default arguments (space-separated): ");
46
- const customArgs = customArgsInput.trim() ? customArgsInput.trim().split(/\s+/) : [];
47
- const customYoloArgsInput = await promptInput("Enter yolo/auto-approve arguments (space-separated): ");
48
- const customYoloArgs = customYoloArgsInput.trim() ? customYoloArgsInput.trim().split(/\s+/) : [];
49
- const customPromptArgsInput = await promptInput("Enter prompt arguments (e.g., -p for flag-based, leave empty for positional): ");
50
- const customPromptArgs = customPromptArgsInput.trim() ? customPromptArgsInput.trim().split(/\s+/) : [];
40
+ let selectedKey;
41
+ let selectedTechnologies = [];
42
+ let checkCommand;
43
+ let testCommand;
44
+ if (useDefaults) {
45
+ // Use defaults: Claude CLI + Node.js
46
+ selectedCliProviderKey = "claude";
47
+ const provider = CLI_PROVIDERS[selectedCliProviderKey];
51
48
  cliConfig = {
52
- command: customCommand || "claude",
53
- args: customArgs,
54
- yoloArgs: customYoloArgs.length > 0 ? customYoloArgs : undefined,
55
- promptArgs: customPromptArgs,
49
+ command: provider.command,
50
+ args: provider.defaultArgs,
51
+ yoloArgs: provider.yoloArgs.length > 0 ? provider.yoloArgs : undefined,
52
+ promptArgs: provider.promptArgs ?? [],
56
53
  };
54
+ selectedKey = "node";
55
+ const config = LANGUAGES[selectedKey];
56
+ checkCommand = config.checkCommand;
57
+ testCommand = config.testCommand;
58
+ console.log(`Using defaults: ${CLI_PROVIDERS[selectedCliProviderKey].name} + ${LANGUAGES[selectedKey].name}`);
57
59
  }
58
60
  else {
59
- cliConfig = {
60
- command: selectedProvider.command,
61
- args: selectedProvider.defaultArgs,
62
- yoloArgs: selectedProvider.yoloArgs.length > 0 ? selectedProvider.yoloArgs : undefined,
63
- promptArgs: selectedProvider.promptArgs ?? [],
64
- };
65
- }
66
- console.log(`\nSelected CLI provider: ${CLI_PROVIDERS[selectedCliProviderKey].name}`);
67
- // Step 2: Select language (second)
68
- const LANGUAGES = getLanguages();
69
- const languageKeys = Object.keys(LANGUAGES);
70
- const languageNames = languageKeys.map(k => `${LANGUAGES[k].name} - ${LANGUAGES[k].description}`);
71
- const selectedName = await promptSelectWithArrows("Select your project language/runtime:", languageNames);
72
- const selectedIndex = languageNames.indexOf(selectedName);
73
- const selectedKey = languageKeys[selectedIndex];
74
- const config = LANGUAGES[selectedKey];
75
- console.log(`\nSelected language: ${config.name}`);
76
- // Step 3: Select technology stack if available (third)
77
- let selectedTechnologies = [];
78
- if (config.technologies && config.technologies.length > 0) {
79
- const techOptions = config.technologies.map(t => `${t.name} - ${t.description}`);
80
- const techNames = config.technologies.map(t => t.name);
81
- selectedTechnologies = await promptMultiSelectWithArrows("Select your technology stack (optional):", techOptions);
82
- // Convert display names back to just technology names for predefined options
83
- selectedTechnologies = selectedTechnologies.map(sel => {
84
- const idx = techOptions.indexOf(sel);
85
- return idx >= 0 ? techNames[idx] : sel;
86
- });
87
- if (selectedTechnologies.length > 0) {
88
- console.log(`\nSelected technologies: ${selectedTechnologies.join(", ")}`);
61
+ // Step 1: Select CLI provider (first)
62
+ const providerKeys = Object.keys(CLI_PROVIDERS);
63
+ const providerNames = providerKeys.map(k => `${CLI_PROVIDERS[k].name} - ${CLI_PROVIDERS[k].description}`);
64
+ const selectedProviderName = await promptSelectWithArrows("Select your AI CLI provider:", providerNames);
65
+ const selectedProviderIndex = providerNames.indexOf(selectedProviderName);
66
+ selectedCliProviderKey = providerKeys[selectedProviderIndex];
67
+ const selectedProvider = CLI_PROVIDERS[selectedCliProviderKey];
68
+ // Handle custom CLI provider
69
+ if (selectedCliProviderKey === "custom") {
70
+ const customCommand = await promptInput("\nEnter your CLI command: ");
71
+ const customArgsInput = await promptInput("Enter default arguments (space-separated): ");
72
+ const customArgs = customArgsInput.trim() ? customArgsInput.trim().split(/\s+/) : [];
73
+ const customYoloArgsInput = await promptInput("Enter yolo/auto-approve arguments (space-separated): ");
74
+ const customYoloArgs = customYoloArgsInput.trim() ? customYoloArgsInput.trim().split(/\s+/) : [];
75
+ const customPromptArgsInput = await promptInput("Enter prompt arguments (e.g., -p for flag-based, leave empty for positional): ");
76
+ const customPromptArgs = customPromptArgsInput.trim() ? customPromptArgsInput.trim().split(/\s+/) : [];
77
+ cliConfig = {
78
+ command: customCommand || "claude",
79
+ args: customArgs,
80
+ yoloArgs: customYoloArgs.length > 0 ? customYoloArgs : undefined,
81
+ promptArgs: customPromptArgs,
82
+ };
89
83
  }
90
84
  else {
91
- console.log("\nNo technologies selected.");
85
+ cliConfig = {
86
+ command: selectedProvider.command,
87
+ args: selectedProvider.defaultArgs,
88
+ yoloArgs: selectedProvider.yoloArgs.length > 0 ? selectedProvider.yoloArgs : undefined,
89
+ promptArgs: selectedProvider.promptArgs ?? [],
90
+ };
91
+ }
92
+ console.log(`\nSelected CLI provider: ${CLI_PROVIDERS[selectedCliProviderKey].name}`);
93
+ // Step 2: Select language (second)
94
+ const languageKeys = Object.keys(LANGUAGES);
95
+ const languageNames = languageKeys.map(k => `${LANGUAGES[k].name} - ${LANGUAGES[k].description}`);
96
+ const selectedName = await promptSelectWithArrows("Select your project language/runtime:", languageNames);
97
+ const selectedIndex = languageNames.indexOf(selectedName);
98
+ selectedKey = languageKeys[selectedIndex];
99
+ const config = LANGUAGES[selectedKey];
100
+ console.log(`\nSelected language: ${config.name}`);
101
+ // Step 3: Select technology stack if available (third)
102
+ if (config.technologies && config.technologies.length > 0) {
103
+ const techOptions = config.technologies.map(t => `${t.name} - ${t.description}`);
104
+ const techNames = config.technologies.map(t => t.name);
105
+ selectedTechnologies = await promptMultiSelectWithArrows("Select your technology stack (optional):", techOptions);
106
+ // Convert display names back to just technology names for predefined options
107
+ selectedTechnologies = selectedTechnologies.map(sel => {
108
+ const idx = techOptions.indexOf(sel);
109
+ return idx >= 0 ? techNames[idx] : sel;
110
+ });
111
+ if (selectedTechnologies.length > 0) {
112
+ console.log(`\nSelected technologies: ${selectedTechnologies.join(", ")}`);
113
+ }
114
+ else {
115
+ console.log("\nNo technologies selected.");
116
+ }
117
+ }
118
+ // Allow custom commands for "none" language
119
+ checkCommand = config.checkCommand;
120
+ testCommand = config.testCommand;
121
+ if (selectedKey === "none") {
122
+ checkCommand = await promptInput("\nEnter your type/build check command: ") || checkCommand;
123
+ testCommand = await promptInput("Enter your test command: ") || testCommand;
92
124
  }
93
- }
94
- // Allow custom commands for "none" language
95
- let checkCommand = config.checkCommand;
96
- let testCommand = config.testCommand;
97
- if (selectedKey === "none") {
98
- checkCommand = await promptInput("\nEnter your type/build check command: ") || checkCommand;
99
- testCommand = await promptInput("Enter your test command: ") || testCommand;
100
125
  }
101
126
  const finalConfig = {
102
- ...config,
127
+ ...LANGUAGES[selectedKey],
103
128
  checkCommand,
104
129
  testCommand,
105
130
  };
106
131
  // Generate image name from directory name
107
132
  const projectName = basename(cwd).toLowerCase().replace(/[^a-z0-9-]/g, "-");
108
133
  const imageName = `ralph-${projectName}`;
109
- // Write config file
134
+ // Write config file with all available options (defaults or empty values)
110
135
  const configData = {
136
+ // Required fields
111
137
  language: selectedKey,
112
138
  checkCommand: finalConfig.checkCommand,
113
139
  testCommand: finalConfig.testCommand,
114
140
  imageName,
141
+ // CLI configuration
115
142
  cli: cliConfig,
116
143
  cliProvider: selectedCliProviderKey,
144
+ // Optional fields with defaults/empty values for discoverability
145
+ notifyCommand: "",
146
+ technologies: selectedTechnologies.length > 0 ? selectedTechnologies : [],
147
+ javaVersion: selectedKey === "java" ? 21 : null,
148
+ // Docker configuration options
149
+ docker: {
150
+ ports: [],
151
+ volumes: [],
152
+ environment: {},
153
+ git: {
154
+ name: "",
155
+ email: "",
156
+ },
157
+ packages: [],
158
+ buildCommands: {
159
+ root: [],
160
+ node: [],
161
+ },
162
+ startCommand: "",
163
+ asciinema: {
164
+ enabled: false,
165
+ autoRecord: false,
166
+ outputDir: ".recordings",
167
+ },
168
+ firewall: {
169
+ allowedDomains: [],
170
+ },
171
+ },
172
+ // Claude-specific configuration (MCP servers and skills)
173
+ claude: {
174
+ mcpServers: {},
175
+ skills: [],
176
+ },
117
177
  };
118
- // Add technologies if any were selected
119
- if (selectedTechnologies.length > 0) {
120
- configData.technologies = selectedTechnologies;
121
- }
122
178
  const configPath = join(ralphDir, CONFIG_FILE);
123
179
  writeFileSync(configPath, JSON.stringify(configData, null, 2) + "\n");
124
180
  console.log(`\nCreated ${RALPH_DIR}/${CONFIG_FILE}`);
125
181
  // Write prompt file (ask if exists) - uses template with $variables
126
182
  const prompt = generatePromptTemplate();
127
183
  const promptPath = join(ralphDir, PROMPT_FILE);
128
- if (existsSync(promptPath)) {
184
+ if (existsSync(promptPath) && !useDefaults) {
129
185
  const overwritePrompt = await promptConfirm(`${RALPH_DIR}/${PROMPT_FILE} already exists. Overwrite?`);
130
186
  if (overwritePrompt) {
131
187
  writeFileSync(promptPath, prompt + "\n");
@@ -137,7 +193,7 @@ export async function init(_args) {
137
193
  }
138
194
  else {
139
195
  writeFileSync(promptPath, prompt + "\n");
140
- console.log(`Created ${RALPH_DIR}/${PROMPT_FILE}`);
196
+ console.log(`${existsSync(promptPath) ? "Updated" : "Created"} ${RALPH_DIR}/${PROMPT_FILE}`);
141
197
  }
142
198
  // Create PRD if not exists
143
199
  const prdPath = join(ralphDir, PRD_FILE);
@@ -259,7 +259,7 @@
259
259
  "checkCommand": "swift build",
260
260
  "testCommand": "swift test",
261
261
  "docker": {
262
- "install": "# Install Swift toolchain\nRUN apt-get update && apt-get install -y \\\n binutils \\\n git \\\n gnupg2 \\\n libc6-dev \\\n libcurl4-openssl-dev \\\n libedit2 \\\n libgcc-11-dev \\\n libpython3-dev \\\n libsqlite3-0 \\\n libstdc++-11-dev \\\n libxml2-dev \\\n libz3-dev \\\n pkg-config \\\n tzdata \\\n unzip \\\n zlib1g-dev \\\n && rm -rf /var/lib/apt/lists/*\nRUN ARCH=$(dpkg --print-architecture) && \\\n if [ \"$ARCH\" = \"amd64\" ]; then SWIFT_ARCH=\"x86_64\"; else SWIFT_ARCH=\"aarch64\"; fi && \\\n curl -fsSL https://download.swift.org/swift-5.10-release/ubuntu2204/swift-5.10-RELEASE/swift-5.10-RELEASE-ubuntu22.04-${SWIFT_ARCH}.tar.gz | tar -xz -C /opt\nENV PATH=\"/opt/swift-5.10-RELEASE-ubuntu22.04/usr/bin:$PATH\""
262
+ "install": "# Install Swift toolchain\nRUN apt-get update && apt-get install -y \\\n binutils \\\n git \\\n gnupg2 \\\n libc6-dev \\\n libcurl4-openssl-dev \\\n libedit2 \\\n libgcc-11-dev \\\n libpython3-dev \\\n libsqlite3-0 \\\n libstdc++-11-dev \\\n libxml2-dev \\\n libz3-dev \\\n pkg-config \\\n tzdata \\\n unzip \\\n zlib1g-dev \\\n && rm -rf /var/lib/apt/lists/*\nRUN ARCH=$(dpkg --print-architecture) && \\\n if [ \"$ARCH\" = \"amd64\" ]; then \\\n SWIFT_PLATFORM=\"ubuntu2204\"; \\\n SWIFT_FILE=\"swift-5.10-RELEASE-ubuntu22.04\"; \\\n else \\\n SWIFT_PLATFORM=\"ubuntu2204-aarch64\"; \\\n SWIFT_FILE=\"swift-5.10-RELEASE-ubuntu22.04-aarch64\"; \\\n fi && \\\n curl -fsSL https://download.swift.org/swift-5.10-release/${SWIFT_PLATFORM}/swift-5.10-RELEASE/${SWIFT_FILE}.tar.gz | tar -xz -C /opt && \\\n mv /opt/${SWIFT_FILE} /opt/swift\nENV PATH=\"/opt/swift/usr/bin:$PATH\""
263
263
  },
264
264
  "technologies": [
265
265
  { "name": "Vapor", "description": "Server-side Swift web framework" },
@@ -5,6 +5,22 @@ export interface CliConfig {
5
5
  promptArgs?: string[];
6
6
  modelArgs?: string[];
7
7
  }
8
+ export interface McpServerConfig {
9
+ command: string;
10
+ args?: string[];
11
+ env?: Record<string, string>;
12
+ }
13
+ export interface SkillConfig {
14
+ name: string;
15
+ description: string;
16
+ instructions: string;
17
+ userInvocable?: boolean;
18
+ }
19
+ export interface AsciinemaConfig {
20
+ enabled: boolean;
21
+ autoRecord?: boolean;
22
+ outputDir?: string;
23
+ }
8
24
  export interface RalphConfig {
9
25
  language: string;
10
26
  checkCommand: string;
@@ -23,6 +39,20 @@ export interface RalphConfig {
23
39
  name?: string;
24
40
  email?: string;
25
41
  };
42
+ packages?: string[];
43
+ buildCommands?: {
44
+ root?: string[];
45
+ node?: string[];
46
+ };
47
+ startCommand?: string;
48
+ asciinema?: AsciinemaConfig;
49
+ firewall?: {
50
+ allowedDomains?: string[];
51
+ };
52
+ };
53
+ claude?: {
54
+ mcpServers?: Record<string, McpServerConfig>;
55
+ skills?: SkillConfig[];
26
56
  };
27
57
  }
28
58
  export declare const DEFAULT_CLI_CONFIG: CliConfig;
@@ -5,6 +5,6 @@ export declare function createPrompt(): {
5
5
  export declare function promptSelectWithArrows(message: string, options: string[]): Promise<string>;
6
6
  export declare function promptInput(message: string): Promise<string>;
7
7
  export declare function promptSelect(message: string, options: string[]): Promise<string>;
8
- export declare function promptConfirm(message: string): Promise<boolean>;
8
+ export declare function promptConfirm(message: string, defaultValue?: boolean): Promise<boolean>;
9
9
  export declare function promptMultiSelect(message: string, options: string[]): Promise<string[]>;
10
10
  export declare function promptMultiSelectWithArrows(message: string, options: string[]): Promise<string[]>;
@@ -96,11 +96,17 @@ export async function promptSelect(message, options) {
96
96
  console.log("Invalid selection.");
97
97
  }
98
98
  }
99
- export async function promptConfirm(message) {
99
+ export async function promptConfirm(message, defaultValue = true) {
100
100
  const prompt = createPrompt();
101
+ const hint = defaultValue ? "(Y/n)" : "(y/N)";
101
102
  while (true) {
102
- const answer = await prompt.question(`${message} (y/n): `);
103
+ const answer = await prompt.question(`${message} ${hint}: `);
103
104
  const normalized = answer.trim().toLowerCase();
105
+ // Empty input returns default
106
+ if (normalized === "") {
107
+ prompt.close();
108
+ return defaultValue;
109
+ }
104
110
  if (normalized === "y" || normalized === "yes") {
105
111
  prompt.close();
106
112
  return true;
@@ -0,0 +1,161 @@
1
+ # Development
2
+
3
+ Guide for contributing to ralph-cli-sandboxed.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ # Clone the repository
9
+ git clone https://github.com/choas/ralph-cli-sandboxed
10
+ cd ralph-cli-sandboxed
11
+
12
+ # Install dependencies
13
+ npm install
14
+ ```
15
+
16
+ ## Development Mode
17
+
18
+ Run ralph directly from TypeScript source without building:
19
+
20
+ ```bash
21
+ npm run dev -- <args>
22
+
23
+ # Examples:
24
+ npm run dev -- --version
25
+ npm run dev -- list
26
+ npm run dev -- once
27
+ npm run dev -- help
28
+ ```
29
+
30
+ This uses `tsx` to run TypeScript directly, allowing you to test changes immediately.
31
+
32
+ ## Building
33
+
34
+ ```bash
35
+ # Build for distribution
36
+ npm run build
37
+
38
+ # This runs:
39
+ # 1. tsc - Compiles TypeScript to dist/
40
+ # 2. Copies config files to dist/config/
41
+ ```
42
+
43
+ ## Project Structure
44
+
45
+ ```
46
+ ralph-cli-sandboxed/
47
+ ├── src/
48
+ │ ├── index.ts # CLI entry point
49
+ │ ├── commands/ # Command implementations
50
+ │ │ ├── init.ts # ralph init
51
+ │ │ ├── run.ts # ralph run
52
+ │ │ ├── once.ts # ralph once
53
+ │ │ ├── prd.ts # PRD management commands
54
+ │ │ ├── docker.ts # Docker commands
55
+ │ │ ├── prompt.ts # ralph prompt
56
+ │ │ ├── fix-prd.ts # ralph fix-prd
57
+ │ │ └── help.ts # ralph help
58
+ │ ├── utils/
59
+ │ │ ├── config.ts # Configuration loading
60
+ │ │ ├── prd-validator.ts # PRD validation and recovery
61
+ │ │ └── prompt.ts # Interactive prompts
62
+ │ ├── templates/
63
+ │ │ └── prompts.ts # Prompt template generation
64
+ │ └── config/
65
+ │ ├── languages.json # Language configurations
66
+ │ └── cli-providers.json # CLI provider configurations
67
+ ├── docs/ # Documentation
68
+ ├── dist/ # Compiled output (generated)
69
+ └── package.json
70
+ ```
71
+
72
+ ## Adding a New Language
73
+
74
+ Edit `src/config/languages.json`:
75
+
76
+ ```json
77
+ {
78
+ "languages": {
79
+ "your-language": {
80
+ "name": "Your Language",
81
+ "description": "Description here",
82
+ "checkCommand": "your-check-command",
83
+ "testCommand": "your-test-command",
84
+ "docker": {
85
+ "install": "# Installation commands for Dockerfile"
86
+ },
87
+ "technologies": [
88
+ { "name": "Framework", "description": "Description" }
89
+ ]
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ ## Adding a New CLI Provider
96
+
97
+ Edit `src/config/cli-providers.json`:
98
+
99
+ ```json
100
+ {
101
+ "providers": {
102
+ "your-cli": {
103
+ "name": "Your CLI",
104
+ "description": "Description",
105
+ "command": "cli-command",
106
+ "defaultArgs": [],
107
+ "yoloArgs": ["--auto-approve-flag"],
108
+ "promptArgs": ["--prompt"],
109
+ "docker": {
110
+ "install": "# Installation commands"
111
+ },
112
+ "envVars": ["YOUR_API_KEY"],
113
+ "modelArgs": ["--model"]
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ ## Testing Changes
120
+
121
+ Since ralph automates AI agents, testing requires caution:
122
+
123
+ 1. **Use a test project** - Create a sample project to test changes
124
+ 2. **Use `ralph once`** - Run single iterations for testing
125
+ 3. **Check output** - Review `.ralph/progress.txt` and git commits
126
+
127
+ ## Platform-Specific Dependencies
128
+
129
+ The `node_modules` folder contains platform-specific binaries. If you switch between environments:
130
+
131
+ ```bash
132
+ # When switching between host and container
133
+ rm -rf node_modules && npm install
134
+ ```
135
+
136
+ Or use a separate volume for container node_modules:
137
+
138
+ ```bash
139
+ docker run -v $(pwd):/workspace -v /workspace/node_modules your-image
140
+ ```
141
+
142
+ ## Code Style
143
+
144
+ - TypeScript with ES2022 target
145
+ - Node.js 18+ required
146
+ - Use async/await for asynchronous operations
147
+ - Keep functions focused and small
148
+
149
+ ## Submitting Changes
150
+
151
+ 1. Fork the repository
152
+ 2. Create a feature branch
153
+ 3. Make your changes
154
+ 4. Test thoroughly
155
+ 5. Submit a pull request
156
+
157
+ ## Requirements
158
+
159
+ - Node.js 18+
160
+ - npm
161
+ - Docker (for testing container functionality)