wile 0.4.17 → 0.4.19

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
@@ -19,13 +19,14 @@ bunx wile config
19
19
  This creates:
20
20
 
21
21
  - `.wile/secrets/.env` for required credentials
22
- - `.wile/secrets/.env.project` for env vars forwarded into the container
22
+ - `.wile/.env.project` for env vars forwarded into the container (or set `WILE_ENV_PROJECT_PATH`)
23
23
  - `.wile/.gitignore` to ignore `secrets/`, `screenshots/`, and `logs/`
24
24
  - `.wile/prd.json` (empty) and `.wile/prd.json.example`
25
25
 
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), otherwise `CODING_AGENT=CC` uses Claude Code.
29
30
 
30
31
  ## Run Wile
31
32
 
@@ -2,7 +2,16 @@
2
2
  # Copy this file to .env and fill in your values
3
3
 
4
4
  # =============================================================================
5
- # REQUIRED - Claude Code Authentication (choose ONE)
5
+ # REQUIRED - Coding Agent Selection
6
+ # =============================================================================
7
+
8
+ # Choose which coding agent to run:
9
+ # - CC = Claude Code (default)
10
+ # - OC = OpenCode (OpenRouter)
11
+ CODING_AGENT=CC
12
+
13
+ # =============================================================================
14
+ # REQUIRED - Claude Code Authentication (CC only, choose ONE)
6
15
  # =============================================================================
7
16
 
8
17
  # OPTION 1: OAuth Token (RECOMMENDED - uses your Pro/Max subscription)
@@ -15,6 +24,15 @@ CC_CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxxxx
15
24
  # Only use this if you don't have a Pro/Max subscription
16
25
  # CC_ANTHROPIC_API_KEY=sk-ant-xxxxx
17
26
 
27
+ # =============================================================================
28
+ # REQUIRED - OpenCode Authentication (OC only)
29
+ # =============================================================================
30
+
31
+ # OpenRouter API key (used with OpenCode)
32
+ # OC_OPENROUTER_API_KEY=sk-or-xxxxx
33
+ # OpenRouter model id (Wile maps `glm-4.7` to `openrouter/z-ai/glm-4.7`)
34
+ # OC_MODEL=glm-4.7
35
+
18
36
  # =============================================================================
19
37
  # GitHub Authentication (required when WILE_REPO_SOURCE=github)
20
38
  # =============================================================================
@@ -48,6 +48,10 @@ ENV NODE_OPTIONS="--max-old-space-size=4096"
48
48
  # https://www.npmjs.com/package/@anthropic-ai/claude-code
49
49
  RUN npm install -g @anthropic-ai/claude-code
50
50
 
51
+ # Install OpenCode CLI
52
+ # https://www.npmjs.com/package/opencode-ai
53
+ RUN npm install -g opencode-ai@latest
54
+
51
55
  # CC_ANTHROPIC_API_KEY is passed at runtime via environment variable (mapped to ANTHROPIC_API_KEY in entrypoint)
52
56
  # Usage: claude --dangerously-skip-permissions
53
57
 
@@ -110,8 +110,11 @@ 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) or `OC` (OpenCode via OpenRouter) |
113
114
  | `CC_CLAUDE_CODE_OAUTH_TOKEN` | Yes* | OAuth token from `claude setup-token` (uses Pro/Max subscription) |
114
115
  | `CC_ANTHROPIC_API_KEY` | Yes* | API key (uses API credits - alternative to OAuth) |
116
+ | `OC_OPENROUTER_API_KEY` | Yes (OC) | OpenRouter API key for OpenCode |
117
+ | `OC_MODEL` | Yes (OC) | OpenRouter model id (set `glm-4.7` to target `openrouter/z-ai/glm-4.7`) |
115
118
  | `WILE_REPO_SOURCE` | No | `github` (default) or `local` |
116
119
  | `GITHUB_TOKEN` | Yes (github) | GitHub PAT with repo access |
117
120
  | `GITHUB_REPO_URL` | Yes (github) | HTTPS URL to repository |
@@ -119,7 +122,7 @@ For UI work, tell the agent how to verify:
119
122
  | `MAX_ITERATIONS` | No | Max loops (default: 25) |
120
123
  | `CC_CLAUDE_MODEL` | No | Claude model alias/name (default: sonnet) |
121
124
 
122
- *Either `CC_CLAUDE_CODE_OAUTH_TOKEN` or `CC_ANTHROPIC_API_KEY` is required, not both.
125
+ *Either `CC_CLAUDE_CODE_OAUTH_TOKEN` or `CC_ANTHROPIC_API_KEY` is required when `CODING_AGENT=CC`.
123
126
 
124
127
  ## Output Files
125
128
 
@@ -38,6 +38,10 @@ const prd = JSON.parse(fs.readFileSync(prdPath, 'utf8'));
38
38
  const stories = Array.isArray(prd.userStories) ? prd.userStories : [];
39
39
  const pending = stories.filter((story) => story.passes === false);
40
40
 
