wile 0.6.8 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,7 @@ This creates:
26
26
  Set `WILE_REPO_SOURCE=local` in `.wile/secrets/.env` to run against the current directory without GitHub.
27
27
  When `WILE_REPO_SOURCE=local`, GitHub credentials are optional.
28
28
  Set `WILE_MAX_ITERATIONS` in `.wile/secrets/.env` to change the default loop limit (default: 25).
29
- Set `CODING_AGENT=OC` to use OpenCode (OpenRouter), `CODING_AGENT=GC` to use Gemini CLI, otherwise `CODING_AGENT=CC` uses Claude Code.
29
+ Set `CODING_AGENT=CX` to use Codex CLI, `CODING_AGENT=OC` to use OpenCode (OpenRouter), `CODING_AGENT=GC` to use Gemini CLI, otherwise `CODING_AGENT=CC` uses Claude Code.
30
30
 
31
31
  ## Run Wile
32
32
 
@@ -9,6 +9,7 @@
9
9
  # - CC = Claude Code (default)
10
10
  # - GC = Gemini CLI (Google account OAuth)
11
11
  # - OC = OpenCode (OpenRouter)
12
+ # - CX = Codex CLI (ChatGPT subscription)
12
13
  CODING_AGENT=CC
13
14
 
14
15
  # =============================================================================
@@ -47,6 +48,19 @@ CC_CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxxxx
47
48
  # Get one at: https://aistudio.google.com/app/apikey
48
49
  # GEMINI_API_KEY=your-key
49
50
 
51
+ # =============================================================================
52
+ # REQUIRED - Codex CLI Authentication (CX only, choose ONE)
53
+ # =============================================================================
54
+
55
+ # OPTION 1: ChatGPT login (RECOMMENDED - uses your ChatGPT subscription)
56
+ # Run: codex login (or codex login --device-auth)
57
+ # Then base64 encode ~/.codex/auth.json and paste here
58
+ # CODEX_AUTH_JSON_B64=eyJ...
59
+
60
+ # OPTION 2: API key (uses API credits - pay per token)
61
+ # Get one at: https://platform.openai.com/api-keys
62
+ # CODEX_API_KEY=sk-openai-xxxxx
63
+
50
64
  # =============================================================================
51
65
  # GitHub Authentication (required when WILE_REPO_SOURCE=github)
52
66
  # =============================================================================
@@ -83,6 +97,9 @@ CC_CLAUDE_MODEL=sonnet
83
97
  # Gemini model name (default: auto-gemini-3)
84
98
  # GEMINI_MODEL=auto-gemini-3
85
99
 
100
+ # Codex model name (defaults to Codex CLI default)
101
+ # CODEX_MODEL=gpt-5.1-codex
102
+
86
103
  # =============================================================================
87
104
  # PROJECT ENVIRONMENT VARIABLES
88
105
  # =============================================================================
@@ -63,11 +63,14 @@ RUN npm install -g opencode-ai@latest
63
63
  # https://www.npmjs.com/package/@google/gemini-cli
64
64
  RUN npm install -g @google/gemini-cli
65
65
 
66
+ # Install Codex CLI
67
+ # https://www.npmjs.com/package/@openai/codex
68
+ RUN npm install -g @openai/codex
69
+
66
70
  # CC_ANTHROPIC_API_KEY is passed at runtime via environment variable (mapped to ANTHROPIC_API_KEY in entrypoint)
67
71
  # Usage: claude --dangerously-skip-permissions
68
72
 
69
- # Install Playwright with Chromium for browser testing
70
- # Dependencies for headless Chromium
73
+ # Install Chromium dependencies for Playwright
71
74
  RUN apt-get update && apt-get install -y \
72
75
  libnss3 \
73
76
  libnspr4 \
@@ -87,11 +90,10 @@ RUN apt-get update && apt-get install -y \
87
90
  libatspi2.0-0 \
