ralph-codex 0.1.1 → 0.1.3
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 +9 -1
- package/README.md +106 -4
- package/package.json +8 -1
- package/src/commands/completion.js +12 -4
- package/src/commands/docker.js +12 -11
- package/src/commands/init.js +21 -7
- package/src/commands/plan.js +69 -9
- package/src/commands/revise.js +6 -85
- package/src/commands/run.js +30 -3
- package/src/commands/view.js +3 -36
- package/src/lib/tasks.js +97 -0
- package/templates/ralph.config.yml +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.3] - 2026-01-22
|
|
6
|
+
- Added comprehensive CLI test suite with vitest, harness, and fixtures.
|
|
7
|
+
- Added GitHub Actions CI for Node 18/20/22.
|
|
8
|
+
- Guarded run completion to require all tasks checked off.
|
|
9
|
+
|
|
10
|
+
## [0.1.2] - 2026-01-22
|
|
11
|
+
- Improved README with setup, defaults, Docker guidance, and troubleshooting.
|
|
12
|
+
|
|
5
13
|
## [0.1.1] - 2026-01-22
|
|
6
|
-
- Added shell completion command
|
|
14
|
+
- Added shell completion command (bash/zsh/fish) with dynamic suggestions.
|
|
7
15
|
|
|
8
16
|
## [0.1.0] - 2026-01-21
|
|
9
17
|
- Initial release.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# ralph-codex
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
Codex-first Ralph-style planning and run loops.
|
|
4
6
|
|
|
5
7
|
## What it does
|
|
@@ -9,7 +11,14 @@ Codex-first Ralph-style planning and run loops.
|
|
|
9
11
|
- Optional Docker mode for reproducible runs.
|
|
10
12
|
- Colorized Codex output in TTY for easier scanning (disable with `NO_COLOR=1`).
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
## How it works
|
|
15
|
+
|
|
16
|
+
- `plan` asks for success criteria, runs Codex, and writes `tasks.md`.
|
|
17
|
+
- `run` executes tasks until `LOOP_COMPLETE`, updating `.ralph/` state and logs.
|
|
18
|
+
- `revise` adds new tasks from feedback without touching existing items.
|
|
19
|
+
- `view` and `reset` help you inspect and reset task status.
|
|
20
|
+
|
|
21
|
+