41
+ if (process.env.TEST_FORWARD) {
42
+ console.log(`Forwarded env: ${process.env.TEST_FORWARD}`);
43
+ }
44
+
41
45
  if (pending.length === 0) {
42
46
  console.log('No pending stories to complete in test mode.');
43
47
  process.exit(0);
@@ -67,10 +71,12 @@ NODE
67
71
  exit 0
68
72
  fi
69
73
 
74
+ CODING_AGENT="${CODING_AGENT:-CC}"
70
75
  REPO_SOURCE="${WILE_REPO_SOURCE:-github}"
71
76
  LOCAL_REPO_PATH="${WILE_LOCAL_REPO_PATH:-/home/wile/workspace/repo}"
72
77
  ADDITIONAL_INSTRUCTIONS_PATH="${WILE_ADDITIONAL_INSTRUCTIONS:-}"
73
78
 
79
+ echo " Agent: $CODING_AGENT"
74
80
  if [ "$REPO_SOURCE" = "local" ]; then
75
81
  if [ ! -d "$LOCAL_REPO_PATH" ]; then
76
82
  echo "ERROR: WILE_LOCAL_REPO_PATH does not exist: $LOCAL_REPO_PATH"
@@ -83,53 +89,74 @@ else
83
89
  : "${GITHUB_TOKEN:?GITHUB_TOKEN is required}"
84
90
  fi
85
91
 
86
- # Authentication: Either CC_CLAUDE_CODE_OAUTH_TOKEN (Pro/Max subscription) or CC_ANTHROPIC_API_KEY (API credits)
87
- if [ -z "$CC_CLAUDE_CODE_OAUTH_TOKEN" ] && [ -z "$CC_ANTHROPIC_API_KEY" ]; then
88
- echo "ERROR: Either CC_CLAUDE_CODE_OAUTH_TOKEN or CC_ANTHROPIC_API_KEY is required"
89
- echo ""
90
- echo " CC_CLAUDE_CODE_OAUTH_TOKEN - Uses your Pro/Max subscription (recommended)"
91
- echo " CC_ANTHROPIC_API_KEY - Uses API credits (pay per token)"
92
- echo ""
93
- echo "Run 'claude setup-token' on your local machine to get an OAuth token."
94
- exit 1
92
+ # Authentication for selected coding agent
93
+ if [ "$CODING_AGENT" = "OC" ]; then
94
+ if [ -z "$OC_OPENROUTER_API_KEY" ]; then
95
+ echo "ERROR: OC_OPENROUTER_API_KEY is required for OpenCode"
96
+ exit 1
97
+ fi
98
+ if [ -z "$OC_MODEL" ]; then
99
+ echo "ERROR: OC_MODEL is required for OpenCode"
100
+ exit 1
101
+ fi
102
+ else
103
+ if [ -z "$CC_CLAUDE_CODE_OAUTH_TOKEN" ] && [ -z "$CC_ANTHROPIC_API_KEY" ]; then
104
+ echo "ERROR: Either CC_CLAUDE_CODE_OAUTH_TOKEN or CC_ANTHROPIC_API_KEY is required"
105
+ echo ""
106
+ echo " CC_CLAUDE_CODE_OAUTH_TOKEN - Uses your Pro/Max subscription (recommended)"
107
+ echo " CC_ANTHROPIC_API_KEY - Uses API credits (pay per token)"
108
+ echo ""
109
+ echo "Run 'claude setup-token' on your local machine to get an OAuth token."
110
+ exit 1
111
+ fi
95
112
  fi
96
113
 
97
114
  MAX_ITERATIONS=${MAX_ITERATIONS:-25}
98
115
  SCRIPT_DIR="/home/wile/scripts"
99
116
  WORKSPACE="/home/wile/workspace"
100
117
 
101
- if [ "${WILE_MOCK_CLAUDE:-}" = "true" ]; then
118
+ if [ "${WILE_MOCK_CLAUDE:-}" = "true" ] && [ "$CODING_AGENT" = "CC" ]; then
102
119
  echo " Claude: Mocked"
103
120
  MOCK_BIN="/home/wile/mock-bin"
104
121
  mkdir -p "$MOCK_BIN"
105
- cat > "$MOCK_BIN/claude" << 'MOCK'
106
- #!/bin/sh
107
- cat <<'JSON'
108
- {"type":"assistant","message":{"content":[{"type":"text","text":"ANSWER: 2\n<promise>COMPLETE</promise>\n"}]}}
109
- JSON
110
- MOCK
122
+ cp "$SCRIPT_DIR/mock-claude.sh" "$MOCK_BIN/claude"
111
123
  chmod +x "$MOCK_BIN/claude"
112
124
  export PATH="$MOCK_BIN:$PATH"
113
125
  fi
114
126
 
115
- # Set up Claude Code authentication
116
- if [ -n "$CC_CLAUDE_CODE_OAUTH_TOKEN" ]; then
117
- echo " Auth: OAuth (Pro/Max subscription)"
127
+ if [ "$CODING_AGENT" = "OC" ]; then
128
+ echo " Auth: OpenRouter (OpenCode)"
129
+ OPENCODE_DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/opencode"
130
+ mkdir -p "$OPENCODE_DATA_DIR"
131
+ cat > "$OPENCODE_DATA_DIR/auth.json" << OPENCODEAUTH
132
+ {
133
+ "openrouter": {
134
+ "type": "api",
135
+ "key": "$OC_OPENROUTER_API_KEY"
136
+ }
137
+ }
138
+ OPENCODEAUTH
139
+ chmod 600 "$OPENCODE_DATA_DIR/auth.json"
140
+ export OPENROUTER_API_KEY="$OC_OPENROUTER_API_KEY"
141
+ else
142
+ # Set up Claude Code authentication
143
+ if [ -n "$CC_CLAUDE_CODE_OAUTH_TOKEN" ]; then
144
+ echo " Auth: OAuth (Pro/Max subscription)"
118
145
 
119
- # Create required directories
120
- mkdir -p ~/.claude ~/.config/claude
146
+ # Create required directories
147
+ mkdir -p ~/.claude ~/.config/claude
121
148
 
122
- # Create ~/.claude.json (THE CRITICAL FILE!)
123
- # Without this, Claude Code thinks it's a fresh install and breaks
124
- cat > ~/.claude.json << 'CLAUDEJSON'
149
+ # Create ~/.claude.json (THE CRITICAL FILE!)
150
+ # Without this, Claude Code thinks it's a fresh install and breaks
151
+ cat > ~/.claude.json << 'CLAUDEJSON'
125
152
  {
126
153
  "hasCompletedOnboarding": true,
127
154
  "theme": "dark"
128
155
  }
129
156
  CLAUDEJSON
130
157
 
131
- # Create credentials file with the OAuth token
132
- cat > ~/.claude/.credentials.json << CREDSJSON
158
+ # Create credentials file with the OAuth token
159
+ cat > ~/.claude/.credentials.json << CREDSJSON
133
160
  {
134
161
  "claudeAiOauth": {
135
162
  "accessToken": "$CC_CLAUDE_CODE_OAUTH_TOKEN",
@@ -140,14 +167,15 @@ CLAUDEJSON
140
167
  }
141
168
  CREDSJSON
142
169
 
143
- # Copy to alternate location too
144
- cp ~/.claude/.credentials.json ~/.config/claude/.credentials.json
170
+ # Copy to alternate location too
171
+ cp ~/.claude/.credentials.json ~/.config/claude/.credentials.json
145
172
 
146
- # Ensure ANTHROPIC_API_KEY is not set (it overrides OAuth)
147
- unset ANTHROPIC_API_KEY
148
- else
149
- echo " Auth: API Key (credits)"
150
- export ANTHROPIC_API_KEY="$CC_ANTHROPIC_API_KEY"
173
+ # Ensure ANTHROPIC_API_KEY is not set (it overrides OAuth)
174
+ unset ANTHROPIC_API_KEY
175
+ else
176
+ echo " Auth: API Key (credits)"
177
+ export ANTHROPIC_API_KEY="$CC_ANTHROPIC_API_KEY"
178
+ fi
151
179
  fi
152
180
 
153
181
  if [ "$REPO_SOURCE" = "local" ]; then
@@ -47,6 +47,10 @@ const extractToolUse = (content) => {
47
47
 
48
48
  rl.on("line", (line) => {
49
49
  if (!line.trim()) return;
50
+ if (process.env.WILE_STREAM_JSON === "true") {
51
+ writeLine(line);
52
+ return;
53
+ }
50
54
  let payload;
51
55
  try {
52
56
  payload = JSON.parse(line);
@@ -0,0 +1,49 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ COUNT_FILE="/tmp/wile-claude-mock-count"
5
+ if [ ! -f "$COUNT_FILE" ]; then
6
+ echo "0" > "$COUNT_FILE"
7
+ fi
8
+
9
+ COUNT=$(cat "$COUNT_FILE")
10
+ NEXT_COUNT=$((COUNT + 1))
11
+ echo "$NEXT_COUNT" > "$COUNT_FILE"
12
+
13
+ if [ "${WILE_MOCK_MODE:-}" = "preflight_fail" ]; then
14
+ PROGRESS_PATH=".wile/progress.txt"
15
+ if [ -f "$PROGRESS_PATH" ]; then
16
+ :
17
+ else
18
+ printf '%s\n' "# Wile Progress Log" "" "## Codebase Patterns" "" "---" > "$PROGRESS_PATH"
19
+ fi
20
+
21
+ DATE=$(date +%Y-%m-%d)
22
+ cat >> "$PROGRESS_PATH" <<EOF
23
+
24
+ ---
25
+
26
+ ## ${DATE} - PREFLIGHT FAILED
27
+
28
+ **Checks run:**
29
+ - Mocked preflight check
30
+
31
+ **Failures:**
32
+ - Mocked preflight failure (WILE_MOCK_MODE=preflight_fail)
33
+ EOF
34
+
35
+ cat <<'JSON'
36
+ {"type":"assistant","message":{"content":[{"type":"text","text":"<promise>PREFLIGHT_FAILED</promise>\n"}]}}
37
+ JSON
38
+ exit 0
39
+ fi
40
+
41
+ if [ "$COUNT" -eq 0 ]; then
42
+ cat <<'JSON'
43
+ {"type":"assistant","message":{"content":[{"type":"text","text":"ANSWER: 2\n"}]}}
44
+ JSON
45
+ else
46
+ cat <<'JSON'
47
+ {"type":"assistant","message":{"content":[{"type":"text","text":"<promise>COMPLETE</promise>\n"}]}}
48
+ JSON
49
+ fi
@@ -0,0 +1,49 @@
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 formatToolLine = (tool, title, input) => {
14
+ if (title) return `[tool] ${tool}: ${title}`;
15
+ if (input && Object.keys(input).length > 0) {
16
+ return `[tool] ${tool}: ${JSON.stringify(input)}`;
17
+ }
18
+ return `[tool] ${tool}`;
19
+ };
20
+
21
+ rl.on("line", (line) => {
22
+ if (!line.trim()) return;
23
+ let payload;
24
+ try {
25
+ payload = JSON.parse(line);
26
+ } catch {
27
+ return;
28
+ }
29
+
30
+ if (payload.type === "text" && payload.part?.text) {
31
+ process.stdout.write(payload.part.text);
32
+ return;
33
+ }
34
+
35
+ if (payload.type === "tool_use" && payload.part) {
36
+ const tool = payload.part.tool ?? "tool";
37
+ const title = payload.part.state?.title ?? "";
38
+ const input = payload.part.state?.input ?? {};
39
+ writeLine(formatToolLine(tool, title, input));
40
+ return;
41
+ }
42
+
43
+ if (payload.type === "error" && payload.error) {
44
+ const name = payload.error?.name ?? "error";
45
+ const message = payload.error?.data?.message ?? payload.error?.message ?? "";
46
+ writeLine(`[error] ${name}${message ? `: ${message}` : ""}`);
47
+ return;
48
+ }
49
+ });
@@ -0,0 +1,56 @@
1
+ # Wile Preflight (Iteration 0)
2
+
3
+ You are running the preflight phase for a Wile autonomous coding session.
4
+
5
+ ## Tasks
6
+
7
+ 1. Verify `.wile/prd.json` exists and is valid JSON:
8
+
9
+ ```bash
10
+ cat .wile/prd.json
11
+ ```
12
+
13
+ 2. If `.wile/preflight.md` does **not** exist, do nothing else. Do not modify files and do not print extra output.
14
+
15
+ 3. If `.wile/preflight.md` exists, read it and follow the checks exactly, in order. Run any commands listed in code blocks. If it describes a check without a command, perform the check and note the result.
16
+
17
+ 4. If **any** check fails or cannot be completed:
18
+ - Append a new entry to `.wile/progress.txt` describing what failed and why, using this format:
19
+
20
+ ```markdown
21
+ ---
22
+
23
+ ## [DATE] - PREFLIGHT FAILED
24
+
25
+ **Checks run:**
26
+ - ...
27
+
28
+ **Failures:**
29
+ - ...
30
+ ```
31
+
32
+ - If GitHub is configured (`WILE_REPO_SOURCE=github` or `GITHUB_REPO_URL` is set), commit and push the progress update:
33
+
34
+ ```bash
35
+ git add .wile/progress.txt
36
+ git commit -m "chore: preflight failed"
37
+ git push
38
+ ```
39
+ - Respond with exactly:
40
+
41
+ ```
42
+ <promise>PREFLIGHT_FAILED</promise>
43
+ ```
44
+
45
+ The entire response must be exactly that single line. No other text before or after. No extra lines. No markdown. No backticks. No code blocks.
46
+
47
+ 5. If all checks pass, respond with exactly:
48
+ ```
49
+ <promise>COMPLETE</promise>
50
+ ```
51
+ The entire response must be exactly that single line. No other text before or after. No extra lines. No markdown. No backticks. No code blocks.
52
+
53
+ ## Notes
54
+
55
+ - Preflight may have side effects if the checks require them.
56
+ - Do not change any files unless a failure must be recorded in `.wile/progress.txt`.
@@ -35,8 +35,8 @@ Pick the highest priority story where `passes: false` and implement it completel
35
35
  Run the project's tests and type checking:
36
36
  ```bash
37
37
  # Try common commands (adapt to the project)
38
- npm run typecheck || npm run tsc || npx tsc --noEmit || true
39
- npm test || npm run test || true
38
+ npm run typecheck || npm run tsc || npx tsc --noEmit
39
+ npm test || npm run test
40
40
  ```
41
41
 
42
42
  If tests or typecheck fail, fix the issues before proceeding.
@@ -58,6 +58,9 @@ Set `passes: true` for the completed story:
58
58
  **Implemented:**
59
59
  - What was done
60
60
 
61
+ **Verification:**
62
+ - Command(s) run and pass/fail status
63
+
61
64
  **Files changed:**
62
65
  - file1.ts
63
66
  - file2.ts
@@ -87,6 +90,7 @@ After completing steps 1-8, check if ALL stories in `.wile/prd.json` have `passe
87
90
  ```
88
91
  <promise>COMPLETE</promise>
89
92
  ```
93
+ The entire response must be exactly that single line. No other text before or after. No extra lines. No markdown. No backticks. No code blocks.
90
94
 
91
95
  **If there are still stories with `passes: false`**, end your response normally. The loop will call you again for the next story.
92
96
 
@@ -103,6 +107,8 @@ After completing steps 1-8, check if ALL stories in `.wile/prd.json` have `passe
103
107
  9. **Integration tests must validate real system behavior, not just the harness**
104
108
  10. **If you discover reusable, module-specific guidance, add it to the nearest AGENTS.md**
105
109
  Note: Never update .wile/AGENTS.md.
110
+ 11. **Definition of done** - Do not set `passes: true` unless each acceptance criterion has a concrete verification and all verifications passed. If any verification fails or can’t run, leave `passes: false` and explain why in `.wile/progress.txt`.
111
+ 12. **No verification section means no pass** - If the progress entry lacks a **Verification** section, do not mark `passes: true`.
106
112
 
107
113
  ## Common Patterns
108
114
 
@@ -47,7 +47,7 @@ EOF
47
47
  cat > "$BIN_DIR/claude" <<'EOF'
48
48
  #!/bin/sh
49
49
  cat > "$CLAUDE_CAPTURE"
50
- printf '%s\n' '{"type":"assistant","message":{"content":[{"type":"text","text":"<promise>COMPLETE</promise>\\n"}]}}'
50
+ printf '%s\n' '{"type":"assistant","message":{"content":[{"type":"text","text":"<promise>COMPLETE</promise>\n"}]}}'
51
51
  EOF
52
52
  chmod +x "$BIN_DIR/claude"
53
53
 
@@ -0,0 +1,52 @@
1
+ #!/bin/sh
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR=$(cd "$(dirname "$0")/../../.." && pwd)
5
+ AGENT_DIR="$ROOT_DIR/packages/agent"
6
+
7
+ docker build -t wile-agent:local "$AGENT_DIR" >/dev/null
8
+
9
+ TMP_DIR=$(mktemp -d /tmp/wile-env-project-XXXXXX)
10
+ cleanup() {
11
+ rm -rf "$TMP_DIR"
12
+ }
13
+ trap cleanup EXIT INT TERM
14
+
15
+ mkdir -p "$TMP_DIR/.wile/secrets"
16
+ cat > "$TMP_DIR/.wile/prd.json" <<'JSON'
17
+ {
18
+ "userStories": [
19
+ {
20
+ "id": "US-TEST-ENV-001",
21
+ "title": "Env project forward test",
22
+ "acceptanceCriteria": ["n/a"],
23
+ "priority": 1,
24
+ "passes": false
25
+ }
26
+ ]
27
+ }
28
+ JSON
29
+
30
+ printf "secrets/\nscreenshots/\nlogs/\n" > "$TMP_DIR/.wile/.gitignore"
31
+
32
+ printf "TEST_FORWARD=ok\n" > "$TMP_DIR/.env.custom"
33
+
34
+ cat > "$TMP_DIR/.wile/secrets/.env" <<'ENV'
35
+ CODING_AGENT=CC
36
+ WILE_ENV_PROJECT_PATH=.env.custom
37
+ ENV
38
+
39
+ export WILE_AGENT_DIR="$AGENT_DIR"
40
+
41
+ cd "$TMP_DIR"
42
+ node "$ROOT_DIR/packages/cli/dist/cli.js" run --test --max-iterations 1 >/dev/null 2>&1
43
+
44
+ LOG_FILE=$(ls "$TMP_DIR/.wile/logs"/run-*.log | head -n 1)
45
+ if [ -z "$LOG_FILE" ]; then
46
+ echo "error: expected run log file" >&2
47
+ exit 1
48
+ fi
49
+
50
+ grep -q "Forwarded env: ok" "$LOG_FILE"
51
+
52
+ echo "test-env-project-docker: ok"
@@ -0,0 +1,134 @@
1
+ #!/bin/sh
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR=$(cd "$(dirname "$0")/../../.." && pwd)
5
+ AGENT_DIR="$ROOT_DIR/packages/agent"
6
+
7
+ docker build -t wile-agent:local "$AGENT_DIR" >/dev/null
8
+
9
+ run_failure_case() {
10
+ TMP_DIR=$(mktemp -d /tmp/wile-preflight-fail-XXXXXX)
11
+ cleanup() {
12
+ rm -rf "$TMP_DIR"
13
+ }
14
+ trap cleanup EXIT INT TERM
15
+
16
+ mkdir -p "$TMP_DIR/.wile"
17
+ cat > "$TMP_DIR/.wile/prd.json" <<'JSON'
18
+ {
19
+ "userStories": [
20
+ {
21
+ "id": "US-TEST-001",
22
+ "title": "Preflight fail test",
23
+ "acceptanceCriteria": ["n/a"],
24
+ "priority": 1,
25
+ "passes": false
26
+ }
27
+ ]
28
+ }
29
+ JSON
30
+
31
+ cat > "$TMP_DIR/.wile/progress.txt" <<'TXT'
32
+ # Wile Progress Log
33
+
34
+ ## Codebase Patterns
35
+
36
+ ---
37
+ TXT
38
+
39
+ cat > "$TMP_DIR/.wile/preflight.md" <<'MD'
40
+ <!--
41
+ Use bullet points for preflight checks, e.g.
42
+ - Confirm SUPABASE_DB_URL is set.
43
+ - Run `supabase db reset --db-url "$SUPABASE_DB_URL"`.
44
+ -->
45
+ MD
46
+
47
+ OUTPUT_FILE="$TMP_DIR/output.txt"
48
+
49
+ set +e
50
+ docker run --rm \
51
+ -e CODING_AGENT=CC \
52
+ -e CC_ANTHROPIC_API_KEY=dummy-key \
53
+ -e WILE_REPO_SOURCE=local \
54
+ -e WILE_LOCAL_REPO_PATH=/home/wile/workspace/repo \
55
+ -e MAX_ITERATIONS=1 \
56
+ -e WILE_MOCK_CLAUDE=true \
57
+ -e WILE_MOCK_MODE=preflight_fail \
58
+ -v "$TMP_DIR:/home/wile/workspace/repo" \
59
+ wile-agent:local > "$OUTPUT_FILE" 2>&1
60
+ EXIT_CODE=$?
61
+ set -e
62
+
63
+ if [ "$EXIT_CODE" -eq 0 ]; then
64
+ echo "error: expected non-zero exit code for preflight failure" >&2
65
+ exit 1
66
+ fi
67
+
68
+ grep -q "PREFLIGHT FAILED - Cannot continue" "$OUTPUT_FILE"
69
+ grep -q "PREFLIGHT FAILED" "$TMP_DIR/.wile/progress.txt"
70
+
71
+ rm -rf "$TMP_DIR"
72
+ trap - EXIT INT TERM
73
+ }
74
+
75
+ run_success_case() {
76
+ TMP_DIR=$(mktemp -d /tmp/wile-preflight-success-XXXXXX)
77
+ cleanup() {
78
+ rm -rf "$TMP_DIR"
79
+ }
80
+ trap cleanup EXIT INT TERM
81
+
82
+ mkdir -p "$TMP_DIR/.wile"
83
+ cat > "$TMP_DIR/.wile/prd.json" <<'JSON'
84
+ {
85
+ "userStories": [
86
+ {
87
+ "id": "US-TEST-002",
88
+ "title": "Preflight success test",
89
+ "acceptanceCriteria": ["n/a"],
90
+ "priority": 1,
91
+ "passes": false
92
+ }
93
+ ]
94
+ }
95
+ JSON
96
+
97
+ cat > "$TMP_DIR/.wile/preflight.md" <<'MD'
98
+ <!--
99
+ Use bullet points for preflight checks, e.g.
100
+ - Confirm SUPABASE_DB_URL is set.
101
+ - Run `supabase db reset --db-url "$SUPABASE_DB_URL"`.
102
+ -->
103
+ MD
104
+
105
+ OUTPUT_FILE="$TMP_DIR/output.txt"
106
+
107
+ set +e
108
+ docker run --rm \
109
+ -e CODING_AGENT=CC \
110
+ -e CC_ANTHROPIC_API_KEY=dummy-key \
111
+ -e WILE_REPO_SOURCE=local \
112
+ -e WILE_LOCAL_REPO_PATH=/home/wile/workspace/repo \
113
+ -e MAX_ITERATIONS=1 \
114
+ -e WILE_MOCK_CLAUDE=true \
115
+ -v "$TMP_DIR:/home/wile/workspace/repo" \
116
+ wile-agent:local > "$OUTPUT_FILE" 2>&1
117
+ EXIT_CODE=$?
118
+ set -e
119
+
120
+ if [ "$EXIT_CODE" -ne 0 ]; then
121
+ echo "error: expected zero exit code for preflight success" >&2
122
+ exit 1
123
+ fi
124
+
125
+ grep -q "Preflight complete. Starting main loop..." "$OUTPUT_FILE"
126
+
127
+ rm -rf "$TMP_DIR"
128
+ trap - EXIT INT TERM
129
+ }
130
+
131
+ run_failure_case
132
+ run_success_case
133
+
134
+ echo "test-preflight-docker: ok"
@@ -0,0 +1,81 @@
1
+ #!/bin/bash
2
+ #
3
+ # Wile - Preflight check (single run)
4
+ #
5
+
6
+ set -e
7
+
8
+ CODING_AGENT=${CODING_AGENT:-CC}
9
+ CLAUDE_MODEL=${CC_CLAUDE_MODEL:-sonnet}
10
+ OC_MODEL=${OC_MODEL:-glm-4.7}
11
+ if [[ "$OC_MODEL" != */* ]]; then
12
+ OC_MODEL="z-ai/$OC_MODEL"
13
+ fi
14
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
+ PREFLIGHT_PROMPT_FILE="$SCRIPT_DIR/prompt-preflight.md"
16
+ TEE_TARGET="${WILE_TEE_TARGET:-/dev/stderr}"
17
+ if ! ( : > "$TEE_TARGET" ) 2>/dev/null; then
18
+ TEE_TARGET="/dev/null"
19
+ fi
20
+
21
+ echo "══════════════════════════════════════════════════════"
22
+ echo " 🌵 WILE - Preflight"
23
+ echo "══════════════════════════════════════════════════════"
24
+ echo " Agent: $CODING_AGENT"
25
+ if [ "$CODING_AGENT" = "OC" ]; then
26
+ echo " Model: $OC_MODEL"
27
+ else
28
+ echo " Model: $CLAUDE_MODEL"
29
+ fi
30
+ echo " Prompt file: $PREFLIGHT_PROMPT_FILE"
31
+ echo "══════════════════════════════════════════════════════"
32
+ echo ""
33
+
34
+ if [ ! -f "$PREFLIGHT_PROMPT_FILE" ]; then
35
+ echo "ERROR: Preflight prompt file not found: $PREFLIGHT_PROMPT_FILE"
36
+ exit 1
37
+ fi
38
+
39
+ run_claude() {
40
+ local prompt_path="$1"
41
+ cat "$prompt_path" \
42
+ | claude --model "$CLAUDE_MODEL" --print --output-format stream-json --verbose --dangerously-skip-permissions \
43
+ | node "$SCRIPT_DIR/claude-stream.js"
44
+ }
45
+
46
+ run_opencode() {
47
+ local prompt_path="$1"
48
+ cat "$prompt_path" \
49
+ | opencode run --format json --model "openrouter/$OC_MODEL" \
50
+ | node "$SCRIPT_DIR/opencode-stream.js"
51
+ }
52
+
53
+ run_agent() {
54
+ local prompt_path="$1"
55
+ if [ "$CODING_AGENT" = "OC" ]; then
56
+ run_opencode "$prompt_path"
57
+ else
58
+ run_claude "$prompt_path"
59
+ fi
60
+ }
61
+
62
+ OUTPUT=$(run_agent "$PREFLIGHT_PROMPT_FILE" | tee "$TEE_TARGET") || true
63
+ CLEAN_OUTPUT=$(printf '%s' "$OUTPUT" | tr -d '\r' | sed -e 's/[[:space:]]*$//')
64
+ if printf '%s\n' "$CLEAN_OUTPUT" | grep -q -E '^[[:space:]]*<promise>PREFLIGHT_FAILED</promise>[[:space:]]*$'; then
65
+ if printf '%s' "$CLEAN_OUTPUT" | grep -F '```' >/dev/null 2>&1; then
66
+ :
67
+ elif printf '%s' "$CLEAN_OUTPUT" | grep -F '`<promise>PREFLIGHT_FAILED</promise>`' >/dev/null 2>&1; then
68
+ :
69
+ else
70
+ echo ""
71
+ echo "══════════════════════════════════════════════════════"
72
+ echo " ❌ PREFLIGHT FAILED"
73
+ echo "══════════════════════════════════════════════════════"
74
+ exit 2
75
+ fi
76
+ fi
77
+
78
+ echo ""
79
+ echo "══════════════════════════════════════════════════════"
80
+ echo " ✅ PREFLIGHT COMPLETE"
81
+ echo "══════════════════════════════════════════════════════"
@@ -7,17 +7,32 @@
7
7
  set -e
8
8
 
9
9
  MAX_ITERATIONS=${1:-25}
10
+ CODING_AGENT=${CODING_AGENT:-CC}
10
11
  CLAUDE_MODEL=${CC_CLAUDE_MODEL:-sonnet}
12
+ OC_MODEL=${OC_MODEL:-glm-4.7}
13
+ if [[ "$OC_MODEL" != */* ]]; then
14
+ OC_MODEL="z-ai/$OC_MODEL"
15
+ fi
11
16
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
17
  PROMPT_FILE="$SCRIPT_DIR/prompt.md"
18
+ PREFLIGHT_PROMPT_FILE="$SCRIPT_DIR/prompt-preflight.md"
13
19
  SETUP_PROMPT_FILE="$SCRIPT_DIR/prompt-setup.md"
14
20
  ADDITIONAL_PROMPT_FILE="${WILE_ADDITIONAL_INSTRUCTIONS:-}"
21
+ TEE_TARGET="${WILE_TEE_TARGET:-/dev/stderr}"
22
+ if ! ( : > "$TEE_TARGET" ) 2>/dev/null; then
23
+ TEE_TARGET="/dev/null"
24
+ fi
15
25
 
16
26
  echo "══════════════════════════════════════════════════════"
17
27
  echo " 🌵 WILE - Autonomous Coding Agent"
18
28
  echo "══════════════════════════════════════════════════════"
29
+ echo " Agent: $CODING_AGENT"
19
30
  echo " Max iterations: $MAX_ITERATIONS"
20
- echo " Model: $CLAUDE_MODEL"
31
+ if [ "$CODING_AGENT" = "OC" ]; then
32
+ echo " Model: $OC_MODEL"
33
+ else
34
+ echo " Model: $CLAUDE_MODEL"
35
+ fi
21
36
  echo " Prompt file: $PROMPT_FILE"
22
37
  echo "══════════════════════════════════════════════════════"
23
38
  echo ""
@@ -39,22 +54,62 @@ if [ -n "$ADDITIONAL_PROMPT_FILE" ] && [ -f "$ADDITIONAL_PROMPT_FILE" ]; then
39
54
  fi
40
55
  fi
41
56
 
57
+ run_claude() {
58
+ local prompt_path="$1"
59
+ cat "$prompt_path" \
60
+ | claude --model "$CLAUDE_MODEL" --print --output-format stream-json --verbose --dangerously-skip-permissions \
61
+ | node "$SCRIPT_DIR/claude-stream.js"
62
+ }
63
+
64
+ run_opencode() {
65
+ local prompt_path="$1"
66
+ cat "$prompt_path" \
67
+ | opencode run --format json --model "openrouter/$OC_MODEL" \
68
+ | node "$SCRIPT_DIR/opencode-stream.js"
69
+ }
70
+
71
+ run_agent() {
72
+ local prompt_path="$1"
73
+ if [ "$CODING_AGENT" = "OC" ]; then
74
+ run_opencode "$prompt_path"
75
+ else
76
+ run_claude "$prompt_path"
77
+ fi
78
+ }
79
+
42
80
  # ════════════════════════════════════════════════════════════
43
- # ITERATION 0: Setup
81
+ # ITERATION 0: Preflight / Setup
44
82
  # ════════════════════════════════════════════════════════════
45
83
  echo ""
46
84
  echo "══════════════════════════════════════════════════════"
47
- echo " Iteration 0 - Setup"
85
+ echo " Iteration 0 - Preflight"
48
86
  echo "══════════════════════════════════════════════════════"
49
87
  echo ""
50
88
 
51
- if [ -f "$SETUP_PROMPT_FILE" ]; then
52
- OUTPUT=$(
53
- cat "$SETUP_PROMPT_FILE" \
54
- | claude --model "$CLAUDE_MODEL" --print --output-format stream-json --verbose --dangerously-skip-permissions \
55
- | node "$SCRIPT_DIR/claude-stream.js" \
56
- | tee /dev/stderr
57
- ) || true
89
+ if [ -f "$PREFLIGHT_PROMPT_FILE" ]; then
90
+ OUTPUT=$(run_agent "$PREFLIGHT_PROMPT_FILE" | tee "$TEE_TARGET") || true
91
+
92
+ # Check if preflight failed critically (tag must be on its own line; reject backticks/code fences)
93
+ CLEAN_OUTPUT=$(printf '%s' "$OUTPUT" | tr -d '\r' | sed -e 's/[[:space:]]*$//')
94
+ if printf '%s\n' "$CLEAN_OUTPUT" | grep -q -E '^[[:space:]]*<promise>PREFLIGHT_FAILED</promise>[[:space:]]*$'; then
95
+ if printf '%s' "$CLEAN_OUTPUT" | grep -F '```' >/dev/null 2>&1; then
96
+ :
97
+ elif printf '%s' "$CLEAN_OUTPUT" | grep -F '`<promise>PREFLIGHT_FAILED</promise>`' >/dev/null 2>&1; then
98
+ :
99
+ else
100
+ echo ""
101
+ echo "══════════════════════════════════════════════════════"
102
+ echo " ❌ PREFLIGHT FAILED - Cannot continue"
103
+ echo "══════════════════════════════════════════════════════"
104
+ exit 2
105
+ fi
106
+ fi
107
+
108
+ echo ""
109
+ echo "Preflight complete. Starting main loop..."
110
+ sleep 2
111
+ elif [ -f "$SETUP_PROMPT_FILE" ]; then
112
+ OUTPUT=$(run_agent "$SETUP_PROMPT_FILE" | tee "$TEE_TARGET") || true
58
113
 
59
114
  # Check if setup failed critically
60
115
  if echo "$OUTPUT" | grep -q "<promise>SETUP_FAILED</promise>"; then
@@ -85,20 +140,22 @@ for i in $(seq 1 $MAX_ITERATIONS); do
85
140
  # Pipe prompt to Claude Code
86
141
  # --dangerously-skip-permissions allows autonomous operation
87
142
  # Capture output while also displaying it (tee to stderr)
88
- OUTPUT=$(
89
- cat "$PROMPT_FILE" \
90
- | claude --model "$CLAUDE_MODEL" --print --output-format stream-json --verbose --dangerously-skip-permissions \
91
- | node "$SCRIPT_DIR/claude-stream.js" \
92
- | tee /dev/stderr
93
- ) || true
94
-
95
- # Check for completion signal
96
- if echo "$OUTPUT" | grep -q "<promise>COMPLETE</promise>"; then
143
+ OUTPUT=$(run_agent "$PROMPT_FILE" | tee "$TEE_TARGET") || true
144
+
145
+ # Check for completion signal (tag must be on its own line; reject backticks/code fences)
146
+ CLEAN_OUTPUT=$(printf '%s' "$OUTPUT" | tr -d '\r' | sed -e 's/[[:space:]]*$//')
147
+ if printf '%s\n' "$CLEAN_OUTPUT" | grep -q -E '^[[:space:]]*<promise>COMPLETE</promise>[[:space:]]*$'; then
148
+ if printf '%s' "$CLEAN_OUTPUT" | grep -F '```' >/dev/null 2>&1; then
149
+ :
150
+ elif printf '%s' "$CLEAN_OUTPUT" | grep -F '`<promise>COMPLETE</promise>`' >/dev/null 2>&1; then
151
+ :
152
+ else
97
153
  echo ""
98
154
  echo "══════════════════════════════════════════════════════"
99
155
  echo " ✅ ALL TASKS COMPLETE"
100
156
  echo "══════════════════════════════════════════════════════"
101
157
  exit 0
158
+ fi
102
159
  fi
103
160
 
104
161
  echo ""
package/dist/cli.js CHANGED
@@ -7442,7 +7442,7 @@ var {
7442
7442
 
7443
7443
  // src/cli.ts
7444
7444
  import { readFileSync as readFileSync3 } from "node:fs";
7445
- import { resolve as resolve2 } from "node:path";
7445
+ import { resolve as resolve3 } from "node:path";
7446
7446
 
7447
7447
  // src/commands/config.ts
7448
7448
  var import_prompts = __toESM(require_prompts3(), 1);
@@ -7544,6 +7544,7 @@ var prdExample = {
7544
7544
  var tips = {
7545
7545
  oauth: "Tip: run 'claude setup-token' on your machine to generate an OAuth token (uses Pro/Max subscription).",
7546
7546
  apiKey: "Tip: create an Anthropic API key in the console (uses API credits).",
7547
+ openrouter: "Tip: create an OpenRouter API key for OpenCode (used for z-ai/glm-4.7).",
7547
7548
  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)."
7548
7549
  };
7549
7550
  var readEnvFile = async (path) => {
@@ -7554,7 +7555,7 @@ var readEnvFile = async (path) => {
7554
7555
  return import_dotenv.default.parse(contents);
7555
7556
  };
7556
7557
  var ensureGitignore = async (path) => {
7557
- const entries = ["secrets/", "screenshots/", "logs/"];
7558
+ const entries = ["secrets/", "screenshots/", "logs/", ".env.project"];
7558
7559
  if (!existsSync(path)) {
7559
7560
  await writeFile(path, entries.join(`
7560
7561
  `) + `
@@ -7616,53 +7617,82 @@ var runConfig = async () => {
7616
7617
  const wileDir = join(cwd, ".wile");
7617
7618
  const secretsDir = join(wileDir, "secrets");
7618
7619
  const envPath = join(secretsDir, ".env");
7619
- const envProjectPath = join(secretsDir, ".env.project");
7620
+ const envProjectPath = join(wileDir, ".env.project");
7620
7621
  const gitignorePath = join(wileDir, ".gitignore");
7621
7622
  const prdPath = join(wileDir, "prd.json");
7622
7623
  const prdExamplePath = join(wileDir, "prd.json.example");
7623
7624
  const additionalInstructionsPath = join(wileDir, "additional-instructions.md");
7625
+ const preflightPath = join(wileDir, "preflight.md");
7624
7626
  const agentsPath = join(wileDir, "AGENTS.md");
7625
7627
  await mkdir(secretsDir, { recursive: true });
7626
7628
  const existingEnv = await readEnvFile(envPath);
7627
- await prompt({
7629
+ const codingAgentResponse = await prompt({
7628
7630
  type: "select",
7629
7631
  name: "codingAgent",
7630
7632
  message: "Select coding agent",
7631
- choices: [{ title: "Claude Code (CC)", value: "CC" }],
7632
- initial: 0
7633
- });
7634
- const authDefault = existingEnv.CC_CLAUDE_CODE_OAUTH_TOKEN ? "oauth" : existingEnv.CC_ANTHROPIC_API_KEY ? "apiKey" : "oauth";
7635
- const authResponse = await prompt({
7636
- type: "select",
7637
- name: "authMethod",
7638
- message: "Claude Code authentication",
7639
7633
  choices: [
7640
- { title: "OAuth token (Pro/Max subscription)", value: "oauth" },
7641
- { title: "API key (Anthropic credits)", value: "apiKey" }
7634
+ { title: "Claude Code (CC)", value: "CC" },
7635
+ { title: "OpenCode (OC)", value: "OC" }
7642
7636
  ],
7643
- initial: authDefault === "apiKey" ? 1 : 0
7644
- });
7645
- const authMethod = authResponse.authMethod;
7646
- console.log("");
7647
- console.log(authMethod === "oauth" ? tips.oauth : tips.apiKey);
7648
- console.log("");
7649
- const authValueResponse = await prompt({
7650
- type: "password",
7651
- name: "authValue",
7652
- message: authMethod === "oauth" ? "Claude Code OAuth token (press enter to keep existing)" : "Anthropic API key (press enter to keep existing)",
7653
- initial: authMethod === "oauth" ? existingEnv.CC_CLAUDE_CODE_OAUTH_TOKEN ?? "" : existingEnv.CC_ANTHROPIC_API_KEY ?? ""
7654
- });
7655
- const defaultModelResponse = await prompt({
7656
- type: "select",
7657
- name: "model",
7658
- message: "Default Claude model",
7659
- choices: [
7660
- { title: "sonnet", value: "sonnet" },
7661
- { title: "opus", value: "opus" },
7662
- { title: "haiku", value: "haiku" }
7663
- ],
7664
- initial: existingEnv.CC_CLAUDE_MODEL === "opus" ? 1 : existingEnv.CC_CLAUDE_MODEL === "haiku" ? 2 : 0
7637
+ initial: existingEnv.CODING_AGENT === "OC" ? 1 : 0
7665
7638
  });
7639
+ const codingAgent = codingAgentResponse.codingAgent;
7640
+ let authMethod = null;
7641
+ let authValueResponse = {};
7642
+ let defaultModelResponse = {};
7643
+ let ocKeyResponse = {};
7644
+ let ocModelResponse = {};
7645
+ if (codingAgent === "CC") {
7646
+ const authDefault = existingEnv.CC_CLAUDE_CODE_OAUTH_TOKEN ? "oauth" : existingEnv.CC_ANTHROPIC_API_KEY ? "apiKey" : "oauth";
7647
+ const authResponse = await prompt({
7648
+ type: "select",
7649
+ name: "authMethod",
7650
+ message: "Claude Code authentication",
7651
+ choices: [
7652
+ { title: "OAuth token (Pro/Max subscription)", value: "oauth" },
7653
+ { title: "API key (Anthropic credits)", value: "apiKey" }
7654
+ ],
7655
+ initial: authDefault === "apiKey" ? 1 : 0
7656
+ });
7657
+ authMethod = authResponse.authMethod;
7658
+ console.log("");
7659
+ console.log(authMethod === "oauth" ? tips.oauth : tips.apiKey);
7660
+ console.log("");
7661
+ authValueResponse = await prompt({
7662
+ type: "password",
7663
+ name: "authValue",
7664
+ message: authMethod === "oauth" ? "Claude Code OAuth token (press enter to keep existing)" : "Anthropic API key (press enter to keep existing)",
7665
+ initial: authMethod === "oauth" ? existingEnv.CC_CLAUDE_CODE_OAUTH_TOKEN ?? "" : existingEnv.CC_ANTHROPIC_API_KEY ?? ""
7666
+ });
7667
+ defaultModelResponse = await prompt({
7668
+ type: "select",
7669
+ name: "model",
7670
+ message: "Default Claude model",
7671
+ choices: [
7672
+ { title: "sonnet", value: "sonnet" },
7673
+ { title: "opus", value: "opus" },
7674
+ { title: "haiku", value: "haiku" }
7675
+ ],
7676
+ initial: existingEnv.CC_CLAUDE_MODEL === "opus" ? 1 : existingEnv.CC_CLAUDE_MODEL === "haiku" ? 2 : 0
7677
+ });
7678
+ } else {
7679
+ console.log("");
7680
+ console.log(tips.openrouter);
7681
+ console.log("");
7682
+ ocKeyResponse = await prompt({
7683
+ type: "password",
7684
+ name: "ocKey",
7685
+ message: "OpenRouter API key (press enter to keep existing)",
7686
+ initial: existingEnv.OC_OPENROUTER_API_KEY ?? ""
7687
+ });
7688
+ ocModelResponse = await prompt({
7689
+ type: "select",
7690
+ name: "ocModel",
7691
+ message: "OpenCode model (OpenRouter)",
7692
+ choices: [{ title: "glm-4.7", value: "glm-4.7" }],
7693
+ initial: existingEnv.OC_MODEL === "glm-4.7" ? 0 : 0
7694
+ });
7695
+ }
7666
7696
  const repoSourceResponse = await prompt({
7667
7697
  type: "select",
7668
7698
  name: "repoSource",
@@ -7691,6 +7721,12 @@ var runConfig = async () => {
7691
7721
  message: "GitHub repo URL",
7692
7722
  initial: existingEnv.GITHUB_REPO_URL ?? ""
7693
7723
  }) : { repoUrl: undefined };
7724
+ const envProjectPathResponse = await prompt({
7725
+ type: "text",
7726
+ name: "envProjectPath",
7727
+ message: "Project env file path to forward into the container",
7728
+ initial: existingEnv.WILE_ENV_PROJECT_PATH ?? ".wile/.env.project"
7729
+ });
7694
7730
  const branchResponse = await prompt({
7695
7731
  type: "text",
7696
7732
  name: "branchName",
@@ -7706,30 +7742,43 @@ var runConfig = async () => {
7706
7742
  const fallbackIterations = existingEnv.WILE_MAX_ITERATIONS ? Number(existingEnv.WILE_MAX_ITERATIONS) : 25;
7707
7743
  const maxIterations = Number.isFinite(iterationsResponse.maxIterations) && iterationsResponse.maxIterations > 0 ? iterationsResponse.maxIterations : fallbackIterations;
7708
7744
  const authFallback = authMethod === "oauth" ? existingEnv.CC_CLAUDE_CODE_OAUTH_TOKEN : existingEnv.CC_ANTHROPIC_API_KEY;
7709
- const authValue = coalesceValue(authValueResponse.authValue, authFallback);
7745
+ const authValue = codingAgent === "CC" ? coalesceValue(authValueResponse.authValue, authFallback) : undefined;
7746
+ const ocKey = codingAgent === "OC" ? coalesceValue(ocKeyResponse.ocKey, existingEnv.OC_OPENROUTER_API_KEY) : undefined;
7747
+ const ocModel = codingAgent === "OC" ? coalesceValue(ocModelResponse.ocModel, existingEnv.OC_MODEL ?? "glm-4.7") : undefined;
7710
7748
  const githubToken = repoSource === "github" ? coalesceValue(githubTokenResponse.githubToken, existingEnv.GITHUB_TOKEN) : existingEnv.GITHUB_TOKEN;
7711
7749
  const repoUrl = repoSource === "github" ? coalesceValue(repoResponse.repoUrl, existingEnv.GITHUB_REPO_URL) : existingEnv.GITHUB_REPO_URL;
7750
+ const envProjectPathValue = coalesceValue(envProjectPathResponse.envProjectPath, existingEnv.WILE_ENV_PROJECT_PATH ?? ".wile/.env.project") ?? ".wile/.env.project";
7712
7751
  const branchName = coalesceValue(branchResponse.branchName, existingEnv.BRANCH_NAME ?? "main");
7713
7752
  const envLines = [
7714
- "CODING_AGENT=CC",
7753
+ `CODING_AGENT=${codingAgent}`,
7715
7754
  `WILE_REPO_SOURCE=${repoSource}`,
7755
+ `WILE_ENV_PROJECT_PATH=${envProjectPathValue}`,
7716
7756
  `GITHUB_TOKEN=${githubToken ?? ""}`,
7717
7757
  `GITHUB_REPO_URL=${repoUrl ?? ""}`,
7718
7758
  `BRANCH_NAME=${branchName ?? "main"}`,
7719
- `CC_CLAUDE_MODEL=${defaultModelResponse.model}`,
7720
7759
  `WILE_MAX_ITERATIONS=${maxIterations}`
7721
7760
  ];
7722
- if (authMethod === "oauth") {
7723
- envLines.push(`CC_CLAUDE_CODE_OAUTH_TOKEN=${authValue ?? ""}`);
7761
+ if (codingAgent === "CC") {
7762
+ envLines.push(`CC_CLAUDE_MODEL=${defaultModelResponse.model}`);
7763
+ if (authMethod === "oauth") {
7764
+ envLines.push(`CC_CLAUDE_CODE_OAUTH_TOKEN=${authValue ?? ""}`);
7765
+ } else {
7766
+ envLines.push(`CC_ANTHROPIC_API_KEY=${authValue ?? ""}`);
7767
+ }
7724
7768
  } else {
7725
- envLines.push(`CC_ANTHROPIC_API_KEY=${authValue ?? ""}`);
7769
+ envLines.push(`OC_MODEL=${ocModel ?? "glm-4.7"}`);
7770
+ envLines.push(`OC_OPENROUTER_API_KEY=${ocKey ?? ""}`);
7726
7771
  }
7727
7772
  await writeFile(envPath, envLines.join(`
7728
7773
  `) + `
7729
7774
  `);
7730
7775
  await ensureGitignore(gitignorePath);
7731
- await writeIfMissing(envProjectPath, `# Add env vars here to forward into the container
7776
+ const envProjectTarget = envProjectPathValue === ".wile/.env.project" ? envProjectPath : envProjectPathValue;
7777
+ const envProjectResolved = envProjectTarget.startsWith("/") ? envProjectTarget : join(cwd, envProjectTarget);
7778
+ if (envProjectResolved.startsWith(wileDir)) {
7779
+ await writeIfMissing(envProjectResolved, `# Add env vars here to forward into the container
7732
7780
  `);
7781
+ }
7733
7782
  if (!existsSync(prdPath)) {
7734
7783
  const prdContents = JSON.stringify({ userStories: [] }, null, 2);
7735
7784
  await writeFile(prdPath, prdContents + `
@@ -7738,11 +7787,18 @@ var runConfig = async () => {
7738
7787
  await writeIfMissing(prdExamplePath, JSON.stringify(prdExample, null, 2) + `
7739
7788
  `);
7740
7789
  const hadAdditionalInstructions = existsSync(additionalInstructionsPath);
7790
+ const hadPreflight = existsSync(preflightPath);
7741
7791
  await writeIfMissing(additionalInstructionsPath, `<!--
7742
7792
  Use bullet points for additional instructions, e.g.
7743
7793
  - You may run \`supabase db reset --db-url "$SUPABASE_DB_URL"\` when needed.
7744
7794
  - Do not ask for permission before running it.
7745
7795
  -->
7796
+ `);
7797
+ await writeIfMissing(preflightPath, `<!--
7798
+ Use bullet points for preflight checks, e.g.
7799
+ - Confirm SUPABASE_DB_URL is set.
7800
+ - Run \`supabase db reset --db-url "$SUPABASE_DB_URL"\`.
7801
+ -->
7746
7802
  `);
7747
7803
  await writeIfMissing(agentsPath, [
7748
7804
  "# PRD authoring guidance for Wile",
@@ -7767,7 +7823,7 @@ Use bullet points for additional instructions, e.g.
7767
7823
  "",
7768
7824
  "Environment notes:",
7769
7825
  "- Playwright (Chromium) is available in the agent container for UI checks.",
7770
- "- Project env vars can be passed via `.wile/secrets/.env.project`.",
7826
+ "- Project env vars can be passed via `.wile/.env.project` (or override with WILE_ENV_PROJECT_PATH).",
7771
7827
  "- Optional extra guidance can be added in `.wile/additional-instructions.md`.",
7772
7828
  "- The container has outbound internet access by default.",
7773
7829
  ""
@@ -7775,22 +7831,25 @@ Use bullet points for additional instructions, e.g.
7775
7831
  `));
7776
7832
  console.log(`
7777
7833
  Wile config complete.`);
7778
- console.log("Add project env vars to .wile/secrets/.env.project when needed.");
7834
+ console.log("Add project env vars to .wile/.env.project when needed.");
7779
7835
  if (!hadAdditionalInstructions) {
7780
7836
  console.log("Created .wile/additional-instructions.md for extra agent guidance (optional).");
7781
7837
  }
7838
+ if (!hadPreflight) {
7839
+ console.log("Created .wile/preflight.md for preflight checks (optional).");
7840
+ }
7782
7841
  };
7783
7842
 
7784
7843
  // src/commands/run.ts
7785
7844
  import { existsSync as existsSync3, readFileSync as readFileSync2, mkdirSync, createWriteStream, writeFileSync } from "node:fs";
7786
7845
  import { spawn, spawnSync } from "node:child_process";
7787
- import { resolve, join as join3, dirname } from "node:path";
7846
+ import { resolve as resolve2, join as join3, dirname } from "node:path";
7788
7847
  import { fileURLToPath } from "node:url";
7789
7848
 
7790
7849
  // src/lib/config.ts
7791
7850
  var import_dotenv2 = __toESM(require_main(), 1);
7792
7851
  import { existsSync as existsSync2, readFileSync } from "node:fs";
7793
- import { join as join2 } from "node:path";
7852
+ import { join as join2, resolve, isAbsolute } from "node:path";
7794
7853
  var getWilePaths = (cwd = process.cwd()) => {
7795
7854
  const wileDir = join2(cwd, ".wile");
7796
7855
  const secretsDir = join2(wileDir, "secrets");
@@ -7798,11 +7857,25 @@ var getWilePaths = (cwd = process.cwd()) => {
7798
7857
  wileDir,
7799
7858
  secretsDir,
7800
7859
  envPath: join2(secretsDir, ".env"),
7801
- envProjectPath: join2(secretsDir, ".env.project"),
7860
+ envProjectPath: join2(wileDir, ".env.project"),
7802
7861
  gitignorePath: join2(wileDir, ".gitignore"),
7803
7862
  prdPath: join2(wileDir, "prd.json")
7804
7863
  };
7805
7864
  };
7865
+ var resolveEnvProjectPath = (cwd, configured) => {
7866
+ const defaultPath = join2(cwd, ".wile", ".env.project");
7867
+ const legacyPath = join2(cwd, ".wile", "secrets", ".env.project");
7868
+ if (configured && configured.trim().length > 0) {
7869
+ return isAbsolute(configured) ? configured : resolve(cwd, configured);
7870
+ }
7871
+ if (existsSync2(defaultPath)) {
7872
+ return defaultPath;
7873
+ }
7874
+ if (existsSync2(legacyPath)) {
7875
+ return legacyPath;
7876
+ }
7877
+ return defaultPath;
7878
+ };
7806
7879
  var parseEnvFile = (path) => {
7807
7880
  if (!existsSync2(path)) {
7808
7881
  return {};
@@ -7822,19 +7895,29 @@ var readWileConfig = (options = {}) => {
7822
7895
  ensureRequired(existsSync2(paths.envPath), "Missing .wile/secrets/.env. Run 'bunx wile config' first.");
7823
7896
  }
7824
7897
  const env = parseEnvFile(paths.envPath);
7825
- const envProject = parseEnvFile(paths.envProjectPath);
7898
+ const envProjectPath = resolveEnvProjectPath(options.cwd ?? process.cwd(), env.WILE_ENV_PROJECT_PATH);
7899
+ const envProject = parseEnvFile(envProjectPath);
7826
7900
  const repoSource = env.WILE_REPO_SOURCE || "github";
7827
7901
  if (validate) {
7828
- ensureRequired(env.CODING_AGENT === "CC", "CODING_AGENT must be set to CC in .wile/secrets/.env. Run 'bunx wile config'.");
7902
+ ensureRequired(env.CODING_AGENT === "CC" || env.CODING_AGENT === "OC", "CODING_AGENT must be set to CC or OC in .wile/secrets/.env. Run 'bunx wile config'.");
7829
7903
  if (repoSource === "github") {
7830
7904
  ensureRequired(Boolean(env.GITHUB_TOKEN), "GITHUB_TOKEN is required in .wile/secrets/.env. Run 'bunx wile config'.");
7831
7905
  ensureRequired(Boolean(env.GITHUB_REPO_URL), "GITHUB_REPO_URL is required in .wile/secrets/.env. Run 'bunx wile config'.");
7832
7906
  ensureRequired(Boolean(env.BRANCH_NAME), "BRANCH_NAME is required in .wile/secrets/.env. Run 'bunx wile config'.");
7833
7907
  }
7834
- ensureRequired(Boolean(env.CC_CLAUDE_CODE_OAUTH_TOKEN || env.CC_ANTHROPIC_API_KEY), "Either CC_CLAUDE_CODE_OAUTH_TOKEN or CC_ANTHROPIC_API_KEY is required in .wile/secrets/.env.");
7908
+ if (env.CODING_AGENT === "CC") {
7909
+ ensureRequired(Boolean(env.CC_CLAUDE_CODE_OAUTH_TOKEN || env.CC_ANTHROPIC_API_KEY), "Either CC_CLAUDE_CODE_OAUTH_TOKEN or CC_ANTHROPIC_API_KEY is required in .wile/secrets/.env.");
7910
+ }
7911
+ if (env.CODING_AGENT === "OC") {
7912
+ ensureRequired(Boolean(env.OC_OPENROUTER_API_KEY), "OC_OPENROUTER_API_KEY is required in .wile/secrets/.env for OpenCode.");
7913
+ ensureRequired(Boolean(env.OC_MODEL), "OC_MODEL is required in .wile/secrets/.env for OpenCode.");
7914
+ }
7835
7915
  }
7836
7916
  return {
7837
- paths,
7917
+ paths: {
7918
+ ...paths,
7919
+ envProjectPath
7920
+ },
7838
7921
  config: {
7839
7922
  codingAgent: env.CODING_AGENT ?? "CC",
7840
7923
  githubToken: env.GITHUB_TOKEN ?? "",
@@ -7845,6 +7928,8 @@ var readWileConfig = (options = {}) => {
7845
7928
  maxIterations: env.WILE_MAX_ITERATIONS,
7846
7929
  ccClaudeCodeOauthToken: env.CC_CLAUDE_CODE_OAUTH_TOKEN,
7847
7930
  ccAnthropicApiKey: env.CC_ANTHROPIC_API_KEY,
7931
+ ocOpenrouterApiKey: env.OC_OPENROUTER_API_KEY,
7932
+ ocModel: env.OC_MODEL,
7848
7933
  envProject
7849
7934
  }
7850
7935
  };
@@ -7924,7 +8009,7 @@ var resolveAgentDir = () => {
7924
8009
  if (existsSync3(bundledAgentDir)) {
7925
8010
  return bundledAgentDir;
7926
8011
  }
7927
- const cliRoot = resolve(here, "..", "..", "..");
8012
+ const cliRoot = resolve2(here, "..", "..", "..");
7928
8013
  const agentDir = join3(cliRoot, "agent");
7929
8014
  if (!existsSync3(agentDir)) {
7930
8015
  throw new Error("Unable to locate packages/agent. Run from the monorepo.");
@@ -8009,6 +8094,7 @@ var runWile = async (options) => {
8009
8094
  console.log(`- cwd: ${cwd}`);
8010
8095
  console.log(`- INIT_CWD: ${initCwd}`);
8011
8096
  console.log(`- WILE_AGENT_DIR: ${process.env.WILE_AGENT_DIR ?? "(unset)"}`);
8097
+ console.log(`- codingAgent: ${config.codingAgent}`);
8012
8098
  console.log(`- repoSource: ${config.repoSource}`);
8013
8099
  console.log(`- githubRepoUrl: ${config.githubRepoUrl || "(empty)"}`);
8014
8100
  console.log(`- branchName: ${config.branchName || "(empty)"}`);
@@ -8054,7 +8140,7 @@ var runWile = async (options) => {
8054
8140
  };
8055
8141
 
8056
8142
  // src/cli.ts
8057
- var packageJsonPath = resolve2(new URL("../package.json", import.meta.url).pathname);
8143
+ var packageJsonPath = resolve3(new URL("../package.json", import.meta.url).pathname);
8058
8144
  var packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
8059
8145
  var program2 = new Command;
8060
8146
  program2.name("wile").description("Autonomous AI coding agent that ships features while you sleep").version(packageJson.version);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wile",
3
- "version": "0.4.17",
3
+ "version": "0.4.19",
4
4
  "description": "Autonomous AI coding agent that ships features while you sleep",
5
5
  "type": "module",
6
6
  "bin": {