88
91
  && rm -rf /var/lib/apt/lists/*
89
92
 
90
- # Install Playwright globally and download Chromium to shared location
93
+ # Download Chromium using the Playwright core version bundled with agent-browser
91
94
  ENV PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers
92
- RUN npm install -g playwright \
93
- && mkdir -p /opt/playwright-browsers \
94
- && npx playwright install chromium \
95
+ RUN mkdir -p /opt/playwright-browsers \
96
+ && node /opt/agent-browser/node_modules/playwright-core/cli.js install chromium \
95
97
  && chmod -R 755 /opt/playwright-browsers
96
98
 
97
99
  # Copy scripts
@@ -110,13 +110,15 @@ For UI work, tell the agent how to verify:
110
110
 
111
111
  | Variable | Required | Description |
112
112
  |----------|----------|-------------|
113
- | `CODING_AGENT` | No | `CC` (Claude Code, default), `GC` (Gemini CLI), or `OC` (OpenCode via OpenRouter) |
113
+ | `CODING_AGENT` | No | `CC` (Claude Code, default), `GC` (Gemini CLI), `OC` (OpenCode via OpenRouter), or `CX` (Codex CLI) |
114
114
  | `CC_CLAUDE_CODE_OAUTH_TOKEN` | Yes* | OAuth token from `claude setup-token` (uses Pro/Max subscription) |
115
115
  | `CC_ANTHROPIC_API_KEY` | Yes* | API key (uses API credits - alternative to OAuth) |
116
116
  | `GEMINI_OAUTH_CREDS_B64` | Yes (GC)* | Base64 OAuth creds from `~/.gemini/oauth_creds.json` (uses Google account) |
117
117
  | `GEMINI_API_KEY` | Yes (GC)* | Gemini API key (uses API credits - alternative to OAuth) |
118
118
  | `OC_OPENROUTER_API_KEY` | Yes (OC) | OpenRouter API key for OpenCode |
119
119
  | `OC_MODEL` | Yes (OC) | OpenRouter model id (set `glm-4.7` to target `openrouter/z-ai/glm-4.7`) |
120
+ | `CODEX_AUTH_JSON_B64` | Yes (CX)* | Base64 of `~/.codex/auth.json` from `codex login` (uses ChatGPT subscription) |
121
+ | `CODEX_API_KEY` | Yes (CX)* | OpenAI API key (uses API credits - alternative to auth.json) |
120
122
  | `GEMINI_MODEL` | No (GC) | Gemini model name (default: `auto-gemini-3`) |
121
123
  | `WILE_REPO_SOURCE` | No | `github` (default) or `local` |
122
124
  | `GITHUB_TOKEN` | Yes (github) | GitHub PAT with repo access |
@@ -124,9 +126,11 @@ For UI work, tell the agent how to verify:
124
126
  | `BRANCH_NAME` | Yes (github) | Branch to work on |
125
127
  | `MAX_ITERATIONS` | No | Max loops (default: 25) |
126
128
  | `CC_CLAUDE_MODEL` | No | Claude model alias/name (default: sonnet) |
129
+ | `CODEX_MODEL` | No (CX) | Codex model name (defaults to Codex CLI default) |
127
130
 
128
131
  *Either `CC_CLAUDE_CODE_OAUTH_TOKEN` or `CC_ANTHROPIC_API_KEY` is required when `CODING_AGENT=CC`.
129
- *Either `GEMINI_OAUTH_CREDS_B64` or `GEMINI_API_KEY` is required when `CODING_AGENT=GC`.
132
+ *Either `GEMINI_OAUTH_CREDS_B64` or `GEMINI_API_KEY` is required when `CODING_AGENT=GC`.
133
+ *Either `CODEX_AUTH_JSON_B64` or `CODEX_API_KEY` is required when `CODING_AGENT=CX`.
130
134
 
131
135
  ## Output Files
132
136
 
@@ -115,6 +115,16 @@ elif [ "$CODING_AGENT" = "GC" ]; then
115
115
  echo "Run 'gemini' locally and choose Login with Google to create ~/.gemini/oauth_creds.json."
116
116
  exit 1
117
117
  fi
118
+ elif [ "$CODING_AGENT" = "CX" ]; then
119
+ if [ -z "$CODEX_AUTH_JSON_B64" ] && [ -z "$CODEX_AUTH_JSON_PATH" ] && [ -z "$CODEX_API_KEY" ] && [ -z "$OPENAI_API_KEY" ]; then
120
+ echo "ERROR: Either CODEX_AUTH_JSON_B64 (or CODEX_AUTH_JSON_PATH) or CODEX_API_KEY is required"
121
+ echo ""
122
+ echo " CODEX_AUTH_JSON_B64 - Base64 of ~/.codex/auth.json from 'codex login' (recommended)"
123
+ echo " CODEX_API_KEY - OpenAI API key (pay per token)"
124
+ echo ""
125
+ echo "Run 'codex login' (or 'codex login --device-auth') locally, then base64 ~/.codex/auth.json."
126
+ exit 1
127
+ fi
118
128
  else
119
129
  if [ -z "$CC_CLAUDE_CODE_OAUTH_TOKEN" ] && [ -z "$CC_ANTHROPIC_API_KEY" ]; then
120
130
  echo "ERROR: Either CC_CLAUDE_CODE_OAUTH_TOKEN or CC_ANTHROPIC_API_KEY is required"
@@ -149,6 +159,15 @@ if [ "${WILE_MOCK_GEMINI:-}" = "true" ] && [ "$CODING_AGENT" = "GC" ]; then
149
159
  export PATH="$MOCK_BIN:$PATH"
150
160
  fi
151
161
 
162
+ if [ "${WILE_MOCK_CODEX:-}" = "true" ] && [ "$CODING_AGENT" = "CX" ]; then
163
+ echo " Codex: Mocked"
164
+ MOCK_BIN="/home/wile/mock-bin"
165
+ mkdir -p "$MOCK_BIN"
166
+ cp "$SCRIPT_DIR/mock-codex.sh" "$MOCK_BIN/codex"
167
+ chmod +x "$MOCK_BIN/codex"
168
+ export PATH="$MOCK_BIN:$PATH"
169
+ fi
170
+
152
171
  if [ "$CODING_AGENT" = "OC" ]; then
153
172
  if [ "$OC_PROVIDER" = "openrouter" ]; then
154
173
  echo " Auth: OpenRouter (OpenCode)"
@@ -212,6 +231,30 @@ JSON
212
231
  }
213
232
  JSON
214
233
  fi
234
+ elif [ "$CODING_AGENT" = "CX" ]; then
235
+ CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
236
+ export CODEX_HOME
237
+
238
+ if [ -n "$CODEX_AUTH_JSON_B64" ] || [ -n "$CODEX_AUTH_JSON_PATH" ]; then
239
+ echo " Auth: ChatGPT (subscription)"
240
+ mkdir -p "$CODEX_HOME"
241
+ if [ -n "$CODEX_AUTH_JSON_B64" ]; then
242
+ echo "$CODEX_AUTH_JSON_B64" | base64 -d > "$CODEX_HOME/auth.json"
243
+ else
244
+ cp "$CODEX_AUTH_JSON_PATH" "$CODEX_HOME/auth.json"
245
+ fi
246
+ else
247
+ echo " Auth: API Key (OpenAI)"
248
+ CODEX_API_KEY_VALUE="${CODEX_API_KEY:-$OPENAI_API_KEY}"
249
+ mkdir -p "$CODEX_HOME"
250
+ cat > "$CODEX_HOME/auth.json" << EOF
251
+ {
252
+ "auth_mode": "apikey",
253
+ "OPENAI_API_KEY": "$CODEX_API_KEY_VALUE"
254
+ }
255
+ EOF
256
+ fi
257
+ chmod 600 "$CODEX_HOME/auth.json"
215
258
  else
216
259
  # Set up Claude Code authentication
217
260
  if [ -n "$CC_CLAUDE_CODE_OAUTH_TOKEN" ]; then
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ const readline = require("node:readline");
3
+
4
+ const rl = readline.createInterface({
5
+ input: process.stdin,
6
+ crlfDelay: Infinity
7
+ });
8
+
9
+ const writeLine = (value) => {
10
+ process.stdout.write(`${value}\n`);
11
+ };
12
+
13
+ const writeText = (text) => {
14
+ if (typeof text !== "string") return;
15
+ process.stdout.write(text);
16
+ if (!text.endsWith("\n")) {
17
+ process.stdout.write("\n");
18
+ }
19
+ };
20
+
21
+ const formatChanges = (changes) => {
22
+ if (!Array.isArray(changes) || changes.length === 0) return "";
23
+ return changes
24
+ .map((change) => `${change.kind ?? "update"} ${change.path ?? "unknown"}`)
25
+ .join(", ");
26
+ };
27
+
28
+ rl.on("line", (line) => {
29
+ if (!line.trim()) return;
30
+ if (process.env.WILE_STREAM_JSON === "true") {
31
+ writeLine(line);
32
+ return;
33
+ }
34
+
35
+ let payload;
36
+ try {
37
+ payload = JSON.parse(line);
38
+ } catch {
39
+ return;
40
+ }
41
+
42
+ const type = payload.type;
43
+
44
+ if (type === "thread.started") {
45
+ if (payload.thread_id) {
46
+ writeLine(`[system] thread: ${payload.thread_id}`);
47
+ }
48
+ return;
49
+ }
50
+
51
+ if (type === "turn.failed") {
52
+ const message = payload.error?.message ?? "Unknown error";
53
+ writeLine(`[error] ${message}`);
54
+ return;
55
+ }
56
+
57
+ if (type === "error") {
58
+ const message = payload.message ?? "Unknown error";
59
+ writeLine(`[error] ${message}`);
60
+ return;
61
+ }
62
+
63
+ if (!payload.item) return;
64
+
65
+ const itemType = payload.item.type;
66
+
67
+ if (type === "item.completed" && itemType === "agent_message") {
68
+ writeText(payload.item.text);
69
+ return;
70
+ }
71
+
72
+ if (type === "item.completed" && itemType === "reasoning") {
73
+ writeLine(`[thinking] ${payload.item.text ?? ""}`);
74
+ return;
75
+ }
76
+
77
+ if (itemType === "command_execution") {
78
+ const command = payload.item.command ?? "command";
79
+ if (type === "item.started") {
80
+ writeLine(`[tool] shell: ${command}`);
81
+ return;
82
+ }
83
+ if (type === "item.completed") {
84
+ const status = payload.item.status ?? "completed";
85
+ const exitCode =
86
+ payload.item.exit_code !== undefined && payload.item.exit_code !== null
87
+ ? ` (exit ${payload.item.exit_code})`
88
+ : "";
89
+ writeLine(`[tool-result] shell ${status}${exitCode}`);
90
+ return;
91
+ }
92
+ }
93
+
94
+ if (type === "item.completed" && itemType === "file_change") {
95
+ const detail = formatChanges(payload.item.changes);
96
+ writeLine(`[tool] file_change${detail ? `: ${detail}` : ""}`);
97
+ return;
98
+ }
99
+
100
+ if (itemType === "mcp_tool_call") {
101
+ const server = payload.item.server ?? "mcp";
102
+ const tool = payload.item.tool ?? "tool";
103
+ if (type === "item.started") {
104
+ writeLine(`[tool] mcp ${server}/${tool}`);
105
+ return;
106
+ }
107
+ if (type === "item.completed") {
108
+ const status = payload.item.status ?? "completed";
109
+ writeLine(`[tool-result] mcp ${server}/${tool} ${status}`);
110
+ return;
111
+ }
112
+ }
113
+
114
+ if (itemType === "web_search") {
115
+ const query = payload.item.query ?? "";
116
+ if (type === "item.started") {
117
+ writeLine(`[tool] web_search${query ? `: ${query}` : ""}`);
118
+ }
119
+ return;
120
+ }
121
+
122
+ if (type === "item.completed" && itemType === "error") {
123
+ writeLine(`[error] ${payload.item.message ?? "Unknown error"}`);
124
+ }
125
+ });
@@ -60,7 +60,11 @@ COUNT=$(cat "$COUNT_FILE")
60
60
  NEXT_COUNT=$((COUNT + 1))
61
61
  echo "$NEXT_COUNT" > "$COUNT_FILE"
62
62
 
63
- if [ "${WILE_MOCK_MODE:-}" = "preflight_fail" ]; then
63
+ MODE=${WILE_MOCK_MODE:-}
64
+ if [ -n "$MODE" ] && { [ "$MODE" = "preflight_fail" ] || [ "$MODE" = "preflight_fail_split" ] || [ "$MODE" = "preflight_fail_trailing" ]; }; then
65
+ if [ "$MODE" = "preflight_fail_trailing" ] && [ "$COUNT" -ne 0 ]; then
66
+ :
67
+ else
64
68
  PROGRESS_PATH=".wile/progress.txt"
65
69
  if [ -f "$PROGRESS_PATH" ]; then
66
70
  :
@@ -82,10 +86,21 @@ if [ "${WILE_MOCK_MODE:-}" = "preflight_fail" ]; then
82
86
  - Mocked preflight failure (WILE_MOCK_MODE=preflight_fail)
83
87
  EOF
84
88
 
85
- cat <<'JSON'
89
+ if [ "$MODE" = "preflight_fail_split" ]; then
90
+ cat <<'JSON'
91
+ {"type":"assistant","message":{"content":[{"type":"text","text":"<\npromise>PREFLIGHT_FAILED</promise>\n"}]}}
92
+ JSON
93
+ elif [ "$MODE" = "preflight_fail_trailing" ]; then
94
+ cat <<'JSON'
95
+ {"type":"assistant","message":{"content":[{"type":"text","text":"<promise>PREFLIGHT_FAILED</promise> trailing\n"}]}}
96
+ JSON
97
+ else
98
+ cat <<'JSON'
86
99
  {"type":"assistant","message":{"content":[{"type":"text","text":"<promise>PREFLIGHT_FAILED</promise>\n"}]}}
87
100
  JSON
88
- exit 0
101
+ fi
102
+ exit 0
103
+ fi
89
104
  fi
90
105
 
91
106
  if [ "$COUNT" -eq 0 ]; then
@@ -0,0 +1,99 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ if [ "${WILE_MODE:-}" = "compact" ]; then
5
+ node - <<'NODE'
6
+ const fs = require("fs");
7
+
8
+ const prdPath = ".wile/prd.json";
9
+ const progressPath = ".wile/progress.txt";
10
+ const prd = JSON.parse(fs.readFileSync(prdPath, "utf8"));
11
+ const stories = Array.isArray(prd.userStories) ? prd.userStories : [];
12
+ const firstStory = stories[0] || {};
13
+ const lastStory = stories[stories.length - 1] || {};
14
+ const priority = typeof firstStory.priority === "number" ? firstStory.priority : 1;
15
+ const taskIds = `From ${firstStory.id || "TASK-001"} to ${lastStory.id || "TASK-001"}`;
16
+
17
+ const compactStory = {
18
+ id: "GROUP-001",
19
+ title: "summary of everything done here",
20
+ tasks: [
21
+ "High level of what was accomplished here",
22
+ "Should NOT have all tasks in here, should be very summarized"
23
+ ],
24
+ taskIds,
25
+ priority,
26
+ passes: true,
27
+ notes: "Don't repeat task ids when starting the next one."
28
+ };
29
+
30
+ fs.writeFileSync(prdPath, JSON.stringify({ userStories: [compactStory] }, null, 2) + "\n");
31
+
32
+ const progressLines = [
33
+ "# Wile Progress Log",
34
+ "",
35
+ "## Codebase Patterns",
36
+ "- Summarized key learnings for future agents.",
37
+ "",
38
+ "---",
39
+ "",
40
+ "Compacted the progress log to highlight the most important work and patterns.",
41
+ "Kept only high-level details to preserve context without noise.",
42
+ ""
43
+ ];
44
+
45
+ fs.writeFileSync(progressPath, progressLines.join("\n"));
46
+ NODE
47
+
48
+ cat <<'JSON'
49
+ {"type":"item.completed","item":{"id":"item_1","type":"agent_message","text":"{\"id\":\"GROUP-001\",\"title\":\"summary of everything done here\",\"tasks\":[\"High level of what was accomplished here\",\"Should NOT have all tasks in here, should be very summarized\"],\"taskIds\":\"From TASK-001 to TASK-029\",\"priority\":1,\"passes\":true,\"notes\":\"Don't repeat task ids when starting the next one.\"}\n"}}
50
+ JSON
51
+ exit 0
52
+ fi
53
+
54
+ COUNT_FILE="/tmp/wile-codex-mock-count"
55
+ if [ ! -f "$COUNT_FILE" ]; then
56
+ echo "0" > "$COUNT_FILE"
57
+ fi
58
+
59
+ COUNT=$(cat "$COUNT_FILE")
60
+ NEXT_COUNT=$((COUNT + 1))
61
+ echo "$NEXT_COUNT" > "$COUNT_FILE"
62
+
63
+ if [ "${WILE_MOCK_MODE:-}" = "preflight_fail" ]; then
64
+ PROGRESS_PATH=".wile/progress.txt"
65
+ if [ -f "$PROGRESS_PATH" ]; then
66
+ :
67
+ else
68
+ printf '%s\n' "# Wile Progress Log" "" "## Codebase Patterns" "" "---" > "$PROGRESS_PATH"
69
+ fi
70
+
71
+ DATE=$(date +%Y-%m-%d)
72
+ cat >> "$PROGRESS_PATH" <<EOF
73
+
74
+ ---
75
+
76
+ ## ${DATE} - PREFLIGHT FAILED
77
+
78
+ **Checks run:**
79
+ - Mocked preflight check
80
+
81
+ **Failures:**
82
+ - Mocked preflight failure (WILE_MOCK_MODE=preflight_fail)
83
+ EOF
84
+
85
+ cat <<'JSON'
86
+ {"type":"item.completed","item":{"id":"item_1","type":"agent_message","text":"<promise>PREFLIGHT_FAILED</promise>\n"}}
87
+ JSON
88
+ exit 0
89
+ fi
90
+
91
+ if [ "$COUNT" -eq 0 ]; then
92
+ cat <<'JSON'
93
+ {"type":"item.completed","item":{"id":"item_1","type":"agent_message","text":"ANSWER: 2\n"}}
94
+ JSON
95
+ else
96
+ cat <<'JSON'
97
+ {"type":"item.completed","item":{"id":"item_1","type":"agent_message","text":"<promise>ALL_STORIES_COMPLETED</promise>\n"}}
98
+ JSON
99
+ fi
@@ -7,6 +7,7 @@ AGENT_DIR="$ROOT_DIR/packages/agent"
7
7
  docker build -t wile-agent:local "$AGENT_DIR" >/dev/null
8
8
 
9
9
  run_failure_case() {
10
+ local mock_mode="${1:-preflight_fail}"
10
11
  TMP_DIR=$(mktemp -d /tmp/wile-preflight-fail-XXXXXX)
11
12
  cleanup() {
12
13
  rm -rf "$TMP_DIR"
@@ -54,7 +55,7 @@ MD
54
55
  -e WILE_LOCAL_REPO_PATH=/home/wile/workspace/repo \
55
56
  -e MAX_ITERATIONS=1 \
56
57
  -e WILE_MOCK_CLAUDE=true \
57
- -e WILE_MOCK_MODE=preflight_fail \
58
+ -e WILE_MOCK_MODE="$mock_mode" \
58
59
  -v "$TMP_DIR:/home/wile/workspace/repo" \
59
60
  wile-agent:local 2>&1 | tee "$OUTPUT_FILE"
60
61
  EXIT_CODE=$?
@@ -76,6 +77,64 @@ MD
76
77
  trap - EXIT INT TERM
77
78
  }
78
79
 
80
+ run_trailing_case() {
81
+ TMP_DIR=$(mktemp -d /tmp/wile-preflight-trailing-XXXXXX)
82
+ cleanup() {
83
+ rm -rf "$TMP_DIR"
84
+ }
85
+ trap cleanup EXIT INT TERM
86
+
87
+ mkdir -p "$TMP_DIR/.wile"
88
+ cat > "$TMP_DIR/.wile/prd.json" <<'JSON'
89
+ {
90
+ "userStories": [
91
+ {
92
+ "id": "US-TEST-003",
93
+ "title": "Preflight trailing marker test",
94
+ "acceptanceCriteria": ["n/a"],
95
+ "priority": 1,
96
+ "passes": false
97
+ }
98
+ ]
99
+ }
100
+ JSON
101
+
102
+ cat > "$TMP_DIR/.wile/preflight.md" <<'MD'
103
+ <!--
104
+ Use bullet points for preflight checks, e.g.
105
+ - Confirm SUPABASE_DB_URL is set.
106
+ - Run `supabase db reset --db-url "$SUPABASE_DB_URL"`.
107
+ -->
108
+ MD
109
+
110
+ OUTPUT_FILE="$TMP_DIR/output.txt"
111
+
112
+ set +e
113
+ docker run --rm \
114
+ -e CODING_AGENT=CC \
115
+ -e CC_ANTHROPIC_API_KEY=dummy-key \
116
+ -e WILE_REPO_SOURCE=local \
117
+ -e WILE_LOCAL_REPO_PATH=/home/wile/workspace/repo \
118
+ -e MAX_ITERATIONS=1 \
119
+ -e WILE_MOCK_CLAUDE=true \
120
+ -e WILE_MOCK_MODE=preflight_fail_trailing \
121
+ -v "$TMP_DIR:/home/wile/workspace/repo" \
122
+ wile-agent:local 2>&1 | tee "$OUTPUT_FILE"
123
+ EXIT_CODE=$?
124
+ set -e
125
+
126
+ if [ "$EXIT_CODE" -ne 0 ]; then
127
+ echo "error: expected zero exit code for trailing marker case" >&2
128
+ exit 1
129
+ fi
130
+
131
+ grep -q "Preflight complete. Starting main loop..." "$OUTPUT_FILE"
132
+ grep -q "✅ ALL TASKS COMPLETE" "$OUTPUT_FILE"
133
+
134
+ rm -rf "$TMP_DIR"
135
+ trap - EXIT INT TERM
136
+ }
137
+
79
138
  run_success_case() {
80
139
  TMP_DIR=$(mktemp -d /tmp/wile-preflight-success-XXXXXX)
81
140
  cleanup() {
@@ -133,6 +192,8 @@ MD
133
192
  }
134
193
 
135
194
  run_failure_case
195
+ run_failure_case preflight_fail_split
196
+ run_trailing_case
136
197
  run_success_case
137
198
 
138
199
  echo "test-preflight-docker: ok"
@@ -10,6 +10,7 @@ CLAUDE_MODEL=${CC_CLAUDE_MODEL:-sonnet}
10
10
  OC_PROVIDER=${OC_PROVIDER:-native}
11
11
  OC_MODEL=${OC_MODEL:-opencode/grok-code}
12
12
  GEMINI_MODEL=${GEMINI_MODEL:-auto-gemini-3}
13
+ CODEX_MODEL=${CODEX_MODEL:-}
13
14
 
14
15
  # For openrouter provider, prepend vendor prefix if missing
15
16
  if [ "$OC_PROVIDER" = "openrouter" ] && [[ "$OC_MODEL" != */* ]]; then
