wile 0.4.17 → 0.4.18

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,6 +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), 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
 
@@ -67,10 +67,12 @@ NODE
67
67
  exit 0
68
68
  fi
69
69
 
70
+ CODING_AGENT="${CODING_AGENT:-CC}"
70
71
  REPO_SOURCE="${WILE_REPO_SOURCE:-github}"
71
72
  LOCAL_REPO_PATH="${WILE_LOCAL_REPO_PATH:-/home/wile/workspace/repo}"
72
73
  ADDITIONAL_INSTRUCTIONS_PATH="${WILE_ADDITIONAL_INSTRUCTIONS:-}"
73
74
 
75
+ echo " Agent: $CODING_AGENT"
74
76
  if [ "$REPO_SOURCE" = "local" ]; then
75
77
  if [ ! -d "$LOCAL_REPO_PATH" ]; then
76
78
  echo "ERROR: WILE_LOCAL_REPO_PATH does not exist: $LOCAL_REPO_PATH"
@@ -83,53 +85,74 @@ else
83
85
  : "${GITHUB_TOKEN:?GITHUB_TOKEN is required}"
84
86
  fi
85
87
 
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
88
+ # Authentication for selected coding agent
89
+ if [ "$CODING_AGENT" = "OC" ]; then
90
+ if [ -z "$OC_OPENROUTER_API_KEY" ]; then
91
+ echo "ERROR: OC_OPENROUTER_API_KEY is required for OpenCode"
92
+ exit 1
93
+ fi
94
+ if [ -z "$OC_MODEL" ]; then
95
+ echo "ERROR: OC_MODEL is required for OpenCode"
96
+ exit 1
97
+ fi
98
+ else
99
+ if [ -z "$CC_CLAUDE_CODE_OAUTH_TOKEN" ] && [ -z "$CC_ANTHROPIC_API_KEY" ]; then
100
+ echo "ERROR: Either CC_CLAUDE_CODE_OAUTH_TOKEN or CC_ANTHROPIC_API_KEY is required"
101
+ echo ""
102
+ echo " CC_CLAUDE_CODE_OAUTH_TOKEN - Uses your Pro/Max subscription (recommended)"
103
+ echo " CC_ANTHROPIC_API_KEY - Uses API credits (pay per token)"
104
+ echo ""
105
+ echo "Run 'claude setup-token' on your local machine to get an OAuth token."
106
+ exit 1
107
+ fi
95
108
  fi
96
109
 
97
110
  MAX_ITERATIONS=${MAX_ITERATIONS:-25}
98
111
  SCRIPT_DIR="/home/wile/scripts"
99
112
  WORKSPACE="/home/wile/workspace"
100
113
 
101
- if [ "${WILE_MOCK_CLAUDE:-}" = "true" ]; then
114
+ if [ "${WILE_MOCK_CLAUDE:-}" = "true" ] && [ "$CODING_AGENT" = "CC" ]; then
102
115
  echo " Claude: Mocked"
103
116
  MOCK_BIN="/home/wile/mock-bin"
104
117
  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
118
+ cp "$SCRIPT_DIR/mock-claude.sh" "$MOCK_BIN/claude"
111
119
  chmod +x "$MOCK_BIN/claude"
112
120
  export PATH="$MOCK_BIN:$PATH"
113
121
  fi
114
122
 
115
- # Set up Claude Code authentication
116
- if [ -n "$CC_CLAUDE_CODE_OAUTH_TOKEN" ]; then
117
- echo " Auth: OAuth (Pro/Max subscription)"
123
+ if [ "$CODING_AGENT" = "OC" ]; then
124
+ echo " Auth: OpenRouter (OpenCode)"
125
+ OPENCODE_DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/opencode"
126
+ mkdir -p "$OPENCODE_DATA_DIR"
127
+ cat > "$OPENCODE_DATA_DIR/auth.json" << OPENCODEAUTH
128
+ {
129
+ "openrouter": {
130
+ "type": "api",
131
+ "key": "$OC_OPENROUTER_API_KEY"
132
+ }
133
+ }
134
+ OPENCODEAUTH
135
+ chmod 600 "$OPENCODE_DATA_DIR/auth.json"
136
+ export OPENROUTER_API_KEY="$OC_OPENROUTER_API_KEY"
137
+ else
138
+ # Set up Claude Code authentication
139
+ if [ -n "$CC_CLAUDE_CODE_OAUTH_TOKEN" ]; then
140
+ echo " Auth: OAuth (Pro/Max subscription)"
118
141
 
