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 +6 -0
- package/LICENSE +21 -0
- package/README.md +216 -0
- package/bin/ralph-codex.js +56 -0
- package/package.json +49 -0
- package/src/commands/docker.js +174 -0
- package/src/commands/init.js +413 -0
- package/src/commands/plan.js +1129 -0
- package/src/commands/refine.js +1 -0
- package/src/commands/reset.js +105 -0
- package/src/commands/revise.js +571 -0
- package/src/commands/run.js +1225 -0
- package/src/commands/view.js +695 -0
- package/src/ui/terminal.js +137 -0
- package/templates/ralph.config.yml +53 -0
package/CHANGELOG.md
ADDED
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();
|