ralph-codex 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/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.1.0] - 2026-01-21
6
+ - Initial release.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ralph Codex
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # ralph-codex
2
+
3
+ Codex-first Ralph-style planning and run loops.
4
+
5
+ ## What it does
6
+
7
+ - Turns an idea into a task plan (`tasks.md`) with one round of questions.
8
+ - Runs the loop until completion, keeping state in `.ralph/`.
9
+ - Optional Docker mode for reproducible runs.
10
+ - Colorized Codex output in TTY for easier scanning (disable with `NO_COLOR=1`).
11
+
12
+ ## Requirements
13
+
14
+ - Node.js >= 18
15
+ - Codex CLI installed and authenticated (`codex` available in PATH)
16
+ - Docker (optional, only for Docker mode)
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ npm install -g ralph-codex
22
+ # or
23
+ npx ralph-codex --help
24
+ ```
25
+
26
+ ## Quick start
27
+
28
+ ```bash
29
+ ralph-codex init
30
+ ralph-codex plan "Add screenshot flow for /demo" --output tasks.md
31
+ ralph-codex run
32
+ ralph-codex revise "Improve the flow after QA"
33
+ ralph-codex view
34
+ ralph-codex reset
35
+ ```
36
+
37
+ ## Command reference
38
+
39
+ Use `--help` with any command to see its available options.
40
+
41
+ ### init
42
+
43
+ Create `ralph.config.yml` and add `.ralph` to `.gitignore`.
44
+ Init is interactive and prompts for Codex settings and Docker usage.
45
+
46
+ ```bash
47
+ ralph-codex init [--force] [--config <path>] [--no-gitignore]
48
+ ```
49
+
50
+ ### plan
51
+
52
+ Generate `tasks.md` with one round of questions.
53
+
54
+ ```bash
55
+ ralph-codex plan "<idea>" [--output <path>] [--tasks <path>] [--max-iterations <n>]
56
+ ```
57
+
58
+ Common options:
59
+
60
+ - `--output <path>` write tasks to a custom file (alias of `--tasks`).
61
+ - `--config <path>` use a custom config file.
62
+ - `--model <name>` or `-m` set the Codex model.
63
+ - `--profile <name>` or `-p` use a Codex CLI profile.
64
+ - `--sandbox <mode>` set sandbox mode.
65
+ - `--ask-for-approval <mode>` set approval policy.
66
+ - `--full-auto` use workspace-write + on-request.
67
+ - `--reasoning [effort]` override reasoning effort; omit value to pick from a list (defaults to medium).
68
+ - `--detect-success-criteria` add auto-detected checks to the success list.
69
+ - `--no-detect-success-criteria` disable auto-detect (overrides config).
70
+ - In the interactive checklist, choose `Ask Codex to choose` to let Codex derive success criteria.
71
+
72
+ ### run
73
+
74
+ Execute the loop until completion.
75
+
76
+ ```bash
77
+ ralph-codex run [--input <path>] [--tasks <path>] [--max-iterations <n>]
78
+ ```
79
+
80
+ Useful options:
81
+
82
+ - `--input <path>` read tasks from a custom file (alias of `--tasks`).
83
+ - `--quiet` reduce output.
84
+ - `--max-iteration-seconds <n>` soft per-iteration limit (stop after current loop).
85
+ - `--max-total-seconds <n>` hard total limit (kills in-flight loop).
86
+ - `--completion-promise <text>` change the completion marker.
87
+ - `--stop-on-error` stop on the first error.
88
+ - `--no-tail` disable log/scratchpad tailing.
89
+ - `--reasoning [effort]` override reasoning effort; omit value to pick from a list (defaults to medium).
90
+
91
+ ### revise
92
+
93
+ Add new tasks from feedback without changing existing tasks (alias: `refine`).
94
+
95
+ ```bash
96
+ ralph-codex revise "<feedback>" [--tasks <path>] [--run]
97
+ ```
98
+
99
+ Useful options:
100
+
101
+ - `--tasks <path>` point to a custom tasks file.
102
+ - `--config <path>` use a custom config file.
103
+ - `--reasoning [effort]` override reasoning effort; omit value to pick from a list.
104
+ - `--run` run after approving the changes.
105
+
106
+ ### view
107
+
108
+ Show tasks, success criteria, and config summaries.
109
+
110
+ ```bash
111
+ ralph-codex view [section] [--format table|list|json]
112
+ ```
113
+
114
+ Sections:
115
+
116
+ - `tasks` shows task status counts and list.
117
+ - `criteria` shows the success criteria list.
118
+ - `config` shows effective config values.
119
+
120
+ Useful options:
121
+
122
+ - `--tasks <path>` point to a custom tasks file.
123
+ - `--config <path>` use a custom config file.
124
+ - `--format <format>` table | list | json (default: table).
125
+ - `--only <filter>` pending | blocked | done (tasks only).
126
+ - `--limit <n>` limit task rows (0 = no limit).
127
+ - `--watch`, `-w` watch for changes and refresh the view.
128
+
129
+ ### reset
130
+
131
+ Reset all tasks in `tasks.md` to `[ ]`.
132
+ Uses `plan.tasks_path` or `run.tasks_path` when set, unless overridden.
133
+
134
+ ```bash
135
+ ralph-codex reset [--tasks <path>] [--config <path>]
136
+ ```
137
+
138
+ ### docker
139
+
140
+ Ask Codex for a base image and update Docker config.
141
+
142
+ ```bash
143
+ ralph-codex docker [--config <path>]
144
+ ```
145
+
146
+ ## Configuration
147
+
148
+ The CLI reads `ralph.config.yml` in the project root. Use `--config <path>` to point elsewhere.
149
+ Run `ralph-codex init` to generate the full config. Example of the most common fields:
150
+
151
+ ```yaml
152
+ version: 1
153
+
154
+ codex:
155
+ model: gpt-5-codex
156
+ profile: default
157
+ sandbox: workspace-write
158
+ ask_for_approval: on-request
159
+ model_reasoning_effort: medium # or null to use Codex default
160
+
161
+ docker:
162
+ enabled: false
163
+ use_for_plan: false
164
+ base_image: node:20-bullseye
165
+ codex_install: npm install -g @openai/codex
166
+
167
+ plan:
168
+ auto_detect_success_criteria: false
169
+ tasks_path: tasks.md
170
+
171
+ run:
172
+ max_iterations: 15
173
+ max_iteration_seconds: null
174
+ max_total_seconds: null
175
+ ```
176
+
177
+ Codex settings quick guide:
178
+ - `profile` selects a Codex CLI profile (default `null`, uses Codex default).
179
+ - `sandbox` sets permission mode (`read-only`, `workspace-write`, `danger-full-access`; default `null`).
180
+ - `ask_for_approval` controls prompt behavior (`untrusted`, `on-failure`, `on-request`, `never`; default `null`).
181
+ - `full_auto` is a convenience preset for `workspace-write` + `on-request` (default `false`).
182
+ - `model_reasoning_effort` chooses reasoning level (`low`, `medium`, `high`, `xhigh`; default `null`).
183
+
184
+ Enable `plan.auto_detect_success_criteria` to add detected checks based on repo files.
185
+
186
+ CLI flags always override config values.
187
+
188
+ ## Docker mode
189
+
190
+ 1. Run `ralph-codex docker` to pick a base image.
191
+ 2. Set `docker.codex_install` so Codex is available inside the container.
192
+ 3. Run `ralph-codex plan` and `ralph-codex run` as usual. Enable `docker.use_for_plan`
193
+ if you want planning to happen inside Docker as well.
194
+
195
+ `Dockerfile.ralph` is generated automatically when Docker is enabled.
196
+
197
+ ## Files created
198
+
199
+ - `ralph.config.yml` default config file.
200
+ - `tasks.md` planning output (configurable).
201
+ - `.ralph/` run state, summaries, and logs.
202
+
203
+ ## Output styling
204
+
205
+ Codex output is colorized when stdout is a TTY. Plan uses spinners and run shows a task
206
+ progress bar when interactive. Set `NO_COLOR=1` to disable color styling.
207
+
208
+ ## Troubleshooting
209
+
210
+ - `codex: command not found` -> install Codex CLI and ensure it is in PATH.
211
+ - Docker errors -> start Docker Desktop/Colima and retry.
212
+ - Plan/run fail to read config -> verify `ralph.config.yml` path or pass `--config`.
213
+
214
+ ## License
215
+
216
+ MIT
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from "path";
4
+ import { spawnSync } from "child_process";
5
+ import { fileURLToPath } from "url";
6
+ import { createRequire } from "module";
7
+ import { colors } from "../src/ui/terminal.js";
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const require = createRequire(import.meta.url);
11
+
12
+ const argv = process.argv.slice(2);
13
+ const cmd = argv[0];
14
+ const args = argv.slice(1);
15
+
16
+ const showHelp = () => {
17
+ process.stdout.write(
18
+ `\n${colors.cyan("ralph-codex <command> [options]")}\n\n` +
19
+ `${colors.yellow("Commands:")}\n` +
20
+ ` ${colors.green("init")} Create ralph.config.yml and update .gitignore\n` +
21
+ ` ${colors.green("plan")} Generate tasks.md with a single round of questions\n` +
22
+ ` ${colors.green("run")} Execute the loop until completion\n` +
23
+ ` ${colors.green("revise")} Add new tasks from feedback (alias: refine)\n` +
24
+ ` ${colors.green("view")} Show tasks, criteria, and config summaries\n` +
25
+ ` ${colors.green("reset")} Reset all tasks in tasks.md to [ ]\n` +
26
+ ` ${colors.green("docker")} Pick a Docker base image via Codex and update config\n\n` +
27
+ `${colors.yellow("Examples:")}\n` +
28
+ ` ralph-codex init\n` +
29
+ ` ralph-codex plan "Add screenshot flow"\n` +
30
+ ` ralph-codex run --max-iterations 15\n` +
31
+ ` ralph-codex revise "Improve copy"\n` +
32
+ ` ralph-codex view tasks --only pending\n` +
33
+ ` ralph-codex reset\n\n` +
34
+ `${colors.gray('Tip: run "ralph-codex <command> --help" for command options.')}\n\n`
35
+ );
36
+ };
37
+
38
+ if (!cmd || cmd === "help" || cmd === "-h" || cmd === "--help") {
39
+ showHelp();
40
+ process.exit(0);
41
+ }
42
+
43
+ if (cmd === "-v" || cmd === "--version") {
44
+ const pkg = require(path.join(__dirname, "..", "package.json"));
45
+ process.stdout.write(`${pkg.version}\n`);
46
+ process.exit(0);
47
+ }
48
+
49
+ const scriptPath = path.join(__dirname, "..", "src", "commands", `${cmd}.js`);
50
+ const result = spawnSync(process.execPath, [scriptPath, ...args], {
51
+ stdio: "inherit",
52
+ cwd: process.cwd(),
53
+ env: process.env,
54
+ });
55
+
56
+ process.exit(result.status ?? 0);
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "ralph-codex",
3
+ "version": "0.1.0",
4
+ "description": "Codex-first Ralph-style planning and run loops",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/vhmartinezm/ralph-codex.git"
8
+ },
9
+ "bugs": {
10
+ "url": "https://github.com/vhmartinezm/ralph-codex/issues"
11
+ },
12
+ "homepage": "https://github.com/vhmartinezm/ralph-codex#readme",
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "license": "MIT",
17
+ "type": "module",
18
+ "bin": {
19
+ "ralph-codex": "bin/ralph-codex.js"
20
+ },
21
+ "exports": {
22
+ "./package.json": "./package.json"
23
+ },
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "packageManager": "npm@10.8.2",
28
+ "dependencies": {
29
+ "cli-progress": "^3.12.0",
30
+ "enquirer": "^2.4.1",
31
+ "js-yaml": "^4.1.0",
32
+ "ora": "^5.4.1",
33
+ "picocolors": "^1.0.0"
34
+ },
35
+ "files": [
36
+ "bin",
37
+ "src",
38
+ "templates",
39
+ "CHANGELOG.md",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "keywords": [
44
+ "codex",
45
+ "ralph",
46
+ "cli",
47
+ "automation"
48
+ ]
49
+ }
@@ -0,0 +1,174 @@
1
+ import { spawnSync } from "child_process";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import enquirer from "enquirer";
5
+ import yaml from "js-yaml";
6
+
7
+ const { Confirm } = enquirer;
8
+
9
+ const root = process.cwd();
10
+ const defaultConfigPath = path.join(root, "ralph.config.yml");
11
+
12
+ const argv = process.argv.slice(2);
13
+ let configPath = null;
14
+
15
+ for (let i = 0; i < argv.length; i += 1) {
16
+ const arg = argv[i];
17
+ if (arg === "--config") {
18
+ configPath = argv[i + 1];
19
+ i += 1;
20
+ continue;
21
+ }
22
+ }
23
+
24
+ const resolvedConfigPath = configPath || defaultConfigPath;
25
+
26
+ function loadConfig(configFilePath) {
27
+ if (!fs.existsSync(configFilePath)) return {};
28
+ try {
29
+ const content = fs.readFileSync(configFilePath, "utf8");
30
+ return yaml.load(content) || {};
31
+ } catch (error) {
32
+ console.error(
33
+ `Failed to read config at ${configFilePath}: ${error?.message || error}`
34
+ );
35
+ process.exit(1);
36
+ }
37
+ }
38
+
39
+ function parseBaseImage(output) {
40
+ const match = output.match(/BASE_IMAGE:\s*([^\r\n]+)/i);
41
+ if (!match) return null;
42
+ const line = match[1].trim();
43
+ const image = line.split(/\s+/)[0].replace(/[`"'(),.]+$/g, "");
44
+ return image || null;
45
+ }
46
+
47
+ function updateDockerConfigText(content, baseImage) {
48
+ const lines = content.split(/\r?\n/);
49
+ let dockerStart = lines.findIndex((line) => /^docker:\s*$/.test(line));
50
+ const topLevel = (line) => /^[A-Za-z_][A-Za-z0-9_-]*:\s*$/.test(line);
51
+
52
+ if (dockerStart === -1) {
53
+ lines.push(
54
+ "",
55
+ "docker:",
56
+ " enabled: true",
57
+ ` base_image: ${baseImage}`
58
+ );
59
+ return lines.join("\n");
60
+ }
61
+
62
+ let end = lines.length;
63
+ for (let i = dockerStart + 1; i < lines.length; i += 1) {
64
+ if (topLevel(lines[i]) && !lines[i].startsWith(" ")) {
65
+ end = i;
66
+ break;
67
+ }
68
+ }
69
+
70
+ let hasEnabled = false;
71
+ let hasBase = false;
72
+ for (let i = dockerStart + 1; i < end; i += 1) {
73
+ if (/^\s+enabled:\s*/.test(lines[i])) {
74
+ lines[i] = " enabled: true";
75
+ hasEnabled = true;
76
+ }
77
+ if (/^\s+base_image:\s*/.test(lines[i])) {
78
+ lines[i] = ` base_image: ${baseImage}`;
79
+ hasBase = true;
80
+ }
81
+ }
82
+
83
+ const insertAt = dockerStart + 1;
84
+ const insertLines = [];
85
+ if (!hasEnabled) insertLines.push(" enabled: true");
86
+ if (!hasBase) insertLines.push(` base_image: ${baseImage}`);
87
+ if (insertLines.length > 0) {
88
+ lines.splice(insertAt, 0, ...insertLines);
89
+ }
90
+
91
+ return lines.join("\n");
92
+ }
93
+
94
+ function runCodex(prompt, codexConfig) {
95
+ const args = ["exec"];
96
+ if (codexConfig.model) args.push("--model", codexConfig.model);
97
+ if (codexConfig.profile) args.push("--profile", codexConfig.profile);
98
+ if (codexConfig.full_auto) args.push("--full-auto");
99
+ if (codexConfig.ask_for_approval) {
100
+ args.push("--config", `ask_for_approval=${codexConfig.ask_for_approval}`);
101
+ }
102
+ if (codexConfig.model_reasoning_effort) {
103
+ args.push(
104
+ "--config",
105
+ `model_reasoning_effort=${codexConfig.model_reasoning_effort}`
106
+ );
107
+ }
108
+ if (codexConfig.sandbox) args.push("--sandbox", codexConfig.sandbox);
109
+ args.push("-");
110
+
111
+ const result = spawnSync("codex", args, {
112
+ input: prompt,
113
+ encoding: "utf8",
114
+ cwd: root,
115
+ env: process.env,
116
+ });
117
+
118
+ const combined = `${result.stdout || ""}\n${result.stderr || ""}`.trim();
119
+ if (combined) process.stdout.write(`${combined}\n`);
120
+ return combined;
121
+ }
122
+
123
+ function buildPrompt(nodeVersion) {
124
+ const nodeLine = nodeVersion ? `Node version: ${nodeVersion}` : "Node 20+";
125
+ return `Encuentra la mejor imagen base de Docker para este proyecto.
126
+ Considera que necesita ejecutar npm scripts, módulos nativos y evitar Alpine.
127
+ ${nodeLine}
128
+
129
+ Responde SOLO con una línea en este formato:
130
+ BASE_IMAGE: <imagen>`;
131
+ }
132
+
133
+ async function main() {
134
+ const config = loadConfig(resolvedConfigPath);
135
+ const codexConfig = config?.codex || {};
136
+
137
+ const nodeVersionPath = path.join(root, ".nvmrc");
138
+ const nodeVersion = fs.existsSync(nodeVersionPath)
139
+ ? fs.readFileSync(nodeVersionPath, "utf8").trim()
140
+ : "";
141
+
142
+ const prompt = buildPrompt(nodeVersion);
143
+ const output = runCodex(prompt, codexConfig);
144
+ const baseImage = parseBaseImage(output);
145
+
146
+ if (!baseImage) {
147
+ console.error("Could not parse BASE_IMAGE from Codex output.");
148
+ process.exit(1);
149
+ }
150
+
151
+ const confirm = new Confirm({
152
+ name: "confirm",
153
+ message: `Set docker.enabled=true and base_image=${baseImage}?`,
154
+ initial: true,
155
+ });
156
+
157
+ const approved = await confirm.run();
158
+ if (!approved) {
159
+ process.stdout.write("Aborted by user.\n");
160
+ process.exit(1);
161
+ }
162
+
163
+ const content = fs.existsSync(resolvedConfigPath)
164
+ ? fs.readFileSync(resolvedConfigPath, "utf8")
165
+ : "";
166
+ const updated = updateDockerConfigText(content, baseImage);
167
+ fs.writeFileSync(resolvedConfigPath, updated, "utf8");
168
+
169
+ process.stdout.write(
170
+ `Updated ${path.relative(root, resolvedConfigPath)} with docker.enabled=true and base_image=${baseImage}.\n`
171
+ );
172
+ }
173
+
174
+ void main();