119
- # Create required directories
120
- mkdir -p ~/.claude ~/.config/claude
142
+ # Create required directories
143
+ mkdir -p ~/.claude ~/.config/claude
121
144
 
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'
145
+ # Create ~/.claude.json (THE CRITICAL FILE!)
146
+ # Without this, Claude Code thinks it's a fresh install and breaks
147
+ cat > ~/.claude.json << 'CLAUDEJSON'
125
148
  {
126
149
  "hasCompletedOnboarding": true,
127
150
  "theme": "dark"
128
151
  }
129
152
  CLAUDEJSON
130
153
 
131
- # Create credentials file with the OAuth token
132
- cat > ~/.claude/.credentials.json << CREDSJSON
154
+ # Create credentials file with the OAuth token
155
+ cat > ~/.claude/.credentials.json << CREDSJSON
133
156
  {
134
157
  "claudeAiOauth": {
135
158
  "accessToken": "$CC_CLAUDE_CODE_OAUTH_TOKEN",
@@ -140,14 +163,15 @@ CLAUDEJSON
140
163
  }
141
164
  CREDSJSON
142
165
 
143
- # Copy to alternate location too
144
- cp ~/.claude/.credentials.json ~/.config/claude/.credentials.json
166
+ # Copy to alternate location too
167
+ cp ~/.claude/.credentials.json ~/.config/claude/.credentials.json
145
168
 
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"
169
+ # Ensure ANTHROPIC_API_KEY is not set (it overrides OAuth)
170
+ unset ANTHROPIC_API_KEY
171
+ else
172
+ echo " Auth: API Key (credits)"
173
+ export ANTHROPIC_API_KEY="$CC_ANTHROPIC_API_KEY"
174
+ fi
151
175
  fi
152
176
 
153
177
  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,21 @@
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 [ "$COUNT" -eq 0 ]; then
14
+ cat <<'JSON'
15
+ {"type":"assistant","message":{"content":[{"type":"text","text":"ANSWER: 2\n"}]}}
16
+ JSON
17
+ else
18
+ cat <<'JSON'
19
+ {"type":"assistant","message":{"content":[{"type":"text","text":"<promise>COMPLETE</promise>\n"}]}}
20
+ JSON
21
+ 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
+ });
@@ -87,6 +87,7 @@ After completing steps 1-8, check if ALL stories in `.wile/prd.json` have `passe
87
87
  ```
88
88
  <promise>COMPLETE</promise>