|
|
13
22
|
|
|
14
23
|
## Requirements
|
|
15
24
|
|
|
@@ -17,6 +26,19 @@ Codex-first Ralph-style planning and run loops.
|
|
|
17
26
|
- Codex CLI installed and authenticated (`codex` available in PATH)
|
|
18
27
|
- Docker (optional, only for Docker mode)
|
|
19
28
|
|
|
29
|
+
## Codex CLI setup
|
|
30
|
+
|
|
31
|
+
Install and verify:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g @openai/codex
|
|
35
|
+
codex --help
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Authenticate using the Codex CLI and/or create a profile in `~/.codex/config.toml`.
|
|
39
|
+
Follow the auth guide at https://developers.openai.com/codex/auth.
|
|
40
|
+
If you use profiles, pass `--profile` or set `codex.profile` in `ralph.config.yml`.
|
|
41
|
+
|
|
20
42
|
## Install
|
|
21
43
|
|
|
22
44
|
```bash
|
|
@@ -36,6 +58,45 @@ ralph-codex view
|
|
|
36
58
|
ralph-codex reset
|
|
37
59
|
```
|
|
38
60
|
|
|
61
|
+
## Demo (sample output)
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
$ ralph-codex plan "Add screenshot flow"
|
|
65
|
+
... (interactive prompts) ...
|
|
66
|
+
tasks.md written
|
|
67
|
+
$ ralph-codex run
|
|
68
|
+
... (loop output) ...
|
|
69
|
+
LOOP_COMPLETE
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Cookbook
|
|
73
|
+
|
|
74
|
+
### Basic flow
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
ralph-codex init
|
|
78
|
+
ralph-codex plan "Add screenshot flow for /demo" --output tasks.md
|
|
79
|
+
ralph-codex run --max-iterations 10
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Docker flow
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
ralph-codex docker
|
|
86
|
+
# Ensure docker.codex_install is set in ralph.config.yml
|
|
87
|
+
ralph-codex plan "Add screenshot flow for /demo"
|
|
88
|
+
ralph-codex run
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Low-touch automation (still interactive)
|
|
92
|
+
|
|
93
|
+
Use a prefilled config and pass flags to minimize prompts. This CLI still expects a TTY.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
ralph-codex plan "Add screenshot flow" --full-auto --reasoning low
|
|
97
|
+
ralph-codex run --full-auto --reasoning low
|
|
98
|
+
```
|
|
99
|
+
|
|
39
100
|
## Command reference
|
|
40
101
|
|
|
41
102
|
Use `--help` with any command to see its available options.
|
|
@@ -174,6 +235,7 @@ run:
|
|
|
174
235
|
max_iterations: 15
|
|
175
236
|
max_iteration_seconds: null
|
|
176
237
|
max_total_seconds: null
|
|
238
|
+
completion_promise: LOOP_COMPLETE
|
|
177
239
|
```
|
|
178
240
|
|
|
179
241
|
Codex settings quick guide:
|
|
@@ -187,6 +249,20 @@ Enable `plan.auto_detect_success_criteria` to add detected checks based on repo
|
|
|
187
249
|
|
|
188
250
|
CLI flags always override config values.
|
|
189
251
|
|
|
252
|
+
## Defaults
|
|
253
|
+
|
|
254
|
+
Defaults are from the template `ralph.config.yml`.
|
|
255
|
+
|
|
256
|
+
| Setting | Default | Details |
|
|
257
|
+
| --- | --- | --- |
|
|
258
|
+
| `plan.tasks_path` | `tasks.md` | Output path for generated tasks. |
|
|
259
|
+
| `plan.auto_detect_success_criteria` | `false` | Detect and suggest checks from the repo. |
|
|
260
|
+
| `run.tasks_path` | `tasks.md` | Input path for tasks during runs. |
|
|
261
|
+
| `run.max_iterations` | `15` | Max loop iterations before stopping. |
|
|
262
|
+
| `run.completion_promise` | `LOOP_COMPLETE` | Completion token printed by the loop. |
|
|
263
|
+
| `docker.enabled` | `false` | Enable Docker execution. |
|
|
264
|
+
| `docker.use_for_plan` | `false` | Run planning inside Docker too. |
|
|
265
|
+
|
|
190
266
|
## Docker mode
|
|
191
267
|
|
|
192
268
|
1. Run `ralph-codex docker` to pick a base image.
|
|
@@ -194,6 +270,20 @@ CLI flags always override config values.
|
|
|
194
270
|
3. Run `ralph-codex plan` and `ralph-codex run` as usual. Enable `docker.use_for_plan`
|
|
195
271
|
if you want planning to happen inside Docker as well.
|
|
196
272
|
|
|
273
|
+
Why Docker (especially with `danger-full-access`):
|
|
274
|
+
- Isolation: lets you grant broad permissions inside the container without exposing your host.
|
|
275
|
+
- Reproducibility: consistent OS/package stack across runs and teammates.
|
|
276
|
+
- Safer cleanup: delete the container/image to reset state.
|
|
277
|
+
|
|
278
|
+
Why `danger-full-access`:
|
|
279
|
+
- Fewer approval prompts for tooling that needs broad filesystem or network access.
|
|
280
|
+
- Works better for complex build/test flows that span many paths.
|
|
281
|
+
- Faster iterations when you trust the environment (best paired with Docker).
|
|
282
|
+
|
|
283
|
+
> **Warning**: Avoid `danger-full-access` on your host unless you fully trust the prompts,
|
|
284
|
+
> scripts, and dependencies. It grants broad access to your machine and can write
|
|
285
|
+
> outside the repo. Prefer Docker when you need this mode.
|
|
286
|
+
|
|
197
287
|
`Dockerfile.ralph` is generated automatically when Docker is enabled.
|
|
198
288
|
|
|
199
289
|
## Files created
|
|
@@ -207,12 +297,24 @@ CLI flags always override config values.
|
|
|
207
297
|
Codex output is colorized when stdout is a TTY. Plan uses spinners and run shows a task
|
|
208
298
|
progress bar when interactive. Set `NO_COLOR=1` to disable color styling.
|
|
209
299
|
|
|
300
|
+
## Exit codes
|
|
301
|
+
|
|
302
|
+
- `0` success.
|
|
303
|
+
- `1` invalid usage or runtime error.
|
|
304
|
+
|
|
210
305
|
## Troubleshooting
|
|
211
306
|
|
|
212
307
|
- `codex: command not found` -> install Codex CLI and ensure it is in PATH.
|
|
308
|
+
- Auth errors (401/403) -> authenticate Codex CLI or select a valid profile.
|
|
309
|
+
- `Missing tasks.md` -> run `ralph-codex plan` first or pass `--tasks`.
|
|
213
310
|
- Docker errors -> start Docker Desktop/Colima and retry.
|
|
214
|
-
-
|
|
311
|
+
- Config read errors -> verify `ralph.config.yml` path or pass `--config`.
|
|
312
|
+
|
|
313
|
+
## Changelog and license
|
|
314
|
+
|
|
315
|
+
- Changelog: `CHANGELOG.md`
|
|
316
|
+
- License: `LICENSE` (MIT)
|
|
215
317
|
|
|
216
|
-
##
|
|
318
|
+
## Privacy
|
|
217
319
|
|
|
218
|
-
|
|
320
|
+
ralph-codex does not add its own telemetry. It sends prompts and context to the Codex CLI you configure; follow your Codex policies and settings.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ralph-codex",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Codex-first Ralph-style planning and run loops",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"node": ">=18"
|
|
26
26
|
},
|
|
27
27
|
"packageManager": "npm@10.8.2",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:watch": "vitest"
|
|
31
|
+
},
|
|
28
32
|
"dependencies": {
|
|
29
33
|
"cli-progress": "^3.12.0",
|
|
30
34
|
"enquirer": "^2.4.1",
|
|
@@ -32,6 +36,9 @@
|
|
|
32
36
|
"ora": "^5.4.1",
|
|
33
37
|
"picocolors": "^1.0.0"
|
|
34
38
|
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"vitest": "^1.6.0"
|
|
41
|
+
},
|
|
35
42
|
"files": [
|
|
36
43
|
"bin",
|
|
37
44
|
"src",
|
|
@@ -130,9 +130,9 @@ _ralph_codex_completion_promise_list() {
|
|
|
130
130
|
|
|
131
131
|
_ralph_codex() {
|
|
132
132
|
local cur prev cmd
|
|
133
|
-
cur="
|
|
134
|
-
prev="
|
|
135
|
-
cmd="
|
|
133
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
134
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
135
|
+
cmd="\${COMP_WORDS[1]}"
|
|
136
136
|
|
|
137
137
|
local commands="init plan run revise refine view reset docker completion help"
|
|
138
138
|
local root_opts="--help -h --version -v"
|
|
@@ -174,6 +174,10 @@ _ralph_codex() {
|
|
|
174
174
|
COMPREPLY=( $(compgen -W "$tasks" -- "$cur") $(compgen -f -- "$cur") )
|
|
175
175
|
return 0
|
|
176
176
|
;;
|
|
177
|
+
--idea-file)
|
|
178
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
179
|
+
return 0
|
|
180
|
+
;;
|
|
177
181
|
--sandbox)
|
|
178
182
|
COMPREPLY=( $(compgen -W "read-only workspace-write danger-full-access" -- "$cur") )
|
|
179
183
|
return 0
|
|
@@ -199,7 +203,7 @@ _ralph_codex() {
|
|
|
199
203
|
return 0
|
|
200
204
|
;;
|
|
201
205
|
esac
|
|
202
|
-
local opts="--output --tasks --max-iterations --config --model -m --profile -p --sandbox --no-sandbox --ask-for-approval --full-auto --reasoning --detect-success-criteria --no-detect-success-criteria --help -h"
|
|
206
|
+
local opts="--output --tasks --idea-file --stdin --max-iterations --config --model -m --profile -p --sandbox --no-sandbox --ask-for-approval --full-auto --reasoning --detect-success-criteria --no-detect-success-criteria --help -h"
|
|
203
207
|
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
204
208
|
return 0
|
|
205
209
|
;;
|
|
@@ -525,6 +529,8 @@ _ralph_codex() {
|
|
|
525
529
|
_arguments \\
|
|
526
530
|
'--output[Write tasks to a custom file]:path:_ralph_codex_tasks' \\
|
|
527
531
|
'--tasks[Write tasks to a custom file]:path:_ralph_codex_tasks' \\
|
|
532
|
+
'--idea-file[Read idea from a markdown file]:file:_files' \\
|
|
533
|
+
'--stdin[Read idea from stdin]' \\
|
|
528
534
|
'--max-iterations[Max planning iterations]:number:' \\
|
|
529
535
|
'--config[Path to ralph.config.yml]:file:_ralph_codex_configs' \\
|
|
530
536
|
'(-m --model)'{-m,--model}'[Codex model]:model:_ralph_codex_models' \\
|
|
@@ -745,6 +751,8 @@ complete -c ralph-codex -n '__fish_seen_subcommand_from init' -l no-gitignore -d
|
|
|
745
751
|
|
|
746
752
|
complete -c ralph-codex -n '__fish_seen_subcommand_from plan' -l output -r -a '(__ralph_codex_tasks)' -d 'Write tasks to a custom file'
|
|
747
753
|
complete -c ralph-codex -n '__fish_seen_subcommand_from plan' -l tasks -r -a '(__ralph_codex_tasks)' -d 'Write tasks to a custom file'
|
|
754
|
+
complete -c ralph-codex -n '__fish_seen_subcommand_from plan' -l idea-file -r -a '(__fish_complete_path)' -d 'Read idea from a markdown file'
|
|
755
|
+
complete -c ralph-codex -n '__fish_seen_subcommand_from plan' -l stdin -d 'Read idea from stdin'
|
|
748
756
|
complete -c ralph-codex -n '__fish_seen_subcommand_from plan' -l max-iterations -r -d 'Max planning iterations'
|
|
749
757
|
complete -c ralph-codex -n '__fish_seen_subcommand_from plan' -l config -r -a '(__ralph_codex_configs)' -d 'Path to ralph.config.yml'
|
|
750
758
|
complete -c ralph-codex -n '__fish_seen_subcommand_from plan' -s m -l model -r -a '(__ralph_codex_models)' -d 'Codex model'
|
package/src/commands/docker.js
CHANGED
|
@@ -8,6 +8,7 @@ const { Confirm } = enquirer;
|
|
|
8
8
|
|
|
9
9
|
const root = process.cwd();
|
|
10
10
|
const defaultConfigPath = path.join(root, "ralph.config.yml");
|
|
11
|
+
const isTestMode = process.env.RALPH_TEST_MODE === "1";
|
|
11
12
|
|
|
12
13
|
const argv = process.argv.slice(2);
|
|
13
14
|
let configPath = null;
|
|
@@ -122,12 +123,12 @@ function runCodex(prompt, codexConfig) {
|
|
|
122
123
|
|
|
123
124
|
function buildPrompt(nodeVersion) {
|
|
124
125
|
const nodeLine = nodeVersion ? `Node version: ${nodeVersion}` : "Node 20+";
|
|
125
|
-
return `
|
|
126
|
-
|
|
126
|
+
return `Find the best Docker base image for this project.
|
|
127
|
+
Consider that it needs to run npm scripts, native modules, and should avoid Alpine.
|
|
127
128
|
${nodeLine}
|
|
128
129
|
|
|
129
|
-
|
|
130
|
-
BASE_IMAGE: <
|
|
130
|
+
Respond with a single line in this format:
|
|
131
|
+
BASE_IMAGE: <image>`;
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
async function main() {
|
|
@@ -148,13 +149,13 @@ async function main() {
|
|
|
148
149
|
process.exit(1);
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
152
|
+
const approved = isTestMode
|
|
153
|
+
? true
|
|
154
|
+
: await new Confirm({
|
|
155
|
+
name: "confirm",
|
|
156
|
+
message: `Set docker.enabled=true and base_image=${baseImage}?`,
|
|
157
|
+
initial: true,
|
|
158
|
+
}).run();
|
|
158
159
|
if (!approved) {
|
|
159
160
|
process.stdout.write("Aborted by user.\n");
|
|
160
161
|
process.exit(1);
|
package/src/commands/init.js
CHANGED
|
@@ -7,6 +7,7 @@ const { AutoComplete, Confirm, Input, Toggle } = enquirer;
|
|
|
7
7
|
|
|
8
8
|
const root = process.cwd();
|
|
9
9
|
const argv = process.argv.slice(2);
|
|
10
|
+
const isTestMode = process.env.RALPH_TEST_MODE === "1";
|
|
10
11
|
|
|
11
12
|
let force = false;
|
|
12
13
|
let configPath = null;
|
|
@@ -42,6 +43,7 @@ const targetPath = configPath
|
|
|
42
43
|
: path.join(root, "ralph.config.yml");
|
|
43
44
|
|
|
44
45
|
async function confirmOverwrite() {
|
|
46
|
+
if (isTestMode) return true;
|
|
45
47
|
const confirm = new Confirm({
|
|
46
48
|
name: "overwrite",
|
|
47
49
|
message: `Overwrite existing ${path.relative(root, targetPath)}?`,
|
|
@@ -224,6 +226,16 @@ async function promptModelChoice() {
|
|
|
224
226
|
}
|
|
225
227
|
|
|
226
228
|
async function collectCodexConfig() {
|
|
229
|
+
if (isTestMode) {
|
|
230
|
+
return {
|
|
231
|
+
model: null,
|
|
232
|
+
profile: null,
|
|
233
|
+
sandbox: null,
|
|
234
|
+
ask_for_approval: null,
|
|
235
|
+
full_auto: false,
|
|
236
|
+
model_reasoning_effort: null,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
227
239
|
const model = await promptModelChoice();
|
|
228
240
|
const profile = await promptOptionalInput(
|
|
229
241
|
"Codex CLI profile (optional; leave blank to use Codex default)",
|
|
@@ -359,13 +371,15 @@ async function main() {
|
|
|
359
371
|
|
|
360
372
|
const content = fs.readFileSync(templatePath, "utf8");
|
|
361
373
|
const codexConfig = await collectCodexConfig();
|
|
362
|
-
const useDocker =
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
374
|
+
const useDocker = isTestMode
|
|
375
|
+
? process.env.RALPH_TEST_USE_DOCKER === "1"
|
|
376
|
+
: await new Toggle({
|
|
377
|
+
name: "use_docker",
|
|
378
|
+
message: "Use Docker for the loop? (adds a docker section)",
|
|
379
|
+
enabled: "Yes",
|
|
380
|
+
disabled: "No",
|
|
381
|
+
initial: false,
|
|
382
|
+
}).run();
|
|
369
383
|
|
|
370
384
|
let updated = content;
|
|
371
385
|
updated = setYamlValue(updated, "model", codexConfig.model);
|
package/src/commands/plan.js
CHANGED
|
@@ -10,10 +10,13 @@ const { AutoComplete, Confirm, Editor, Input, MultiSelect } = enquirer;
|
|
|
10
10
|
|
|
11
11
|
const root = process.cwd();
|
|
12
12
|
const agentDir = path.join(root, ".ralph");
|
|
13
|
+
const isTestMode = process.env.RALPH_TEST_MODE === "1";
|
|
13
14
|
|
|
14
15
|
const argv = process.argv.slice(2);
|
|
15
16
|
let maxIterations = "1";
|
|
16
17
|
let tasksPath = "tasks.md";
|
|
18
|
+
let ideaFile = null;
|
|
19
|
+
let readStdin = false;
|
|
17
20
|
let noSandbox = false;
|
|
18
21
|
let sandbox = null;
|
|
19
22
|
let fullAuto = false;
|
|
@@ -27,6 +30,7 @@ let autoDetectSuccessCriteria = null;
|
|
|
27
30
|
let reasoningChoice;
|
|
28
31
|
let showHelp = false;
|
|
29
32
|
const ideaParts = [];
|
|
33
|
+
let idea = "";
|
|
30
34
|
|
|
31
35
|
for (let i = 0; i < argv.length; i += 1) {
|
|
32
36
|
const arg = argv[i];
|
|
@@ -49,6 +53,20 @@ for (let i = 0; i < argv.length; i += 1) {
|
|
|
49
53
|
i += 1;
|
|
50
54
|
continue;
|
|
51
55
|
}
|
|
56
|
+
if (arg === "--idea-file") {
|
|
57
|
+
const value = argv[i + 1];
|
|
58
|
+
if (!value || (value.startsWith("-") && value !== "-")) {
|
|
59
|
+
console.error("Missing --idea-file <path>.");
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
ideaFile = value;
|
|
63
|
+
i += 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (arg === "--stdin") {
|
|
67
|
+
readStdin = true;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
52
70
|
if (arg === "--no-sandbox") {
|
|
53
71
|
noSandbox = true;
|
|
54
72
|
continue;
|
|
@@ -109,6 +127,8 @@ function printHelp() {
|
|
|
109
127
|
`${colors.yellow("Options:")}\n` +
|
|
110
128
|
` ${colors.green("--output <path>")} Write tasks to a custom file (alias of --tasks)\n` +
|
|
111
129
|
` ${colors.green("--tasks <path>")} Write tasks to a custom file (default: tasks.md)\n` +
|
|
130
|
+
` ${colors.green("--idea-file <path>")} Read idea from a markdown file ('-' for stdin)\n` +
|
|
131
|
+
` ${colors.green("--stdin")} Read idea from stdin (paste then Ctrl-D)\n` +
|
|
112
132
|
` ${colors.green("--max-iterations <n>")} Max planning iterations (default: 1)\n` +
|
|
113
133
|
` ${colors.green("--config <path>")} Path to ralph.config.yml\n` +
|
|
114
134
|
` ${colors.green("--model <name>, -m")} Codex model\n` +
|
|
@@ -129,16 +149,24 @@ if (showHelp) {
|
|
|
129
149
|
process.exit(0);
|
|
130
150
|
}
|
|
131
151
|
|
|
132
|
-
const
|
|
152
|
+
const promptPath = path.join(agentDir, "ralph-plan-prompt.md");
|
|
133
153
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
)
|
|
138
|
-
|
|
154
|
+
function readIdeaFromStdin() {
|
|
155
|
+
try {
|
|
156
|
+
return fs.readFileSync(0, "utf8");
|
|
157
|
+
} catch (_) {
|
|
158
|
+
return "";
|
|
159
|
+
}
|
|
139
160
|
}
|
|
140
161
|
|
|
141
|
-
|
|
162
|
+
function readIdeaFromFile(filePath) {
|
|
163
|
+
const resolved = path.resolve(root, filePath);
|
|
164
|
+
if (!fs.existsSync(resolved)) {
|
|
165
|
+
console.error(`Missing idea file: ${filePath}`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
return fs.readFileSync(resolved, "utf8");
|
|
169
|
+
}
|
|
142
170
|
function loadConfig(configFilePath) {
|
|
143
171
|
if (!configFilePath) return {};
|
|
144
172
|
if (!fs.existsSync(configFilePath)) return {};
|
|
@@ -175,7 +203,7 @@ function resolveDockerConfig(config) {
|
|
|
175
203
|
? dockerConfig.pip_packages
|
|
176
204
|
: [],
|
|
177
205
|
useForPlan: Boolean(dockerConfig.use_for_plan),
|
|
178
|
-
tty: dockerConfig.tty ??
|
|
206
|
+
tty: dockerConfig.tty ?? false,
|
|
179
207
|
};
|
|
180
208
|
}
|
|
181
209
|
|
|
@@ -675,6 +703,7 @@ Context scan (read-only):
|
|
|
675
703
|
pyproject.toml, requirements.txt, go.mod, Cargo.toml, pom.xml, build.gradle, Makefile,
|
|
676
704
|
.nvmrc, Dockerfile, etc. Only inspect files that exist.
|
|
677
705
|
- Use this context to infer file locations, tooling, and sensible commands.
|
|
706
|
+
- You may use read-only commands like ls, rg, and cat to inspect files.
|
|
678
707
|
|
|
679
708
|
Requirements:
|
|
680
709
|
- If there are open questions, ask them first and do not write ${tasksPath}.
|
|
@@ -693,13 +722,18 @@ Requirements:
|
|
|
693
722
|
- Tasks must be atomic, ordered, and verifiable. Include exact file paths,
|
|
694
723
|
commands to run (if any), and expected outcomes. Avoid vague verbs like "handle" or "improve".
|
|
695
724
|
- Keep scope minimal: avoid refactors unless required by the idea or to unblock tasks.
|
|
725
|
+
- Use this exact section order and headings in ${tasksPath}:
|
|
726
|
+
1) # Tasks
|
|
727
|
+
2) ## Assumptions (only if needed)
|
|
728
|
+
3) ## Success criteria
|
|
729
|
+
4) ## Required tools
|
|
696
730
|
${successCriteriaBlock}
|
|
697
731
|
- Include a "Required tools" section using this exact format:
|
|
698
732
|
- \`- apt: <comma-separated packages or none>\`
|
|
699
733
|
- \`- npm: <comma-separated packages or none>\`
|
|
700
734
|
- \`- pip: <comma-separated packages or none>\`
|
|
701
735
|
- Do not edit any files other than ${tasksPath}.
|
|
702
|
-
- Do not run commands, tests, or start dev servers during planning.
|
|
736
|
+
- Do not run write commands, tests, installs, or start dev servers during planning.
|
|
703
737
|
|
|
704
738
|
When done, output exactly: LOOP_COMPLETE
|
|
705
739
|
`;
|
|
@@ -856,6 +890,13 @@ async function selectSuccessCriteria(
|
|
|
856
890
|
standardChoices,
|
|
857
891
|
detectedChoices = []
|
|
858
892
|
) {
|
|
893
|
+
if (isTestMode) {
|
|
894
|
+
const safeDefaults =
|
|
895
|
+
defaultCriteria && defaultCriteria.length > 0
|
|
896
|
+
? defaultCriteria
|
|
897
|
+
: standardChoices;
|
|
898
|
+
return { mode: "manual", criteria: safeDefaults || [] };
|
|
899
|
+
}
|
|
859
900
|
const autoChoiceValue = "__auto__";
|
|
860
901
|
const customChoiceValue = "__custom__";
|
|
861
902
|
const defaults =
|
|
@@ -949,6 +990,7 @@ async function selectSuccessCriteria(
|
|
|
949
990
|
}
|
|
950
991
|
|
|
951
992
|
async function confirmPlan(tasksFile) {
|
|
993
|
+
if (isTestMode) return true;
|
|
952
994
|
const content = fs.readFileSync(tasksFile, "utf8");
|
|
953
995
|
process.stdout.write(`\n${colors.cyan("--- Proposed tasks.md ---")}\n\n`);
|
|
954
996
|
process.stdout.write(content);
|
|
@@ -972,6 +1014,7 @@ async function readRevisionFeedback() {
|
|
|
972
1014
|
}
|
|
973
1015
|
|
|
974
1016
|
async function confirmResetState(tasksFilePath, agentPath) {
|
|
1017
|
+
if (isTestMode) return false;
|
|
975
1018
|
const hasTasks = fs.existsSync(tasksFilePath);
|
|
976
1019
|
const hasAgent = fs.existsSync(agentPath);
|
|
977
1020
|
if (!hasTasks && !hasAgent) return false;
|
|
@@ -994,6 +1037,23 @@ function resetState(tasksFilePath, agentPath) {
|
|
|
994
1037
|
}
|
|
995
1038
|
|
|
996
1039
|
async function main() {
|
|
1040
|
+
const ideaFromArgs = ideaParts.join(" ").trim();
|
|
1041
|
+
if (ideaFile) {
|
|
1042
|
+
idea = ideaFile === "-" ? readIdeaFromStdin() : readIdeaFromFile(ideaFile);
|
|
1043
|
+
} else if (readStdin || (!ideaFromArgs && !process.stdin.isTTY)) {
|
|
1044
|
+
idea = readIdeaFromStdin();
|
|
1045
|
+
} else {
|
|
1046
|
+
idea = ideaFromArgs;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
idea = String(idea || "").trim();
|
|
1050
|
+
if (!idea) {
|
|
1051
|
+
console.error(
|
|
1052
|
+
'Usage: ralph-codex plan "<idea>" [--idea-file <path>] [--stdin] [--output <path>] [--tasks <path>] [--max-iterations <n>]',
|
|
1053
|
+
);
|
|
1054
|
+
process.exit(1);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
997
1057
|
const resolvedConfigPath = configPath || path.join(root, "ralph.config.yml");
|
|
998
1058
|
const config = loadConfig(resolvedConfigPath);
|
|
999
1059
|
const codexConfig = config?.codex || {};
|
package/src/commands/revise.js
CHANGED
|
@@ -4,6 +4,12 @@ import path from "path";
|
|
|
4
4
|
import enquirer from "enquirer";
|
|
5
5
|
import yaml from "js-yaml";
|
|
6
6
|
import { colors, createLogStyler, createSpinner } from "../ui/terminal.js";
|
|
7
|
+
import {
|
|
8
|
+
diffCriteria,
|
|
9
|
+
diffTasks,
|
|
10
|
+
parseSuccessCriteria,
|
|
11
|
+
parseTasks,
|
|
12
|
+
} from "../lib/tasks.js";
|
|
7
13
|
|
|
8
14
|
const { AutoComplete, Confirm, Editor, Input } = enquirer;
|
|
9
15
|
|
|
@@ -223,91 +229,6 @@ async function readFeedback(promptMessage) {
|
|
|
223
229
|
return input.run();
|
|
224
230
|
}
|
|
225
231
|
|
|
226
|
-
function parseTasks(content) {
|
|
227
|
-
const tasks = [];
|
|
228
|
-
const lines = content.split(/\r?\n/);
|
|
229
|
-
for (const line of lines) {
|
|
230
|
-
const match = line.match(/^\s*[-*]\s+\[([ x~])\]\s+(.*)$/);
|
|
231
|
-
if (!match) continue;
|
|
232
|
-
const statusToken = match[1].toLowerCase();
|
|
233
|
-
const status =
|
|
234
|
-
statusToken === "x" ? "done" : statusToken === "~" ? "blocked" : "pending";
|
|
235
|
-
tasks.push({
|
|
236
|
-
status,
|
|
237
|
-
text: match[2].trim(),
|
|
238
|
-
raw: line.trim(),
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
return tasks;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function normalizeTaskText(text) {
|
|
245
|
-
return String(text || "")
|
|
246
|
-
.toLowerCase()
|
|
247
|
-
.replace(/\s+/g, " ")
|
|
248
|
-
.trim();
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function parseSuccessCriteria(content) {
|
|
252
|
-
const lines = content.split(/\r?\n/);
|
|
253
|
-
let start = -1;
|
|
254
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
255
|
-
if (/^(#+\s*)?success criteria\b/i.test(lines[i].trim())) {
|
|
256
|
-
start = i;
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
if (start === -1) return [];
|
|
261
|
-
const items = [];
|
|
262
|
-
for (let i = start + 1; i < lines.length; i += 1) {
|
|
263
|
-
const line = lines[i].trim();
|
|
264
|
-
if (!line) continue;
|
|
265
|
-
if (/^#+\s+/.test(line)) break;
|
|
266
|
-
if (line.startsWith("- ")) items.push(line.slice(2).trim());
|
|
267
|
-
if (line.startsWith("* ")) items.push(line.slice(2).trim());
|
|
268
|
-
}
|
|
269
|
-
return items;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function diffTasks(oldTasks, newTasks) {
|
|
273
|
-
const oldSet = new Set(oldTasks.map((task) => normalizeTaskText(task.text)));
|
|
274
|
-
const newSet = new Set(newTasks.map((task) => normalizeTaskText(task.text)));
|
|
275
|
-
|
|
276
|
-
const additions = newTasks.filter(
|
|
277
|
-
(task) => !oldSet.has(normalizeTaskText(task.text))
|
|
278
|
-
);
|
|
279
|
-
const removals = oldTasks.filter(
|
|
280
|
-
(task) => !newSet.has(normalizeTaskText(task.text))
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
const modified = [];
|
|
284
|
-
const compareCount = Math.min(oldTasks.length, newTasks.length);
|
|
285
|
-
for (let i = 0; i < compareCount; i += 1) {
|
|
286
|
-
const before = oldTasks[i];
|
|
287
|
-
const after = newTasks[i];
|
|
288
|
-
if (
|
|
289
|
-
before.status !== after.status ||
|
|
290
|
-
normalizeTaskText(before.text) !== normalizeTaskText(after.text)
|
|
291
|
-
) {
|
|
292
|
-
modified.push({
|
|
293
|
-
index: i + 1,
|
|
294
|
-
before,
|
|
295
|
-
after,
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return { additions, removals, modified };
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function diffCriteria(oldCriteria, newCriteria) {
|
|
304
|
-
const oldSet = new Set(oldCriteria);
|
|
305
|
-
const newSet = new Set(newCriteria);
|
|
306
|
-
const added = newCriteria.filter((item) => !oldSet.has(item));
|
|
307
|
-
const removed = oldCriteria.filter((item) => !newSet.has(item));
|
|
308
|
-
return { added, removed };
|
|
309
|
-
}
|
|
310
|
-
|
|
311
232
|
function renderChanges(changes) {
|
|
312
233
|
if (changes.additions.length > 0) {
|
|
313
234
|
process.stdout.write(`${colors.cyan("Proposed new tasks:")}\n`);
|
package/src/commands/run.js
CHANGED
|
@@ -278,7 +278,7 @@ function resolveDockerConfig(config) {
|
|
|
278
278
|
fixAttempts: Number(dockerConfig.fix_attempts || 2),
|
|
279
279
|
fixUseHost: dockerConfig.fix_use_host !== false,
|
|
280
280
|
fixLog: dockerConfig.fix_log || ".ralph/docker-build.log",
|
|
281
|
-
tty: dockerConfig.tty ??
|
|
281
|
+
tty: dockerConfig.tty ?? false,
|
|
282
282
|
cleanup: dockerConfig.cleanup || "none",
|
|
283
283
|
};
|
|
284
284
|
}
|
|
@@ -356,6 +356,8 @@ ${result.output}
|
|
|
356
356
|
Constraints:
|
|
357
357
|
- Only edit ${dockerConfig.dockerfile}
|
|
358
358
|
- Do not change other files
|
|
359
|
+
- Keep the existing base image unless the error requires changing it
|
|
360
|
+
- Do not add new dependencies unless required by the error
|
|
359
361
|
- Do not ask questions
|
|
360
362
|
- Output exactly: LOOP_COMPLETE
|
|
361
363
|
`;
|
|
@@ -1142,8 +1144,33 @@ async function main() {
|
|
|
1142
1144
|
}
|
|
1143
1145
|
|
|
1144
1146
|
if (hasCompletion(result.output)) {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
+
const completionProgress = getTaskProgress(tasksFile);
|
|
1148
|
+
if (
|
|
1149
|
+
completionProgress.total > 0 &&
|
|
1150
|
+
completionProgress.completed === completionProgress.total
|
|
1151
|
+
) {
|
|
1152
|
+
completed = true;
|
|
1153
|
+
break;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
const pendingCount = Math.max(
|
|
1157
|
+
0,
|
|
1158
|
+
completionProgress.total -
|
|
1159
|
+
completionProgress.completed -
|
|
1160
|
+
completionProgress.blocked,
|
|
1161
|
+
);
|
|
1162
|
+
const reason =
|
|
1163
|
+
completionProgress.total === 0
|
|
1164
|
+
? "no tasks detected"
|
|
1165
|
+
: `${pendingCount} pending, ${completionProgress.blocked} blocked`;
|
|
1166
|
+
notes.push(
|
|
1167
|
+
`Completion token received but tasks remain incomplete (${reason}).`,
|
|
1168
|
+
);
|
|
1169
|
+
process.stdout.write(
|
|
1170
|
+
`${colors.yellow(
|
|
1171
|
+
"Completion token received but tasks remain incomplete; continuing.",
|
|
1172
|
+
)}\n`,
|
|
1173
|
+
);
|
|
1147
1174
|
}
|
|
1148
1175
|
|
|
1149
1176
|
if (iterationTimedOut) {
|
package/src/commands/view.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import yaml from "js-yaml";
|
|
4
4
|
import { colors } from "../ui/terminal.js";
|
|
5
|
+
import { parseSuccessCriteria, parseTasks } from "../lib/tasks.js";
|
|
5
6
|
|
|
6
7
|
const root = process.cwd();
|
|
7
8
|
const argv = process.argv.slice(2);
|
|
@@ -156,21 +157,6 @@ function formatStatus(status) {
|
|
|
156
157
|
return { raw, display: colors.gray(raw) };
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
function parseTasks(content) {
|
|
160
|
-
const tasks = [];
|
|
161
|
-
const lines = content.split(/\r?\n/);
|
|
162
|
-
let index = 0;
|
|
163
|
-
for (const line of lines) {
|
|
164
|
-
const match = line.match(/^\s*[-*]\s+\[([ x~])\]\s+(.*)$/);
|
|
165
|
-
if (!match) continue;
|
|
166
|
-
index += 1;
|
|
167
|
-
const statusToken = match[1].toLowerCase();
|
|
168
|
-
const status =
|
|
169
|
-
statusToken === "x" ? "done" : statusToken === "~" ? "blocked" : "pending";
|
|
170
|
-
tasks.push({ index, status, text: match[2].trim() });
|
|
171
|
-
}
|
|
172
|
-
return tasks;
|
|
173
|
-
}
|
|
174
160
|
|
|
175
161
|
function summarizeTasks(tasks) {
|
|
176
162
|
const total = tasks.length;
|
|
@@ -186,26 +172,6 @@ function filterTasks(tasks, filter) {
|
|
|
186
172
|
return tasks.filter((task) => task.status === filter);
|
|
187
173
|
}
|
|
188
174
|
|
|
189
|
-
function extractSuccessCriteria(content) {
|
|
190
|
-
const lines = content.split(/\r?\n/);
|
|
191
|
-
let start = -1;
|
|
192
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
193
|
-
if (/^(#+\s*)?success criteria\b/i.test(lines[i].trim())) {
|
|
194
|
-
start = i;
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
if (start === -1) return [];
|
|
199
|
-
const items = [];
|
|
200
|
-
for (let i = start + 1; i < lines.length; i += 1) {
|
|
201
|
-
const line = lines[i].trim();
|
|
202
|
-
if (!line) continue;
|
|
203
|
-
if (/^#+\s+/.test(line)) break;
|
|
204
|
-
if (line.startsWith("- ")) items.push(line.slice(2).trim());
|
|
205
|
-
if (line.startsWith("* ")) items.push(line.slice(2).trim());
|
|
206
|
-
}
|
|
207
|
-
return items;
|
|
208
|
-
}
|
|
209
175
|
|
|
210
176
|
function getLastBlocker(logPath) {
|
|
211
177
|
if (!fs.existsSync(logPath)) return "";
|
|
@@ -339,6 +305,7 @@ function getConfigRows(config) {
|
|
|
339
305
|
use_for_plan: false,
|
|
340
306
|
base_image: "node:20-bullseye",
|
|
341
307
|
codex_install: "",
|
|
308
|
+
tty: false,
|
|
342
309
|
},
|
|
343
310
|
plan: {
|
|
344
311
|
tasks_path: "tasks.md",
|
|
@@ -468,7 +435,7 @@ function renderOnce({ allowMissingTasks }) {
|
|
|
468
435
|
const summary = summarizeTasks(allTasks);
|
|
469
436
|
const filtered = filterTasks(allTasks, only);
|
|
470
437
|
const sliced = limit > 0 ? filtered.slice(0, limit) : filtered;
|
|
471
|
-
const criteria =
|
|
438
|
+
const criteria = parseSuccessCriteria(tasksContent);
|
|
472
439
|
|
|
473
440
|
tasksData = {
|
|
474
441
|
path: resolvedTasksPath,
|
package/src/lib/tasks.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
function parseTasks(content) {
|
|
2
|
+
const tasks = [];
|
|
3
|
+
const lines = String(content || "").split(/\r?\n/);
|
|
4
|
+
let index = 0;
|
|
5
|
+
|
|
6
|
+
for (const line of lines) {
|
|
7
|
+
const match = line.match(/^\s*[-*]\s+\[([ x~])\]\s+(.*)$/);
|
|
8
|
+
if (!match) continue;
|
|
9
|
+
index += 1;
|
|
10
|
+
const statusToken = match[1].toLowerCase();
|
|
11
|
+
const status =
|
|
12
|
+
statusToken === "x" ? "done" : statusToken === "~" ? "blocked" : "pending";
|
|
13
|
+
tasks.push({
|
|
14
|
+
index,
|
|
15
|
+
status,
|
|
16
|
+
text: match[2].trim(),
|
|
17
|
+
raw: line.trim(),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return tasks;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseSuccessCriteria(content) {
|
|
25
|
+
const lines = String(content || "").split(/\r?\n/);
|
|
26
|
+
let start = -1;
|
|
27
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
28
|
+
if (/^(#+\s*)?success criteria\b/i.test(lines[i].trim())) {
|
|
29
|
+
start = i;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (start === -1) return [];
|
|
34
|
+
const items = [];
|
|
35
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
36
|
+
const line = lines[i].trim();
|
|
37
|
+
if (!line) continue;
|
|
38
|
+
if (/^#+\s+/.test(line)) break;
|
|
39
|
+
if (line.startsWith("- ")) items.push(line.slice(2).trim());
|
|
40
|
+
if (line.startsWith("* ")) items.push(line.slice(2).trim());
|
|
41
|
+
}
|
|
42
|
+
return items;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizeTaskText(text) {
|
|
46
|
+
return String(text || "")
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.replace(/\s+/g, " ")
|
|
49
|
+
.trim();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function diffTasks(oldTasks, newTasks) {
|
|
53
|
+
const oldSet = new Set(oldTasks.map((task) => normalizeTaskText(task.text)));
|
|
54
|
+
const newSet = new Set(newTasks.map((task) => normalizeTaskText(task.text)));
|
|
55
|
+
|
|
56
|
+
const additions = newTasks.filter(
|
|
57
|
+
(task) => !oldSet.has(normalizeTaskText(task.text))
|
|
58
|
+
);
|
|
59
|
+
const removals = oldTasks.filter(
|
|
60
|
+
(task) => !newSet.has(normalizeTaskText(task.text))
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const modified = [];
|
|
64
|
+
const compareCount = Math.min(oldTasks.length, newTasks.length);
|
|
65
|
+
for (let i = 0; i < compareCount; i += 1) {
|
|
66
|
+
const before = oldTasks[i];
|
|
67
|
+
const after = newTasks[i];
|
|
68
|
+
if (
|
|
69
|
+
before.status !== after.status ||
|
|
70
|
+
normalizeTaskText(before.text) !== normalizeTaskText(after.text)
|
|
71
|
+
) {
|
|
72
|
+
modified.push({
|
|
73
|
+
index: i + 1,
|
|
74
|
+
before,
|
|
75
|
+
after,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { additions, removals, modified };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function diffCriteria(oldCriteria, newCriteria) {
|
|
84
|
+
const oldSet = new Set(oldCriteria);
|
|
85
|
+
const newSet = new Set(newCriteria);
|
|
86
|
+
const added = newCriteria.filter((item) => !oldSet.has(item));
|
|
87
|
+
const removed = oldCriteria.filter((item) => !newSet.has(item));
|
|
88
|
+
return { added, removed };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
diffCriteria,
|
|
93
|
+
diffTasks,
|
|
94
|
+
normalizeTaskText,
|
|
95
|
+
parseSuccessCriteria,
|
|
96
|
+
parseTasks,
|
|
97
|
+
};
|
|
@@ -18,7 +18,7 @@ docker:
|
|
|
18
18
|
codex_install: npm install -g @openai/codex
|
|
19
19
|
codex_home: .ralph/codex # Writable codex home inside the repo
|
|
20
20
|
mount_codex_config: true # Seed codex_home from host ~/.codex if empty
|
|
21
|
-
tty:
|
|
21
|
+
tty: false # auto | true | false
|
|
22
22
|
pass_env:
|
|
23
23
|
- OPENAI_API_KEY
|
|
24
24
|
apt_packages:
|