@@ -33,6 +34,8 @@ if [ "$CODING_AGENT" = "OC" ]; then
33
34
  echo " Model: $OC_MODEL"
34
35
  elif [ "$CODING_AGENT" = "GC" ]; then
35
36
  echo " Model: ${GEMINI_MODEL:-auto}"
37
+ elif [ "$CODING_AGENT" = "CX" ]; then
38
+ echo " Model: ${CODEX_MODEL:-default}"
36
39
  else
37
40
  echo " Model: $CLAUDE_MODEL"
38
41
  fi
@@ -85,12 +88,25 @@ run_gemini() {
85
88
  | node "$SCRIPT_DIR/gemini-stream.js"
86
89
  }
87
90
 
91
+ run_codex() {
92
+ local prompt_path="$1"
93
+ local model_args=()
94
+ if [ -n "$CODEX_MODEL" ]; then
95
+ model_args=(--model "$CODEX_MODEL")
96
+ fi
97
+ cat "$prompt_path" \
98
+ | codex exec --json --dangerously-bypass-approvals-and-sandbox "${model_args[@]}" - \
99
+ | node "$SCRIPT_DIR/codex-stream.js"
100
+ }
101
+
88
102
  run_agent() {
89
103
  local prompt_path="$1"
90
104
  if [ "$CODING_AGENT" = "OC" ]; then
91
105
  run_opencode "$prompt_path"
92
106
  elif [ "$CODING_AGENT" = "GC" ]; then
93
107
  run_gemini "$prompt_path"
108
+ elif [ "$CODING_AGENT" = "CX" ]; then
109
+ run_codex "$prompt_path"
94
110
  else
95
111
  run_claude "$prompt_path"
96
112
  fi
@@ -10,6 +10,7 @@ CLAUDE_MODEL=${CC_CLAUDE_MODEL:-sonnet}
10
10
  OC_PROVIDER=${OC_PROVIDER:-native}
11
11
  OC_MODEL=${OC_MODEL:-opencode/grok-code}
12
12
  GEMINI_MODEL=${GEMINI_MODEL:-auto-gemini-3}
13
+ CODEX_MODEL=${CODEX_MODEL:-}
13
14
 
14
15
  # For openrouter provider, prepend vendor prefix if missing
15
16
  if [ "$OC_PROVIDER" = "openrouter" ] && [[ "$OC_MODEL" != */* ]]; then
@@ -17,6 +18,7 @@ if [ "$OC_PROVIDER" = "openrouter" ] && [[ "$OC_MODEL" != */* ]]; then
17
18
  fi
18
19
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
20
  PREFLIGHT_PROMPT_FILE="$SCRIPT_DIR/prompt-preflight.md"
21
+ PROGRESS_PATH="${WILE_PROGRESS_PATH:-.wile/progress.txt}"
20
22
  TEE_TARGET="${WILE_TEE_TARGET:-/dev/stderr}"
21
23
  if ! ( : > "$TEE_TARGET" ) 2>/dev/null; then
22
24
  TEE_TARGET="/dev/null"
@@ -31,6 +33,8 @@ if [ "$CODING_AGENT" = "OC" ]; then
31
33
  echo " Model: $OC_MODEL"
32
34
  elif [ "$CODING_AGENT" = "GC" ]; then
33
35
  echo " Model: ${GEMINI_MODEL:-auto}"
36
+ elif [ "$CODING_AGENT" = "CX" ]; then
37
+ echo " Model: ${CODEX_MODEL:-default}"
34
38
  else
35
39
  echo " Model: $CLAUDE_MODEL"
36
40
  fi
@@ -73,25 +77,58 @@ run_gemini() {
73
77
  | node "$SCRIPT_DIR/gemini-stream.js"
74
78
  }
75
79
 
80
+ run_codex() {
81
+ local prompt_path="$1"
82
+ local model_args=()
83
+ if [ -n "$CODEX_MODEL" ]; then
84
+ model_args=(--model "$CODEX_MODEL")
85
+ fi
86
+ cat "$prompt_path" \
87
+ | codex exec --json --dangerously-bypass-approvals-and-sandbox "${model_args[@]}" - \
88
+ | node "$SCRIPT_DIR/codex-stream.js"
89
+ }
90
+
76
91
  run_agent() {
77
92
  local prompt_path="$1"
78
93
  if [ "$CODING_AGENT" = "OC" ]; then
79
94
  run_opencode "$prompt_path"
80
95
  elif [ "$CODING_AGENT" = "GC" ]; then
81
96
  run_gemini "$prompt_path"
97
+ elif [ "$CODING_AGENT" = "CX" ]; then
98
+ run_codex "$prompt_path"
82
99
  else
83
100
  run_claude "$prompt_path"
84
101
  fi
85
102
  }
86
103
 
104
+ has_terminal_promise() {
105
+ local output="$1"
106
+ local marker="$2"
107
+ local clean_output normalized_output marker_re
108
+
109
+ clean_output=$(printf '%s' "$output" | tr -d '\r')
110
+ normalized_output=$(printf '%s' "$clean_output" | tr '\n' ' ' | sed -e 's/[[:space:]]*$//')
111
+ marker_re=${marker//_/[[:space:]]*_[[:space:]]*}
112
+
113
+ printf '%s' "$normalized_output" | grep -q -E "<[[:space:]]*promise>[[:space:]]*${marker_re}[[:space:]]*</[[:space:]]*promise>[[:space:]]*$"
114
+ }
115
+
87
116
  OUTPUT=$(run_agent "$PREFLIGHT_PROMPT_FILE" | tee "$TEE_TARGET") || true
88
- CLEAN_OUTPUT=$(printf '%s' "$OUTPUT" | tr -d '\r' | sed -e 's/[[:space:]]*$//')
89
- if printf '%s\n' "$CLEAN_OUTPUT" | grep -q -E '^[[:space:]]*<promise>PREFLIGHT_FAILED</promise>[[:space:]]*$'; then
117
+ CLEAN_OUTPUT=$(printf '%s' "$OUTPUT" | tr -d '\r')
118
+ if has_terminal_promise "$OUTPUT" "PREFLIGHT_FAILED"; then
90
119
  if printf '%s' "$CLEAN_OUTPUT" | grep -F '```' >/dev/null 2>&1; then
91
120
  :
92
121
  elif printf '%s' "$CLEAN_OUTPUT" | grep -F '`<promise>PREFLIGHT_FAILED</promise>`' >/dev/null 2>&1; then
93
122
  :
94
123
  else
124
+ if [ -f "$PROGRESS_PATH" ]; then
125
+ TAIL_LINES="${PREFLIGHT_LOG_TAIL:-80}"
126
+ echo ""
127
+ echo "══════════════════════════════════════════════════════"
128
+ echo " Preflight log tail (${TAIL_LINES} lines)"
129
+ echo "══════════════════════════════════════════════════════"
130
+ tail -n "$TAIL_LINES" "$PROGRESS_PATH" || true
131
+ fi
95
132
  echo ""
96
133
  echo "══════════════════════════════════════════════════════"
97
134
  echo " ❌ PREFLIGHT FAILED"
@@ -12,6 +12,7 @@ CLAUDE_MODEL=${CC_CLAUDE_MODEL:-sonnet}
12
12
  OC_PROVIDER=${OC_PROVIDER:-native}
13
13
  OC_MODEL=${OC_MODEL:-opencode/grok-code}
14
14
  GEMINI_MODEL=${GEMINI_MODEL:-auto-gemini-3}
15
+ CODEX_MODEL=${CODEX_MODEL:-}
15
16
 
16
17
  # For openrouter provider, prepend vendor prefix if missing
17
18
  if [ "$OC_PROVIDER" = "openrouter" ] && [[ "$OC_MODEL" != */* ]]; then
@@ -20,6 +21,7 @@ fi
20
21
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
22
  PROMPT_FILE="$SCRIPT_DIR/prompt.md"
22
23
  PREFLIGHT_PROMPT_FILE="$SCRIPT_DIR/prompt-preflight.md"
24
+ PROGRESS_PATH="${WILE_PROGRESS_PATH:-.wile/progress.txt}"
23
25
  ADDITIONAL_PROMPT_FILE="${WILE_ADDITIONAL_INSTRUCTIONS:-}"
24
26
  TEE_TARGET="${WILE_TEE_TARGET:-/dev/stderr}"
25
27
  if ! ( : > "$TEE_TARGET" ) 2>/dev/null; then
@@ -36,6 +38,8 @@ if [ "$CODING_AGENT" = "OC" ]; then
36
38
  echo " Model: $OC_MODEL"
37
39
  elif [ "$CODING_AGENT" = "GC" ]; then
38
40
  echo " Model: ${GEMINI_MODEL:-auto}"
41
+ elif [ "$CODING_AGENT" = "CX" ]; then
42
+ echo " Model: ${CODEX_MODEL:-default}"
39
43
  else
40
44
  echo " Model: $CLAUDE_MODEL"
41
45
  fi
@@ -90,17 +94,42 @@ run_gemini() {
90
94
  | node "$SCRIPT_DIR/gemini-stream.js"
91
95
  }
92
96
 
97
+ run_codex() {
98
+ local prompt_path="$1"
99
+ local model_args=()
100
+ if [ -n "$CODEX_MODEL" ]; then
101
+ model_args=(--model "$CODEX_MODEL")
102
+ fi
103
+ cat "$prompt_path" \
104
+ | codex exec --json --dangerously-bypass-approvals-and-sandbox "${model_args[@]}" - \
105
+ | node "$SCRIPT_DIR/codex-stream.js"
106
+ }
107
+
93
108
  run_agent() {
94
109
  local prompt_path="$1"
95
110
  if [ "$CODING_AGENT" = "OC" ]; then
96
111
  run_opencode "$prompt_path"
97
112
  elif [ "$CODING_AGENT" = "GC" ]; then
98
113
  run_gemini "$prompt_path"
114
+ elif [ "$CODING_AGENT" = "CX" ]; then
115
+ run_codex "$prompt_path"
99
116
  else
100
117
  run_claude "$prompt_path"
101
118
  fi
102
119
  }
103
120
 
121
+ has_terminal_promise() {
122
+ local output="$1"
123
+ local marker="$2"
124
+ local clean_output normalized_output marker_re
125
+
126
+ clean_output=$(printf '%s' "$output" | tr -d '\r')
127
+ normalized_output=$(printf '%s' "$clean_output" | tr '\n' ' ' | sed -e 's/[[:space:]]*$//')
128
+ marker_re=${marker//_/[[:space:]]*_[[:space:]]*}
129
+
130
+ printf '%s' "$normalized_output" | grep -q -E "<[[:space:]]*promise>[[:space:]]*${marker_re}[[:space:]]*</[[:space:]]*promise>[[:space:]]*$"
131
+ }
132
+
104
133
  # ════════════════════════════════════════════════════════════
105
134
  # ITERATION 0: Preflight / Setup
106
135
  # ════════════════════════════════════════════════════════════
@@ -113,14 +142,22 @@ echo ""
113
142
  if [ -f "$PREFLIGHT_PROMPT_FILE" ]; then
114
143
  OUTPUT=$(run_agent "$PREFLIGHT_PROMPT_FILE" | tee "$TEE_TARGET") || true
115
144
 
116
- # Check if preflight failed critically (tag must be on its own line; reject backticks/code fences)
117
- CLEAN_OUTPUT=$(printf '%s' "$OUTPUT" | tr -d '\r' | sed -e 's/[[:space:]]*$//')
118
- if printf '%s\n' "$CLEAN_OUTPUT" | grep -q -E '^[[:space:]]*<promise>PREFLIGHT_FAILED</promise>[[:space:]]*$'; then
145
+ # Check if preflight failed critically (tag must be terminal; reject backticks/code fences)
146
+ CLEAN_OUTPUT=$(printf '%s' "$OUTPUT" | tr -d '\r')
147
+ if has_terminal_promise "$OUTPUT" "PREFLIGHT_FAILED"; then
119
148
  if printf '%s' "$CLEAN_OUTPUT" | grep -F '```' >/dev/null 2>&1; then
120
149
  :
121
150
  elif printf '%s' "$CLEAN_OUTPUT" | grep -F '`<promise>PREFLIGHT_FAILED</promise>`' >/dev/null 2>&1; then
122
151
  :
123
152
  else
153
+ if [ -f "$PROGRESS_PATH" ]; then
154
+ TAIL_LINES="${PREFLIGHT_LOG_TAIL:-80}"
155
+ echo ""
156
+ echo "══════════════════════════════════════════════════════"
157
+ echo " Preflight log tail (${TAIL_LINES} lines)"
158
+ echo "══════════════════════════════════════════════════════"
159
+ tail -n "$TAIL_LINES" "$PROGRESS_PATH" || true
160
+ fi
124
161
  echo ""
125
162
  echo "══════════════════════════════════════════════════════"
126
163
  echo " ❌ PREFLIGHT FAILED - Cannot continue"
@@ -151,10 +188,8 @@ for i in $(seq 1 $MAX_ITERATIONS); do
151
188
  # Capture output while also displaying it (tee to stderr)
152
189
  OUTPUT=$(run_agent "$PROMPT_FILE" | tee "$TEE_TARGET") || true
153
190
 
154
- # Check for completion signal (tag must be the final non-empty line)
155
- CLEAN_OUTPUT=$(printf '%s' "$OUTPUT" | tr -d '\r' | sed -e 's/[[:space:]]*$//')
156
- FINAL_LINE=$(printf '%s\n' "$CLEAN_OUTPUT" | awk 'NF { last=$0 } END { print last }')
157
- if printf '%s\n' "$FINAL_LINE" | grep -q -E '^[[:space:]]*<promise>ALL_STORIES_COMPLETED</promise>[[:space:]]*$'; then
191
+ # Check for completion signal (tag must be terminal)
192
+ if has_terminal_promise "$OUTPUT" "ALL_STORIES_COMPLETED"; then
158
193
  echo ""
159
194
  echo "══════════════════════════════════════════════════════"
160
195
  echo " ✅ ALL TASKS COMPLETE"
package/dist/cli.js CHANGED
@@ -7548,7 +7548,9 @@ var tips = {
7548
7548
  openrouter: "Tip: create an OpenRouter API key at https://openrouter.ai/keys (pay per token).",
7549
7549
  github: "Tip: use a GitHub Personal Access Token (fine-grained recommended). Create at https://github.com/settings/tokens?type=beta with Contents (read/write) and Metadata (read).",
7550
7550
  geminiOauth: "Tip: run 'gemini' locally and choose Login with Google to create ~/.gemini/oauth_creds.json.",
7551
- geminiApiKey: "Tip: create a Gemini API key at https://aistudio.google.com/app/apikey (pay per token)."
7551
+ geminiApiKey: "Tip: create a Gemini API key at https://aistudio.google.com/app/apikey (pay per token).",
7552
+ codexOauth: "Tip: run 'codex login' (or 'codex login --device-auth') locally to create ~/.codex/auth.json.",
7553
+ codexApiKey: "Tip: create an OpenAI API key at https://platform.openai.com/api-keys (pay per token)."
7552
7554
  };
7553
7555
  var nativeOcModels = [
7554
7556
  { title: "Grok Code Fast 1 (recommended)", value: "opencode/grok-code" },
@@ -7642,7 +7644,11 @@ var toEnvString = (env) => {
7642
7644
  "OC_MODEL",
7643
7645
  "OC_OPENROUTER_API_KEY",
7644
7646
  "GEMINI_OAUTH_CREDS_B64",
7645
- "GEMINI_API_KEY"
7647
+ "GEMINI_API_KEY",
7648
+ "CODEX_AUTH_JSON_B64",
7649
+ "CODEX_AUTH_JSON_PATH",
7650
+ "CODEX_API_KEY",
7651
+ "CODEX_MODEL"
7646
7652
  ];
7647
7653
  const orderedSet = new Set(orderedKeys);
7648
7654
  const ordered = orderedKeys.filter((key) => (key in env));
@@ -7682,13 +7688,15 @@ var runConfig = async () => {
7682
7688
  choices: [
7683
7689
  { title: "Claude Code (CC)", value: "CC" },
7684
7690
  { title: "Gemini CLI (GC)", value: "GC" },
7685
- { title: "OpenCode (OC)", value: "OC" }
7691
+ { title: "OpenCode (OC)", value: "OC" },
7692
+ { title: "Codex CLI (CX)", value: "CX" }
7686
7693
  ],
7687
- initial: existingEnv.CODING_AGENT === "OC" ? 2 : existingEnv.CODING_AGENT === "GC" ? 1 : 0
7694
+ initial: existingEnv.CODING_AGENT === "OC" ? 2 : existingEnv.CODING_AGENT === "GC" ? 1 : existingEnv.CODING_AGENT === "CX" ? 3 : 0
7688
7695
  });
7689
7696
  const codingAgent = codingAgentResponse.codingAgent;
7690
7697
  let authMethod = null;
7691
7698
  let geminiAuthMethod = null;
7699
+ let codexAuthMethod = null;
7692
7700
  let authValueResponse = {};
7693
7701
  let defaultModelResponse = {};
7694
7702
  let ocProviderResponse = {};
@@ -7697,6 +7705,9 @@ var runConfig = async () => {
7697
7705
  let ocNativeModelResponse = {};
7698
7706
  let geminiOauthPathResponse = {};
7699
7707
  let geminiApiKeyResponse = {};
7708
+ let codexAuthJsonPathResponse = {};
7709
+ let codexApiKeyResponse = {};
7710
+ let codexModelResponse = {};
7700
7711
  if (codingAgent === "CC") {
7701
7712
  const authDefault = existingEnv.CC_CLAUDE_CODE_OAUTH_TOKEN ? "oauth" : existingEnv.CC_ANTHROPIC_API_KEY ? "apiKey" : "oauth";
7702
7713
  const authResponse = await prompt({
@@ -7770,7 +7781,7 @@ var runConfig = async () => {
7770
7781
  initial: existingNativeIdx >= 0 ? existingNativeIdx : 0
7771
7782
  });
7772
7783
  }
7773
- } else {
7784
+ } else if (codingAgent === "GC") {
7774
7785
  const geminiAuthDefault = existingEnv.GEMINI_OAUTH_CREDS_B64 ? "oauth" : existingEnv.GEMINI_API_KEY ? "apiKey" : "oauth";
7775
7786
  const geminiAuthResponse = await prompt({
7776
7787
  type: "select",
@@ -7802,6 +7813,44 @@ var runConfig = async () => {
7802
7813
  initial: existingEnv.GEMINI_API_KEY ?? ""
7803
7814
  });
7804
7815
  }
7816
+ } else {
7817
+ const codexAuthDefault = existingEnv.CODEX_AUTH_JSON_B64 ? "oauth" : existingEnv.CODEX_API_KEY || existingEnv.OPENAI_API_KEY ? "apiKey" : "oauth";
7818
+ const codexAuthResponse = await prompt({
7819
+ type: "select",
7820
+ name: "codexAuthMethod",
7821
+ message: "Codex CLI authentication",
7822
+ choices: [
7823
+ { title: "ChatGPT login (subscription)", value: "oauth" },
7824
+ { title: "API key (OpenAI API)", value: "apiKey" }
7825
+ ],
7826
+ initial: codexAuthDefault === "apiKey" ? 1 : 0
7827
+ });
7828
+ codexAuthMethod = codexAuthResponse.codexAuthMethod;
7829
+ console.log("");
7830
+ console.log(codexAuthMethod === "oauth" ? tips.codexOauth : tips.codexApiKey);
7831
+ console.log("");
7832
+ if (codexAuthMethod === "oauth") {
7833
+ const defaultAuthPath = "~/.codex/auth.json";
7834
+ codexAuthJsonPathResponse = await prompt({
7835
+ type: "text",
7836
+ name: "codexAuthJsonPath",
7837
+ message: "Codex auth.json path (press enter to keep existing)",
7838
+ initial: defaultAuthPath
7839
+ });
7840
+ } else {
7841
+ codexApiKeyResponse = await prompt({
7842
+ type: "password",
7843
+ name: "codexApiKey",
7844
+ message: "OpenAI API key (press enter to keep existing)",
7845
+ initial: existingEnv.CODEX_API_KEY ?? existingEnv.OPENAI_API_KEY ?? ""
7846
+ });
7847
+ }
7848
+ codexModelResponse = await prompt({
7849
+ type: "text",
7850
+ name: "codexModel",
7851
+ message: "Codex model (press enter to keep existing)",
7852
+ initial: existingEnv.CODEX_MODEL ?? ""
7853
+ });
7805
7854
  }
7806
7855
  const repoSourceResponse = await prompt({
7807
7856
  type: "select",
@@ -7871,6 +7920,22 @@ var runConfig = async () => {
7871
7920
  geminiOauthCredsB64 = existingEnv.GEMINI_OAUTH_CREDS_B64;
7872
7921
  }
7873
7922
  }
7923
+ const codexApiKey = codingAgent === "CX" ? coalesceValue(codexApiKeyResponse.codexApiKey, existingEnv.CODEX_API_KEY ?? existingEnv.OPENAI_API_KEY) : undefined;
7924
+ let codexAuthJsonB64;
7925
+ if (codingAgent === "CX" && codexAuthMethod === "oauth") {
7926
+ const configuredPath = coalesceValue(codexAuthJsonPathResponse.codexAuthJsonPath);
7927
+ if (configuredPath) {
7928
+ const resolvedPath = resolvePath(cwd, configuredPath);
7929
+ try {
7930
+ codexAuthJsonB64 = await readBase64File(resolvedPath);
7931
+ } catch {
7932
+ throw new Error(`Failed to read Codex auth.json file: ${resolvedPath}`);
7933
+ }
7934
+ } else {
7935
+ codexAuthJsonB64 = existingEnv.CODEX_AUTH_JSON_B64;
7936
+ }
7937
+ }
7938
+ const codexModel = codingAgent === "CX" ? coalesceValue(codexModelResponse.codexModel, existingEnv.CODEX_MODEL) : undefined;
7874
7939
  const githubToken = repoSource === "github" ? coalesceValue(githubTokenResponse.githubToken, existingEnv.GITHUB_TOKEN) : existingEnv.GITHUB_TOKEN;
7875
7940
  const repoUrl = repoSource === "github" ? coalesceValue(repoResponse.repoUrl, existingEnv.GITHUB_REPO_URL) : existingEnv.GITHUB_REPO_URL;
7876
7941
  const envProjectPathValue = coalesceValue(envProjectPathResponse.envProjectPath, existingEnv.WILE_ENV_PROJECT_PATH ?? ".wile/.env.project") ?? ".wile/.env.project";
@@ -7896,12 +7961,19 @@ var runConfig = async () => {
7896
7961
  if (ocProvider === "openrouter") {
7897
7962
  setIfDefined(envOut, "OC_OPENROUTER_API_KEY", ocKey);
7898
7963
  }
7899
- } else {
7964
+ } else if (codingAgent === "GC") {
7900
7965
  if (geminiAuthMethod === "apiKey") {
7901
7966
  setIfDefined(envOut, "GEMINI_API_KEY", geminiApiKey);
7902
7967
  } else {
7903
7968
  setIfDefined(envOut, "GEMINI_OAUTH_CREDS_B64", geminiOauthCredsB64);
7904
7969
  }
7970
+ } else if (codingAgent === "CX") {
7971
+ if (codexAuthMethod === "apiKey") {
7972
+ setIfDefined(envOut, "CODEX_API_KEY", codexApiKey);
7973
+ } else {
7974
+ setIfDefined(envOut, "CODEX_AUTH_JSON_B64", codexAuthJsonB64);
7975
+ }
7976
+ setIfDefined(envOut, "CODEX_MODEL", codexModel);
7905
7977
  }
7906
7978
  await writeFile(envPath, toEnvString(envOut));
7907
7979
  await ensureGitignore(gitignorePath);
@@ -8028,7 +8100,7 @@ var readWileConfig = (options = {}) => {
8028
8100
  const envProject = parseEnvFile(envProjectPath);
8029
8101
  const repoSource = env.WILE_REPO_SOURCE || "github";
8030
8102
  if (validate) {
8031
- ensureRequired(env.CODING_AGENT === "CC" || env.CODING_AGENT === "OC" || env.CODING_AGENT === "GC", "CODING_AGENT must be set to CC, OC, or GC in .wile/secrets/.env. Run 'bunx wile config'.");
8103
+ ensureRequired(env.CODING_AGENT === "CC" || env.CODING_AGENT === "OC" || env.CODING_AGENT === "GC" || env.CODING_AGENT === "CX", "CODING_AGENT must be set to CC, OC, GC, or CX in .wile/secrets/.env. Run 'bunx wile config'.");
8032
8104
  if (repoSource === "github") {
8033
8105
  ensureRequired(Boolean(env.GITHUB_TOKEN), "GITHUB_TOKEN is required in .wile/secrets/.env. Run 'bunx wile config'.");
8034
8106
  ensureRequired(Boolean(env.GITHUB_REPO_URL), "GITHUB_REPO_URL is required in .wile/secrets/.env. Run 'bunx wile config'.");
@@ -8047,6 +8119,9 @@ var readWileConfig = (options = {}) => {
8047
8119
  if (env.CODING_AGENT === "GC") {
8048
8120
  ensureRequired(Boolean(env.GEMINI_OAUTH_CREDS_B64 || env.GEMINI_API_KEY), "Either GEMINI_OAUTH_CREDS_B64 or GEMINI_API_KEY is required in .wile/secrets/.env for Gemini CLI.");
8049
8121
  }
8122
+ if (env.CODING_AGENT === "CX") {
8123
+ ensureRequired(Boolean(env.CODEX_AUTH_JSON_B64 || env.CODEX_AUTH_JSON_PATH || env.CODEX_API_KEY || env.OPENAI_API_KEY), "Either CODEX_AUTH_JSON_B64 (or CODEX_AUTH_JSON_PATH) or CODEX_API_KEY is required in .wile/secrets/.env for Codex CLI.");
8124
+ }
8050
8125
  }
8051
8126
  return {
8052
8127
  paths: {
@@ -8068,6 +8143,10 @@ var readWileConfig = (options = {}) => {
8068
8143
  ocModel: env.OC_MODEL,
8069
8144
  geminiOauthCredsB64: env.GEMINI_OAUTH_CREDS_B64,
8070
8145
  geminiApiKey: env.GEMINI_API_KEY,
8146
+ codexAuthJsonB64: env.CODEX_AUTH_JSON_B64,
8147
+ codexAuthJsonPath: env.CODEX_AUTH_JSON_PATH,
8148
+ codexApiKey: env.CODEX_API_KEY ?? env.OPENAI_API_KEY,
8149
+ codexModel: env.CODEX_MODEL,
8071
8150
  envProject
8072
8151
  }
8073
8152
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wile",
3
- "version": "0.6.8",
3
+ "version": "0.8.0",
4
4
  "description": "Autonomous AI coding agent that ships features while you sleep",
5
5
  "type": "module",
6
6
  "bin": {