89
89
  ```
90
+ 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
91
 
91
92
  **If there are still stories with `passes: false`**, end your response normally. The loop will call you again for the next story.
92
93
 
@@ -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
 
@@ -7,17 +7,31 @@
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"
13
18
  SETUP_PROMPT_FILE="$SCRIPT_DIR/prompt-setup.md"
14
19
  ADDITIONAL_PROMPT_FILE="${WILE_ADDITIONAL_INSTRUCTIONS:-}"
20
+ TEE_TARGET="${WILE_TEE_TARGET:-/dev/stderr}"
21
+ if ! ( : > "$TEE_TARGET" ) 2>/dev/null; then
22
+ TEE_TARGET="/dev/null"
23
+ fi
15
24
 
16
25
  echo "══════════════════════════════════════════════════════"
17
26
  echo " 🌵 WILE - Autonomous Coding Agent"
18
27
  echo "══════════════════════════════════════════════════════"
28
+ echo " Agent: $CODING_AGENT"
19
29
  echo " Max iterations: $MAX_ITERATIONS"
20
- echo " Model: $CLAUDE_MODEL"
30
+ if [ "$CODING_AGENT" = "OC" ]; then
31
+ echo " Model: $OC_MODEL"
32
+ else
33
+ echo " Model: $CLAUDE_MODEL"
34
+ fi
21
35
  echo " Prompt file: $PROMPT_FILE"
22
36
  echo "══════════════════════════════════════════════════════"
23
37
  echo ""
@@ -39,6 +53,29 @@ if [ -n "$ADDITIONAL_PROMPT_FILE" ] && [ -f "$ADDITIONAL_PROMPT_FILE" ]; then
39
53
  fi
40
54
  fi
41
55
 
56
+ run_claude() {
57
+ local prompt_path="$1"
58
+ cat "$prompt_path" \
59
+ | claude --model "$CLAUDE_MODEL" --print --output-format stream-json --verbose --dangerously-skip-permissions \
60
+ | node "$SCRIPT_DIR/claude-stream.js"
61
+ }
62
+
63
+ run_opencode() {
64
+ local prompt_path="$1"
65
+ cat "$prompt_path" \
66
+ | opencode run --format json --model "openrouter/$OC_MODEL" \
67
+ | node "$SCRIPT_DIR/opencode-stream.js"
68
+ }
69
+
70
+ run_agent() {
71
+ local prompt_path="$1"
72
+ if [ "$CODING_AGENT" = "OC" ]; then
73
+ run_opencode "$prompt_path"
74
+ else
75
+ run_claude "$prompt_path"
76
+ fi
77
+ }
78
+
42
79
  # ════════════════════════════════════════════════════════════
43
80
  # ITERATION 0: Setup
44
81
  # ════════════════════════════════════════════════════════════
@@ -49,12 +86,7 @@ echo "════════════════════════
49
86
  echo ""
50
87
 
51
88
  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
+ OUTPUT=$(run_agent "$SETUP_PROMPT_FILE" | tee "$TEE_TARGET") || true
58
90
 
59
91
  # Check if setup failed critically
60
92
  if echo "$OUTPUT" | grep -q "<promise>SETUP_FAILED</promise>"; then
@@ -85,20 +117,22 @@ for i in $(seq 1 $MAX_ITERATIONS); do
85
117
  # Pipe prompt to Claude Code
86
118
  # --dangerously-skip-permissions allows autonomous operation
87
119
  # 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
120
+ OUTPUT=$(run_agent "$PROMPT_FILE" | tee "$TEE_TARGET") || true
121
+
122
+ # Check for completion signal (tag must be on its own line; reject backticks/code fences)
123
+ CLEAN_OUTPUT=$(printf '%s' "$OUTPUT" | tr -d '\r' | sed -e 's/[[:space:]]*$//')
124
+ if printf '%s\n' "$CLEAN_OUTPUT" | grep -q -E '^[[:space:]]*<promise>COMPLETE</promise>[[:space:]]*$'; then
125
+ if printf '%s' "$CLEAN_OUTPUT" | grep -F '```' >/dev/null 2>&1; then
126
+ :
127
+ elif printf '%s' "$CLEAN_OUTPUT" | grep -F '`<promise>COMPLETE</promise>`' >/dev/null 2>&1; then
128
+ :
129
+ else
97
130
  echo ""
98
131
  echo "══════════════════════════════════════════════════════"
99
132
  echo " ✅ ALL TASKS COMPLETE"
100
133
  echo "══════════════════════════════════════════════════════"
101
134
  exit 0
135
+ fi
102
136
  fi
103
137
 
104
138
  echo ""
package/dist/cli.js CHANGED
@@ -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) => {
@@ -7624,45 +7625,73 @@ var runConfig = async () => {
7624
7625
  const agentsPath = join(wileDir, "AGENTS.md");
7625
7626
  await mkdir(secretsDir, { recursive: true });
7626
7627
  const existingEnv = await readEnvFile(envPath);
7627
- await prompt({
7628
+ const codingAgentResponse = await prompt({
7628
7629
  type: "select",
7629
7630
  name: "codingAgent",
7630
7631
  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
7632
  choices: [
7640
- { title: "OAuth token (Pro/Max subscription)", value: "oauth" },
7641
- { title: "API key (Anthropic credits)", value: "apiKey" }
7633
+ { title: "Claude Code (CC)", value: "CC" },
7634
+ { title: "OpenCode (OC)", value: "OC" }
7642
7635
  ],
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
7636
+ initial: existingEnv.CODING_AGENT === "OC" ? 1 : 0
7665
7637
  });
7638
+ const codingAgent = codingAgentResponse.codingAgent;
7639
+ let authMethod = null;
7640
+ let authValueResponse = {};
7641
+ let defaultModelResponse = {};
7642
+ let ocKeyResponse = {};
7643
+ let ocModelResponse = {};
7644
+ if (codingAgent === "CC") {
7645
+ const authDefault = existingEnv.CC_CLAUDE_CODE_OAUTH_TOKEN ? "oauth" : existingEnv.CC_ANTHROPIC_API_KEY ? "apiKey" : "oauth";
7646
+ const authResponse = await prompt({
7647
+ type: "select",
7648
+ name: "authMethod",
7649
+ message: "Claude Code authentication",
7650
+ choices: [
7651
+ { title: "OAuth token (Pro/Max subscription)", value: "oauth" },
7652
+ { title: "API key (Anthropic credits)", value: "apiKey" }
7653
+ ],
7654
+ initial: authDefault === "apiKey" ? 1 : 0
7655
+ });
7656
+ authMethod = authResponse.authMethod;
7657
+ console.log("");
7658
+ console.log(authMethod === "oauth" ? tips.oauth : tips.apiKey);
7659
+ console.log("");
7660
+ authValueResponse = await prompt({
7661
+ type: "password",
7662
+ name: "authValue",
7663
+ message: authMethod === "oauth" ? "Claude Code OAuth token (press enter to keep existing)" : "Anthropic API key (press enter to keep existing)",
7664
+ initial: authMethod === "oauth" ? existingEnv.CC_CLAUDE_CODE_OAUTH_TOKEN ?? "" : existingEnv.CC_ANTHROPIC_API_KEY ?? ""
7665
+ });
7666
+ defaultModelResponse = await prompt({
7667
+ type: "select",
7668
+ name: "model",
7669
+ message: "Default Claude model",
7670
+ choices: [
7671
+ { title: "sonnet", value: "sonnet" },
7672
+ { title: "opus", value: "opus" },
7673
+ { title: "haiku", value: "haiku" }
7674
+ ],
7675
+ initial: existingEnv.CC_CLAUDE_MODEL === "opus" ? 1 : existingEnv.CC_CLAUDE_MODEL === "haiku" ? 2 : 0
7676
+ });
7677
+ } else {
7678
+ console.log("");
7679
+ console.log(tips.openrouter);
7680
+ console.log("");
7681
+ ocKeyResponse = await prompt({
7682
+ type: "password",
7683
+ name: "ocKey",
7684
+ message: "OpenRouter API key (press enter to keep existing)",
7685
+ initial: existingEnv.OC_OPENROUTER_API_KEY ?? ""
7686
+ });
7687
+ ocModelResponse = await prompt({
7688
+ type: "select",
7689
+ name: "ocModel",
7690
+ message: "OpenCode model (OpenRouter)",
7691
+ choices: [{ title: "glm-4.7", value: "glm-4.7" }],
7692
+ initial: existingEnv.OC_MODEL === "glm-4.7" ? 0 : 0
7693
+ });
7694
+ }
7666
7695
  const repoSourceResponse = await prompt({
7667
7696
  type: "select",
7668
7697
  name: "repoSource",
@@ -7706,23 +7735,30 @@ var runConfig = async () => {
7706
7735
  const fallbackIterations = existingEnv.WILE_MAX_ITERATIONS ? Number(existingEnv.WILE_MAX_ITERATIONS) : 25;
7707
7736
  const maxIterations = Number.isFinite(iterationsResponse.maxIterations) && iterationsResponse.maxIterations > 0 ? iterationsResponse.maxIterations : fallbackIterations;
7708
7737
  const authFallback = authMethod === "oauth" ? existingEnv.CC_CLAUDE_CODE_OAUTH_TOKEN : existingEnv.CC_ANTHROPIC_API_KEY;
7709
- const authValue = coalesceValue(authValueResponse.authValue, authFallback);
7738
+ const authValue = codingAgent === "CC" ? coalesceValue(authValueResponse.authValue, authFallback) : undefined;
7739
+ const ocKey = codingAgent === "OC" ? coalesceValue(ocKeyResponse.ocKey, existingEnv.OC_OPENROUTER_API_KEY) : undefined;
7740
+ const ocModel = codingAgent === "OC" ? coalesceValue(ocModelResponse.ocModel, existingEnv.OC_MODEL ?? "glm-4.7") : undefined;
7710
7741
  const githubToken = repoSource === "github" ? coalesceValue(githubTokenResponse.githubToken, existingEnv.GITHUB_TOKEN) : existingEnv.GITHUB_TOKEN;
7711
7742
  const repoUrl = repoSource === "github" ? coalesceValue(repoResponse.repoUrl, existingEnv.GITHUB_REPO_URL) : existingEnv.GITHUB_REPO_URL;
7712
7743
  const branchName = coalesceValue(branchResponse.branchName, existingEnv.BRANCH_NAME ?? "main");
7713
7744
  const envLines = [
7714
- "CODING_AGENT=CC",
7745
+ `CODING_AGENT=${codingAgent}`,
7715
7746
  `WILE_REPO_SOURCE=${repoSource}`,
7716
7747
  `GITHUB_TOKEN=${githubToken ?? ""}`,
7717
7748
  `GITHUB_REPO_URL=${repoUrl ?? ""}`,
7718
7749
  `BRANCH_NAME=${branchName ?? "main"}`,
7719
- `CC_CLAUDE_MODEL=${defaultModelResponse.model}`,
7720
7750
  `WILE_MAX_ITERATIONS=${maxIterations}`
7721
7751
  ];
7722
- if (authMethod === "oauth") {
7723
- envLines.push(`CC_CLAUDE_CODE_OAUTH_TOKEN=${authValue ?? ""}`);
7752
+ if (codingAgent === "CC") {
7753
+ envLines.push(`CC_CLAUDE_MODEL=${defaultModelResponse.model}`);
7754
+ if (authMethod === "oauth") {
7755
+ envLines.push(`CC_CLAUDE_CODE_OAUTH_TOKEN=${authValue ?? ""}`);
7756
+ } else {
7757
+ envLines.push(`CC_ANTHROPIC_API_KEY=${authValue ?? ""}`);
7758
+ }
7724
7759
  } else {
7725
- envLines.push(`CC_ANTHROPIC_API_KEY=${authValue ?? ""}`);
7760
+ envLines.push(`OC_MODEL=${ocModel ?? "glm-4.7"}`);
7761
+ envLines.push(`OC_OPENROUTER_API_KEY=${ocKey ?? ""}`);
7726
7762
  }
7727
7763
  await writeFile(envPath, envLines.join(`
7728
7764
  `) + `
@@ -7825,13 +7861,19 @@ var readWileConfig = (options = {}) => {
7825
7861
  const envProject = parseEnvFile(paths.envProjectPath);
7826
7862
  const repoSource = env.WILE_REPO_SOURCE || "github";
7827
7863
  if (validate) {
7828
- ensureRequired(env.CODING_AGENT === "CC", "CODING_AGENT must be set to CC in .wile/secrets/.env. Run 'bunx wile config'.");
7864
+ 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
7865
  if (repoSource === "github") {
7830
7866
  ensureRequired(Boolean(env.GITHUB_TOKEN), "GITHUB_TOKEN is required in .wile/secrets/.env. Run 'bunx wile config'.");
7831
7867
  ensureRequired(Boolean(env.GITHUB_REPO_URL), "GITHUB_REPO_URL is required in .wile/secrets/.env. Run 'bunx wile config'.");
7832
7868
  ensureRequired(Boolean(env.BRANCH_NAME), "BRANCH_NAME is required in .wile/secrets/.env. Run 'bunx wile config'.");
7833
7869
  }
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.");
7870
+ if (env.CODING_AGENT === "CC") {
7871
+ 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.");
7872
+ }
7873
+ if (env.CODING_AGENT === "OC") {
7874
+ ensureRequired(Boolean(env.OC_OPENROUTER_API_KEY), "OC_OPENROUTER_API_KEY is required in .wile/secrets/.env for OpenCode.");
7875
+ ensureRequired(Boolean(env.OC_MODEL), "OC_MODEL is required in .wile/secrets/.env for OpenCode.");
7876
+ }
7835
7877
  }
7836
7878
  return {
7837
7879
  paths,
@@ -7845,6 +7887,8 @@ var readWileConfig = (options = {}) => {
7845
7887
  maxIterations: env.WILE_MAX_ITERATIONS,
7846
7888
  ccClaudeCodeOauthToken: env.CC_CLAUDE_CODE_OAUTH_TOKEN,
7847
7889
  ccAnthropicApiKey: env.CC_ANTHROPIC_API_KEY,
7890
+ ocOpenrouterApiKey: env.OC_OPENROUTER_API_KEY,
7891
+ ocModel: env.OC_MODEL,
7848
7892
  envProject
7849
7893
  }
7850
7894
  };
@@ -8009,6 +8053,7 @@ var runWile = async (options) => {
8009
8053
  console.log(`- cwd: ${cwd}`);
8010
8054
  console.log(`- INIT_CWD: ${initCwd}`);
8011
8055
  console.log(`- WILE_AGENT_DIR: ${process.env.WILE_AGENT_DIR ?? "(unset)"}`);
8056
+ console.log(`- codingAgent: ${config.codingAgent}`);
8012
8057
  console.log(`- repoSource: ${config.repoSource}`);
8013
8058
  console.log(`- githubRepoUrl: ${config.githubRepoUrl || "(empty)"}`);
8014
8059
  console.log(`- branchName: ${config.branchName || "(empty)"}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wile",
3
- "version": "0.4.17",
3
+ "version": "0.4.18",
4
4
  "description": "Autonomous AI coding agent that ships features while you sleep",
5
5
  "type": "module",
6
6
  "bin": {