wile 0.4.3 → 0.4.5

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.
@@ -0,0 +1,55 @@
1
+ # Wile Agent Configuration
2
+ # Copy this file to .env and fill in your values
3
+
4
+ # =============================================================================
5
+ # REQUIRED - Claude Code Authentication (choose ONE)
6
+ # =============================================================================
7
+
8
+ # OPTION 1: OAuth Token (RECOMMENDED - uses your Pro/Max subscription)
9
+ # Generate with: claude setup-token
10
+ # This uses your subscription, NOT API credits!
11
+ CC_CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxxxx
12
+
13
+ # OPTION 2: API Key (uses API credits - pay per token)
14
+ # Get one at: https://console.anthropic.com/
15
+ # Only use this if you don't have a Pro/Max subscription
16
+ # CC_ANTHROPIC_API_KEY=sk-ant-xxxxx
17
+
18
+ # =============================================================================
19
+ # GitHub Authentication (required when WILE_REPO_SOURCE=github)
20
+ # =============================================================================
21
+
22
+ # GitHub Personal Access Token (fine-grained recommended)
23
+ # Create at: https://github.com/settings/tokens?type=beta
24
+ # Required permissions: Contents (read/write), Metadata (read)
25
+ # Scope to specific repositories only for security
26
+ GITHUB_TOKEN=github_pat_xxxxx
27
+
28
+ # =============================================================================
29
+ # Target Repository (required when WILE_REPO_SOURCE=github)
30
+ # =============================================================================
31
+
32
+ # Repo source: github (default) or local
33
+ WILE_REPO_SOURCE=github
34
+
35
+ # Full HTTPS URL to the GitHub repository
36
+ GITHUB_REPO_URL=https://github.com/username/repo
37
+
38
+ # Branch to work on (must contain .wile/prd.json at root)
39
+ BRANCH_NAME=feature/my-feature
40
+
41
+ # =============================================================================
42
+ # OPTIONAL - Configuration
43
+ # =============================================================================
44
+
45
+ # Maximum loop iterations before stopping (default: 25)
46
+ MAX_ITERATIONS=25
47
+
48
+ # Claude model alias or full model name (default: sonnet)
49
+ CC_CLAUDE_MODEL=sonnet
50
+
51
+ # =============================================================================
52
+ # PROJECT ENVIRONMENT VARIABLES
53
+ # =============================================================================
54
+ # Wile forwards variables from .wile/secrets/.env.project into the container
55
+ # as-is. Put your project-specific values there (no ENV_FORWARD_ prefix).
@@ -0,0 +1,97 @@
1
+ FROM ubuntu:22.04
2
+
3
+ # Prevent interactive prompts during package installation
4
+ ENV DEBIAN_FRONTEND=noninteractive
5
+
6
+ # Install base dependencies
7
+ RUN apt-get update && apt-get install -y \
8
+ curl \
9
+ git \
10
+ ca-certificates \
11
+ gnupg \
12
+ unzip \
13
+ build-essential \
14
+ software-properties-common \
15
+ && rm -rf /var/lib/apt/lists/*
16
+
17
+ # Install Node.js 20 LTS
18
+ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
19
+ && apt-get install -y nodejs \
20
+ && rm -rf /var/lib/apt/lists/*
21
+
22
+ # Install Bun to /usr/local (globally accessible)
23
+ RUN curl -fsSL https://bun.sh/install | BUN_INSTALL=/usr/local bash
24
+
25
+ # Install Python 3.11
26
+ RUN add-apt-repository ppa:deadsnakes/ppa -y \
27
+ && apt-get update \
28
+ && apt-get install -y python3.11 python3.11-venv python3-pip \
29
+ && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1 \
30
+ && rm -rf /var/lib/apt/lists/*
31
+
32
+ # Create non-root user
33
+ RUN useradd -m -s /bin/bash wile \
34
+ && mkdir -p /home/wile/workspace \
35
+ && chown -R wile:wile /home/wile
36
+
37
+ # Memory optimization settings per Claude Code specs
38
+ ENV NODE_OPTIONS="--max-old-space-size=4096"
39
+
40
+ # Note: The following settings must be configured on the HOST (EC2 instance), not in Docker:
41
+ # - Swap (8GB): fallocate -l 8G /swapfile && mkswap /swapfile && swapon /swapfile
42
+ # - zswap with lz4: echo lz4 > /sys/module/zswap/parameters/compressor && echo 1 > /sys/module/zswap/parameters/enabled
43
+ # - vm.swappiness=10: sysctl -w vm.swappiness=10
44
+ # - earlyoom: apt install earlyoom && systemctl enable earlyoom
45
+ # See packages/agent/scripts/host-setup.sh for EC2 bootstrap
46
+
47
+ # Install Claude Code CLI
48
+ # https://www.npmjs.com/package/@anthropic-ai/claude-code
49
+ RUN npm install -g @anthropic-ai/claude-code
50
+
51
+ # CC_ANTHROPIC_API_KEY is passed at runtime via environment variable (mapped to ANTHROPIC_API_KEY in entrypoint)
52
+ # Usage: claude --dangerously-skip-permissions
53
+
54
+ # Install Playwright with Chromium for browser testing
55
+ # Dependencies for headless Chromium
56
+ RUN apt-get update && apt-get install -y \
57
+ libnss3 \
58
+ libnspr4 \
59
+ libatk1.0-0 \
60
+ libatk-bridge2.0-0 \
61
+ libcups2 \
62
+ libdrm2 \
63
+ libxkbcommon0 \
64
+ libxcomposite1 \
65
+ libxdamage1 \
66
+ libxfixes3 \
67
+ libxrandr2 \
68
+ libgbm1 \
69
+ libasound2 \
70
+ libpango-1.0-0 \
71
+ libcairo2 \
72
+ libatspi2.0-0 \
73
+ && rm -rf /var/lib/apt/lists/*
74
+
75
+ # Install Playwright globally and download Chromium to shared location
76
+ ENV PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers
77
+ RUN npm install -g playwright \
78
+ && mkdir -p /opt/playwright-browsers \
79
+ && npx playwright install chromium \
80
+ && chmod -R 755 /opt/playwright-browsers
81
+
82
+ # Copy scripts
83
+ COPY scripts/ /home/wile/scripts/
84
+ COPY entrypoint.sh /home/wile/entrypoint.sh
85
+
86
+ RUN chmod +x /home/wile/entrypoint.sh \
87
+ && chmod +x /home/wile/scripts/*.sh 2>/dev/null || true \
88
+ && chown -R wile:wile /home/wile
89
+
90
+ # Working directory
91
+ WORKDIR /home/wile/workspace
92
+
93
+ # Switch to non-root user
94
+ USER wile
95
+
96
+ # Default command
97
+ ENTRYPOINT ["/home/wile/entrypoint.sh"]
@@ -0,0 +1,150 @@
1
+ # Wile Agent
2
+
3
+ Autonomous AI coding agent that runs in a Docker container. Ships features while you sleep.
4
+
5
+ ## Quick Start
6
+
7
+ ### 1. Set up your environment
8
+
9
+ ```bash
10
+ cp .env.example .env
11
+ # Edit .env with your credentials
12
+ ```
13
+
14
+ ### 2. Build and run
15
+
16
+ ```bash
17
+ docker compose up --build
18
+ ```
19
+
20
+ ## How It Works
21
+
22
+ 1. Container clones your repository
23
+ 2. Checks out the specified branch
24
+ 3. **Iteration 0 (Setup)**: Sets up .gitignore, initializes progress.txt
25
+ 4. Reads `.wile/prd.json` from repo
26
+ 5. Loops through user stories, implementing one per iteration:
27
+ - Picks highest priority story where `passes: false`
28
+ - Implements the feature/fix
29
+ - Runs tests and typecheck
30
+ - Commits and pushes
31
+ - Marks story as complete
32
+ - Logs learnings
33
+ 6. Repeats until all stories pass or max iterations reached
34
+
35
+ ## Project Structure
36
+
37
+ Your repository must have a `.wile/` folder:
38
+
39
+ ```
40
+ your-repo/
41
+ ├── .wile/
42
+ │ ├── prd.json # User stories (required)
43
+ │ ├── progress.txt # Learnings log (created by agent)
44
+ │ └── screenshots/ # Visual verification (created by agent)
45
+ └── ... your code ...
46
+ ```
47
+
48
+ ## .wile/prd.json Format
49
+
50
+ ```json
51
+ {
52
+ "userStories": [
53
+ {
54
+ "id": "US-001",
55
+ "title": "Add login form",
56
+ "acceptanceCriteria": [
57
+ "Email/password fields exist",
58
+ "Form validates email format",
59
+ "typecheck passes"
60
+ ],
61
+ "priority": 1,
62
+ "passes": false,
63
+ "notes": "Optional notes"
64
+ }
65
+ ]
66
+ }
67
+ ```
68
+
69
+ ### Fields
70
+
71
+ - `id`: Unique identifier (used in commit messages)
72
+ - `title`: Short description
73
+ - `acceptanceCriteria`: List of requirements (be specific!)
74
+ - `priority`: Lower number = done first
75
+ - `passes`: Set to `true` by the agent when complete
76
+ - `notes`: Optional context for the agent
77
+
78
+ ## Tips for Success
79
+
80
+ ### Write Small Stories
81
+ Each story should fit in one context window. Break large features into steps.
82
+
83
+ ```
84
+ ❌ "Build entire auth system"
85
+ ✅ "Add login form"
86
+ ✅ "Add email validation"
87
+ ✅ "Add login API endpoint"
88
+ ```
89
+
90
+ ### Be Explicit in Criteria
91
+ Vague criteria lead to incomplete work.
92
+
93
+ ```
94
+ ❌ "Users can log in"
95
+ ✅ "Email field with type=email"
96
+ ✅ "Password field with minlength=8"
97
+ ✅ "Submit button disabled when fields empty"
98
+ ✅ "Shows error toast on invalid credentials"
99
+ ```
100
+
101
+ ### Include Verification Steps
102
+ For UI work, tell the agent how to verify:
103
+
104
+ ```
105
+ "Verify login form at localhost:3000/login"
106
+ "Take screenshot of completed form"
107
+ ```
108
+
109
+ ## Environment Variables
110
+
111
+ | Variable | Required | Description |
112
+ |----------|----------|-------------|
113
+ | `CC_CLAUDE_CODE_OAUTH_TOKEN` | Yes* | OAuth token from `claude setup-token` (uses Pro/Max subscription) |
114
+ | `CC_ANTHROPIC_API_KEY` | Yes* | API key (uses API credits - alternative to OAuth) |
115
+ | `WILE_REPO_SOURCE` | No | `github` (default) or `local` |
116
+ | `GITHUB_TOKEN` | Yes (github) | GitHub PAT with repo access |
117
+ | `GITHUB_REPO_URL` | Yes (github) | HTTPS URL to repository |
118
+ | `BRANCH_NAME` | Yes (github) | Branch to work on |
119
+ | `MAX_ITERATIONS` | No | Max loops (default: 25) |
120
+ | `CC_CLAUDE_MODEL` | No | Claude model alias/name (default: sonnet) |
121
+
122
+ *Either `CC_CLAUDE_CODE_OAUTH_TOKEN` or `CC_ANTHROPIC_API_KEY` is required, not both.
123
+
124
+ ## Output Files
125
+
126
+ The `.wile/` folder is **tracked in git** (except screenshots):
127
+
128
+ - `prd.json` - User stories / Product Requirements Document
129
+ - `progress.txt` - Log of completed work and learnings
130
+ - `screenshots/` - Visual verification screenshots (gitignored)
131
+
132
+ ## Monitoring Progress
133
+
134
+ Watch commits appear on your branch:
135
+
136
+ ```bash
137
+ git log --oneline origin/your-branch
138
+ ```
139
+
140
+ Check the progress log:
141
+
142
+ ```bash
143
+ git show origin/your-branch:.wile/progress.txt
144
+ ```
145
+
146
+ Check story status:
147
+
148
+ ```bash
149
+ git show origin/your-branch:.wile/prd.json | jq '.userStories[] | {id, title, passes}'
150
+ ```
@@ -0,0 +1,27 @@
1
+ version: '3.8'
2
+
3
+ services:
4
+ wile:
5
+ build: .
6
+ image: wile-agent:local
7
+ container_name: wile-agent
8
+
9
+ # Environment variables from .env file
10
+ env_file:
11
+ - .env
12
+
13
+ # Override for local testing (optional - mount local repo)
14
+ # volumes:
15
+ # - /path/to/local/repo:/home/wile/workspace/repo
16
+
17
+ # Keep stdin open for debugging
18
+ stdin_open: true
19
+ tty: true
20
+
21
+ # Resource limits (optional - adjust based on your machine)
22
+ # deploy:
23
+ # resources:
24
+ # limits:
25
+ # memory: 8G
26
+ # reservations:
27
+ # memory: 4G
@@ -0,0 +1,277 @@
1
+ #!/bin/bash
2
+ #
3
+ # Wile Container Entrypoint
4
+ # Clones repo, sets up workspace, runs the autonomous coding loop
5
+ #
6
+
7
+ set -e
8
+
9
+ echo "══════════════════════════════════════════════════════"
10
+ echo " 🗡️ WILE - Container Startup"
11
+ echo "══════════════════════════════════════════════════════"
12
+
13
+ if [ "${WILE_TEST:-}" = "true" ]; then
14
+ TEST_REPO="${WILE_TEST_REPO_PATH:-/home/wile/workspace/repo}"
15
+
16
+ if [ ! -d "$TEST_REPO" ]; then
17
+ echo "ERROR: WILE_TEST_REPO_PATH does not exist: $TEST_REPO"
18
+ exit 1
19
+ fi
20
+
21
+ cd "$TEST_REPO"
22
+
23
+ if [ ! -f ".wile/prd.json" ]; then
24
+ echo "ERROR: .wile/prd.json not found in test repo"
25
+ exit 1
26
+ fi
27
+
28
+ echo "TEST MODE: running mocked agent (no GitHub, no Claude)"
29
+
30
+ node - << 'NODE'
31
+ const fs = require('fs');
32
+ const path = require('path');
33
+
34
+ const prdPath = path.join('.wile', 'prd.json');
35
+ const progressPath = path.join('.wile', 'progress.txt');
36
+
37
+ const prd = JSON.parse(fs.readFileSync(prdPath, 'utf8'));
38
+ const stories = Array.isArray(prd.userStories) ? prd.userStories : [];
39
+ const pending = stories.filter((story) => story.passes === false);
40
+
41
+ if (pending.length === 0) {
42
+ console.log('No pending stories to complete in test mode.');
43
+ process.exit(0);
44
+ }
45
+
46
+ pending.sort((a, b) => {
47
+ const aPriority = typeof a.priority === 'number' ? a.priority : Number.MAX_SAFE_INTEGER;
48
+ const bPriority = typeof b.priority === 'number' ? b.priority : Number.MAX_SAFE_INTEGER;
49
+ return aPriority - bPriority;
50
+ });
51
+
52
+ const story = pending[0];
53
+ story.passes = true;
54
+ fs.writeFileSync(prdPath, JSON.stringify(prd, null, 2) + '\n');
55
+
56
+ if (!fs.existsSync(progressPath)) {
57
+ fs.writeFileSync(progressPath, '# Wile Progress Log\n\n## Codebase Patterns\n(Autogenerated in test mode)\n\n---\n');
58
+ }
59
+
60
+ const date = new Date().toISOString().split('T')[0];
61
+ const entry = `\n---\n\n## ${date} - ${story.id}: ${story.title}\n\n**Implemented:**\n- Mocked test mode completion\n\n**Files changed:**\n- .wile/prd.json\n- .wile/progress.txt\n\n**Learnings:**\n- Test mode mock run\n`;
62
+
63
+ fs.appendFileSync(progressPath, entry);
64
+ console.log(`Marked ${story.id} complete (test mode).`);
65
+ NODE
66
+
67
+ exit 0
68
+ fi
69
+
70
+ REPO_SOURCE="${WILE_REPO_SOURCE:-github}"
71
+ LOCAL_REPO_PATH="${WILE_LOCAL_REPO_PATH:-/home/wile/workspace/repo}"
72
+ ADDITIONAL_INSTRUCTIONS_PATH="${WILE_ADDITIONAL_INSTRUCTIONS:-}"
73
+
74
+ if [ "$REPO_SOURCE" = "local" ]; then
75
+ if [ ! -d "$LOCAL_REPO_PATH" ]; then
76
+ echo "ERROR: WILE_LOCAL_REPO_PATH does not exist: $LOCAL_REPO_PATH"
77
+ exit 1
78
+ fi
79
+ else
80
+ # Required environment variables
81
+ : "${GITHUB_REPO_URL:?GITHUB_REPO_URL is required}"
82
+ : "${BRANCH_NAME:?BRANCH_NAME is required}"
83
+ : "${GITHUB_TOKEN:?GITHUB_TOKEN is required}"
84
+ fi
85
+
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
95
+ fi
96
+
97
+ MAX_ITERATIONS=${MAX_ITERATIONS:-25}
98
+ SCRIPT_DIR="/home/wile/scripts"
99
+ WORKSPACE="/home/wile/workspace"
100
+
101
+ # Set up Claude Code authentication
102
+ if [ -n "$CC_CLAUDE_CODE_OAUTH_TOKEN" ]; then
103
+ echo " Auth: OAuth (Pro/Max subscription)"
104
+
105
+ # Create required directories
106
+ mkdir -p ~/.claude ~/.config/claude
107
+
108
+ # Create ~/.claude.json (THE CRITICAL FILE!)
109
+ # Without this, Claude Code thinks it's a fresh install and breaks
110
+ cat > ~/.claude.json << 'CLAUDEJSON'
111
+ {
112
+ "hasCompletedOnboarding": true,
113
+ "theme": "dark"
114
+ }
115
+ CLAUDEJSON
116
+
117
+ # Create credentials file with the OAuth token
118
+ cat > ~/.claude/.credentials.json << CREDSJSON
119
+ {
120
+ "claudeAiOauth": {
121
+ "accessToken": "$CC_CLAUDE_CODE_OAUTH_TOKEN",
122
+ "refreshToken": "$CC_CLAUDE_CODE_OAUTH_TOKEN",
123
+ "expiresAt": 9999999999999,
124
+ "scopes": ["user:inference", "user:profile"]
125
+ }
126
+ }
127
+ CREDSJSON
128
+
129
+ # Copy to alternate location too
130
+ cp ~/.claude/.credentials.json ~/.config/claude/.credentials.json
131
+
132
+ # Ensure ANTHROPIC_API_KEY is not set (it overrides OAuth)
133
+ unset ANTHROPIC_API_KEY
134
+ else
135
+ echo " Auth: API Key (credits)"
136
+ export ANTHROPIC_API_KEY="$CC_ANTHROPIC_API_KEY"
137
+ fi
138
+
139
+ if [ "$REPO_SOURCE" = "local" ]; then
140
+ echo " Repo: local ($LOCAL_REPO_PATH)"
141
+ else
142
+ echo " Repo: $GITHUB_REPO_URL"
143
+ echo " Branch: $BRANCH_NAME"
144
+ fi
145
+ echo " Iterations: $MAX_ITERATIONS"
146
+ echo "══════════════════════════════════════════════════════"
147
+ echo ""
148
+
149
+ # Configure git
150
+ echo "Configuring git..."
151
+ git config --global user.name "Wile Bot"
152
+ git config --global user.email "wile@bot.local"
153
+ git config --global credential.helper store
154
+
155
+ if [ "$REPO_SOURCE" = "local" ]; then
156
+ echo "Using local repo mount..."
157
+ cd "$LOCAL_REPO_PATH"
158
+ else
159
+ # Set up GitHub token authentication
160
+ # Extract host from URL (handles both https://github.com/... and git@github.com:...)
161
+ if [[ "$GITHUB_REPO_URL" =~ ^https://([^/]+)/ ]]; then
162
+ GIT_HOST="${BASH_REMATCH[1]}"
163
+ # Store credentials for HTTPS
164
+ echo "https://x-access-token:${GITHUB_TOKEN}@${GIT_HOST}" > ~/.git-credentials
165
+ elif [[ "$GITHUB_REPO_URL" =~ ^git@([^:]+): ]]; then
166
+ GIT_HOST="${BASH_REMATCH[1]}"
167
+ # Convert to HTTPS for token auth
168
+ GITHUB_REPO_URL=$(echo "$GITHUB_REPO_URL" | sed 's|git@\([^:]*\):|https://\1/|')
169
+ echo "https://x-access-token:${GITHUB_TOKEN}@${GIT_HOST}" > ~/.git-credentials
170
+ fi
171
+
172
+ # Clone the repository
173
+ echo "Cloning repository..."
174
+ cd "$WORKSPACE"
175
+ git clone "$GITHUB_REPO_URL" repo
176
+ cd repo
177
+
178
+ # Checkout the branch
179
+ echo "Checking out branch: $BRANCH_NAME"
180
+ git fetch origin
181
+ if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then
182
+ git checkout "$BRANCH_NAME"
183
+ else
184
+ echo "Branch $BRANCH_NAME does not exist remotely. Creating it..."
185
+ git checkout -b "$BRANCH_NAME"
186
+ git push -u origin "$BRANCH_NAME"
187
+ fi
188
+ fi
189
+
190
+ if [ -n "$ADDITIONAL_INSTRUCTIONS_PATH" ] && [ -f "$ADDITIONAL_INSTRUCTIONS_PATH" ]; then
191
+ if [ -s "$ADDITIONAL_INSTRUCTIONS_PATH" ]; then
192
+ echo " Extra: Using additional instructions"
193
+ fi
194
+ fi
195
+
196
+ # Verify .wile/prd.json exists
197
+ echo "Checking for .wile/prd.json..."
198
+ if [ ! -f ".wile/prd.json" ]; then
199
+ echo "ERROR: .wile/prd.json not found!"
200
+ echo ""
201
+ echo "Your repository must have a .wile/prd.json file at the root."
202
+ echo "This file contains the user stories for Wile to implement."
203
+ echo ""
204
+ echo "Example structure:"
205
+ echo ' {'
206
+ echo ' "userStories": ['
207
+ echo ' {'
208
+ echo ' "id": "US-001",'
209
+ echo ' "title": "My feature",'
210
+ echo ' "acceptanceCriteria": ["..."],'
211
+ echo ' "priority": 1,'
212
+ echo ' "passes": false'
213
+ echo ' }'
214
+ echo ' ]'
215
+ echo ' }'
216
+ exit 1
217
+ fi
218
+
219
+ # Set up .wile/screenshots directory
220
+ echo "Setting up .wile directory..."
221
+ mkdir -p .wile/screenshots
222
+
223
+ echo ""
224
+ echo "══════════════════════════════════════════════════════"
225
+ echo " Starting Wile Loop"
226
+ echo "══════════════════════════════════════════════════════"
227
+ echo ""
228
+
229
+ # Run the main loop
230
+ EXIT_CODE=0
231
+ "$SCRIPT_DIR/wile.sh" "$MAX_ITERATIONS" || EXIT_CODE=$?
232
+
233
+ echo ""
234
+ echo "══════════════════════════════════════════════════════"
235
+ echo " Wile Loop Complete (exit code: $EXIT_CODE)"
236
+ echo "══════════════════════════════════════════════════════"
237
+
238
+ # Handle completion/partial completion
239
+ if [ $EXIT_CODE -eq 0 ]; then
240
+ echo "All tasks completed successfully!"
241
+ # Ensure final state is pushed
242
+ git push || true
243
+ elif [ $EXIT_CODE -eq 1 ]; then
244
+ echo "Max iterations reached. Committing partial work..."
245
+
246
+ # Check if there are uncommitted changes
247
+ if ! git diff --quiet || ! git diff --staged --quiet; then
248
+ git add -A
249
+ TIMESTAMP=$(date +%Y%m%d_%H%M%S)
250
+ git commit -m "WIP: wile stopped at max iterations ($TIMESTAMP)
251
+
252
+ Partial work committed. Check .wile/progress.txt for details.
253
+ Some stories may still have passes=false in .wile/prd.json."
254
+ git push
255
+ fi
256
+ elif [ $EXIT_CODE -eq 2 ]; then
257
+ echo "Setup failed. Check the logs above for details."
258
+ else
259
+ echo "Wile exited with error code: $EXIT_CODE"
260
+ # Try to commit whatever state we have
261
+ if ! git diff --quiet || ! git diff --staged --quiet; then
262
+ git add -A
263
+ TIMESTAMP=$(date +%Y%m%d_%H%M%S)
264
+ git commit -m "WIP: wile error exit ($TIMESTAMP)
265
+
266
+ Exit code: $EXIT_CODE
267
+ Check logs for details."
268
+ git push || true
269
+ fi
270
+ fi
271
+
272
+ echo ""
273
+ echo "══════════════════════════════════════════════════════"
274
+ echo " Wile Container Finished"
275
+ echo "══════════════════════════════════════════════════════"
276
+
277
+ exit $EXIT_CODE
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "@wile/agent",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Wile autonomous coding agent - Docker image and scripts"
6
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Browser testing helper for Wile agent
3
+ *
4
+ * Usage:
5
+ * npx tsx browser-test.ts screenshot <url> <output-path>
6
+ * npx tsx browser-test.ts verify <url> <selector>
7
+ *
8
+ * Examples:
9
+ * npx tsx browser-test.ts screenshot http://localhost:3000 .wile-temp/screenshots/home.png
10
+ * npx tsx browser-test.ts verify http://localhost:3000/login "form#login"
11
+ */
12
+
13
+ import { chromium, type Page, type Browser } from 'playwright';
14
+
15
+ const VIEWPORT = { width: 1280, height: 900 };
16
+ const TIMEOUT = 30000;
17
+
18
+ async function waitForPageLoad(page: Page): Promise<void> {
19
+ await page.waitForLoadState('networkidle', { timeout: TIMEOUT });
20
+ }
21
+
22
+ async function screenshot(url: string, outputPath: string): Promise<void> {
23
+ let browser: Browser | null = null;
24
+
25
+ try {
26
+ browser = await chromium.launch({ headless: true });
27
+ const context = await browser.newContext({ viewport: VIEWPORT });
28
+ const page = await context.newPage();
29
+
30
+ console.log(`Navigating to ${url}...`);
31
+ await page.goto(url, { timeout: TIMEOUT });
32
+ await waitForPageLoad(page);
33
+
34
+ console.log(`Taking screenshot...`);
35
+ await page.screenshot({ path: outputPath, fullPage: true });
36
+
37
+ console.log(`Screenshot saved to ${outputPath}`);
38
+ } finally {
39
+ if (browser) await browser.close();
40
+ }
41
+ }
42
+
43
+ async function verify(url: string, selector: string): Promise<boolean> {
44
+ let browser: Browser | null = null;
45
+
46
+ try {
47
+ browser = await chromium.launch({ headless: true });
48
+ const context = await browser.newContext({ viewport: VIEWPORT });
49
+ const page = await context.newPage();
50
+
51
+ console.log(`Navigating to ${url}...`);
52
+ await page.goto(url, { timeout: TIMEOUT });
53
+ await waitForPageLoad(page);
54
+
55
+ console.log(`Looking for selector: ${selector}...`);
56
+ const element = await page.$(selector);
57
+
58
+ if (element) {
59
+ console.log(`✓ Found element matching "${selector}"`);
60
+ return true;
61
+ } else {
62
+ console.log(`✗ Element not found: "${selector}"`);
63
+ return false;
64
+ }
65
+ } finally {
66
+ if (browser) await browser.close();
67
+ }
68
+ }
69
+
70
+ async function main(): Promise<void> {
71
+ const [, , command, ...args] = process.argv;
72
+
73
+ switch (command) {
74
+ case 'screenshot': {
75
+ const [url, outputPath] = args;
76
+ if (!url || !outputPath) {
77
+ console.error('Usage: browser-test.ts screenshot <url> <output-path>');
78
+ process.exit(1);
79
+ }
80
+ await screenshot(url, outputPath);
81
+ break;
82
+ }
83
+
84
+ case 'verify': {
85
+ const [url, selector] = args;
86
+ if (!url || !selector) {
87
+ console.error('Usage: browser-test.ts verify <url> <selector>');
88
+ process.exit(1);
89
+ }
90
+ const found = await verify(url, selector);
91
+ process.exit(found ? 0 : 1);
92
+ break;
93
+ }
94
+
95
+ default:
96
+ console.error('Commands: screenshot, verify');
97
+ console.error(' screenshot <url> <output-path> - Take a full page screenshot');
98
+ console.error(' verify <url> <selector> - Check if selector exists on page');
99
+ process.exit(1);
100
+ }
101
+ }
102
+
103
+ main().catch((error) => {
104
+ console.error('Error:', error.message);
105
+ process.exit(1);
106
+ });
@@ -0,0 +1,35 @@
1
+ #!/bin/bash
2
+ # Host-level setup for EC2 instances running Wile
3
+ # These settings cannot be configured inside Docker containers
4
+ # Run this script on EC2 UserData or manually on the host
5
+
6
+ set -e
7
+
8
+ echo "=== Wile Host Setup ==="
9
+
10
+ # Configure swap (8GB)
11
+ echo "Configuring 8GB swap..."
12
+ fallocate -l 8G /swapfile
13
+ chmod 600 /swapfile
14
+ mkswap /swapfile
15
+ swapon /swapfile
16
+ echo '/swapfile none swap sw 0 0' >> /etc/fstab
17
+
18
+ # Set vm.swappiness to 10 (prefer RAM, swap only when needed)
19
+ echo "Setting vm.swappiness=10..."
20
+ sysctl -w vm.swappiness=10
21
+ echo 'vm.swappiness=10' >> /etc/sysctl.conf
22
+
23
+ # Enable zswap with lz4 compression (3x effective swap, faster access)
24
+ echo "Enabling zswap with lz4..."
25
+ echo lz4 > /sys/module/zswap/parameters/compressor
26
+ echo 1 > /sys/module/zswap/parameters/enabled
27
+
28
+ # Install and enable earlyoom (prevents hard freeze)
29
+ echo "Installing earlyoom..."
30
+ apt-get update
31
+ apt-get install -y earlyoom
32
+ systemctl enable earlyoom
33
+ systemctl start earlyoom
34
+
35
+ echo "=== Host setup complete ==="
@@ -0,0 +1,23 @@
1
+ # Wile Setup (Iteration 0)
2
+
3
+ You are running the setup 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
+ If the file is missing or invalid JSON, respond with:
14
+ ```
15
+ <promise>SETUP_FAILED</promise>
16
+ ```
17
+
18
+ Otherwise, **do nothing else**. Do not modify files, do not run git commands, and do not print extra output.
19
+
20
+ ## Notes
21
+
22
+ - Setup should have no side effects.
23
+ - Only emit `<promise>SETUP_FAILED</promise>` on failure.
@@ -0,0 +1,168 @@
1
+ # Wile Agent Instructions
2
+
3
+ You are an autonomous coding agent running in a loop. Each iteration, you complete ONE user story from the backlog, then exit. The loop will call you again for the next story.
4
+
5
+ ## Your Task (Execute in Order)
6
+
7
+ ### 1. Read the Backlog
8
+ ```bash
9
+ cat .wile/prd.json
10
+ ```
11
+ Parse the `userStories` array. Find the highest priority story where `passes: false`.
12
+
13
+ ### 2. Read Progress Log
14
+ ```bash
15
+ cat .wile/progress.txt
16
+ ```
17
+ Check the **Codebase Patterns** section at the top for learnings from previous iterations. Apply these patterns to avoid repeating mistakes.
18
+
19
+ ### 3. Check Current Branch
20
+ ```bash
21
+ git branch --show-current
22
+ git status
23
+ ```
24
+ Ensure you're on the correct branch specified by the `BRANCH_NAME` environment variable.
25
+
26
+ ### 4. Implement the Story
27
+ Pick the highest priority story where `passes: false` and implement it completely.
28
+
29
+ - Read and understand all acceptance criteria
30
+ - Implement the feature/fix
31
+ - Follow existing code patterns in the codebase
32
+ - Keep changes minimal and focused
33
+
34
+ ### 5. Verify Your Work
35
+ Run the project's tests and type checking:
36
+ ```bash
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
40
+ ```
41
+
42
+ If tests or typecheck fail, fix the issues before proceeding.
43
+
44
+ ### 6. Update prd.json
45
+ Set `passes: true` for the completed story:
46
+ ```bash
47
+ # Edit .wile/prd.json to mark the story as complete
48
+ ```
49
+
50
+ ### 7. Log Your Progress
51
+ **APPEND** to `.wile/progress.txt`:
52
+
53
+ ```markdown
54
+ ---
55
+
56
+ ## [DATE] - [STORY-ID]: [Story Title]
57
+
58
+ **Implemented:**
59
+ - What was done
60
+
61
+ **Files changed:**
62
+ - file1.ts
63
+ - file2.ts
64
+
65
+ **Learnings:**
66
+ - Any patterns discovered
67
+ - Gotchas encountered
68
+ - Things to remember for future iterations
69
+ ```
70
+
71
+ If you discovered important patterns, also add them to the **Codebase Patterns** section at the TOP of progress.txt.
72
+
73
+ ### 8. Commit and Push
74
+ ```bash
75
+ git add -A
76
+ git commit -m "feat: [STORY-ID] - [Story Title]"
77
+ git push
78
+ ```
79
+
80
+ Use the exact story ID and title from `.wile/prd.json`.
81
+
82
+ ## Stop Condition
83
+
84
+ After completing steps 1-8, check if ALL stories in `.wile/prd.json` have `passes: true`.
85
+
86
+ **If ALL stories pass**, respond with exactly:
87
+ ```
88
+ <promise>COMPLETE</promise>
89
+ ```
90
+
91
+ **If there are still stories with `passes: false`**, end your response normally. The loop will call you again for the next story.
92
+
93
+ ## Important Rules
94
+
95
+ 1. **ONE story per iteration** - Do not implement multiple stories
96
+ 2. **Always push** - Push after every commit so progress is visible
97
+ 3. **One commit per feature** - Include the implementation, prd.json update, and progress log in a single commit
98
+ 4. **Fix related files** - If typecheck requires changes in other files, make them (this is not scope creep)
99
+ 5. **Be idempotent** - Use `IF NOT EXISTS` for migrations, check before creating files
100
+ 6. **No interactive prompts** - Use `echo -e "\n\n\n" |` if a command might prompt
101
+ 7. **NEVER commit node_modules, dist, or build artifacts** - .gitignore should already be set up by iteration 0
102
+
103
+ ## Common Patterns
104
+
105
+ ### Migrations
106
+ ```sql
107
+ ALTER TABLE x ADD COLUMN IF NOT EXISTS y TEXT;
108
+ ```
109
+
110
+ ### React Refs
111
+ ```typescript
112
+ const ref = useRef<NodeJS.Timeout | null>(null);
113
+ ```
114
+
115
+ ### Skipping Interactive Prompts
116
+ ```bash
117
+ echo -e "\n\n\n" | npm run db:generate
118
+ ```
119
+
120
+ ## Browser Testing (Visual Verification)
121
+
122
+ When acceptance criteria mention UI verification, visual checks, or "verify at localhost":
123
+
124
+ ### Take a Screenshot
125
+ ```bash
126
+ cd /tmp && npm init -y && npm install playwright
127
+ node -e "
128
+ const { chromium } = require('playwright');
129
+ (async () => {
130
+ const browser = await chromium.launch();
131
+ const page = await browser.newPage();
132
+ await page.setViewportSize({ width: 1280, height: 900 });
133
+ await page.goto('http://localhost:3000/your-page');
134
+ await page.waitForLoadState('networkidle');
135
+ await page.screenshot({ path: '.wile/screenshots/verification.png', fullPage: true });
136
+ console.log('Screenshot saved');
137
+ await browser.close();
138
+ })();
139
+ "
140
+ ```
141
+
142
+ ### Verify Element Exists
143
+ ```bash
144
+ node -e "
145
+ const { chromium } = require('playwright');
146
+ (async () => {
147
+ const browser = await chromium.launch();
148
+ const page = await browser.newPage();
149
+ await page.goto('http://localhost:3000/login');
150
+ await page.waitForLoadState('networkidle');
151
+ const element = await page.\$('form#login');
152
+ console.log(element ? 'FOUND: form#login' : 'NOT FOUND: form#login');
153
+ await browser.close();
154
+ })();
155
+ "
156
+ ```
157
+
158
+ Save screenshots to `.wile/screenshots/` with descriptive names.
159
+
160
+ ## Error Recovery
161
+
162
+ If something goes wrong:
163
+ 1. Read the error message carefully
164
+ 2. Fix the issue
165
+ 3. Continue with the current story
166
+ 4. Document the issue in the progress log
167
+
168
+ Do NOT skip to the next story if the current one fails. Fix it first.
@@ -0,0 +1,82 @@
1
+ #!/bin/sh
2
+ set -euo pipefail
3
+
4
+ run_case() {
5
+ LABEL="$1"
6
+ MODE="$2"
7
+ EXPECT_HEADER="$3"
8
+
9
+ TMP_DIR=$(mktemp -d /tmp/wile-additional-test-XXXXXX)
10
+ cleanup() {
11
+ rm -rf "$TMP_DIR"
12
+ }
13
+ trap cleanup EXIT INT TERM
14
+
15
+ SCRIPT_DIR="$TMP_DIR/agent"
16
+ BIN_DIR="$TMP_DIR/bin"
17
+ mkdir -p "$SCRIPT_DIR" "$BIN_DIR"
18
+
19
+ cp /Users/thiagoduarte/Projects/personal/wile/packages/agent/scripts/wile.sh "$SCRIPT_DIR/wile.sh"
20
+ chmod +x "$SCRIPT_DIR/wile.sh"
21
+
22
+ echo "BASE PROMPT" > "$SCRIPT_DIR/prompt.md"
23
+
24
+ ADDITIONAL="$TMP_DIR/additional.md"
25
+ if [ "$MODE" = "comment-only" ]; then
26
+ cat > "$ADDITIONAL" <<'EOF'
27
+ <!--
28
+ Use bullet points for additional instructions, e.g.
29
+ - You may run `supabase db reset --db-url "$SUPABASE_DB_URL"` when needed.
30
+ - Do not ask for permission before running it.
31
+ -->
32
+ EOF
33
+ else
34
+ cat > "$ADDITIONAL" <<'EOF'
35
+ <!--
36
+ Use bullet points for additional instructions, e.g.
37
+ - You may run `supabase db reset --db-url "$SUPABASE_DB_URL"` when needed.
38
+ - Do not ask for permission before running it.
39
+ -->
40
+ - Always reset the database before integration tests.
41
+ EOF
42
+ fi
43
+
44
+ CAPTURE="$TMP_DIR/capture.txt"
45
+
46
+ cat > "$BIN_DIR/claude" <<'EOF'
47
+ #!/bin/sh
48
+ cat > "$CLAUDE_CAPTURE"
49
+ echo "<promise>COMPLETE</promise>"
50
+ EOF
51
+ chmod +x "$BIN_DIR/claude"
52
+
53
+ PATH="$BIN_DIR:$PATH" \
54
+ CLAUDE_CAPTURE="$CAPTURE" \
55
+ WILE_ADDITIONAL_INSTRUCTIONS="$ADDITIONAL" \
56
+ CC_CLAUDE_MODEL="sonnet" \
57
+ "$SCRIPT_DIR/wile.sh" 1 >/dev/null 2>&1
58
+
59
+ if ! grep -q "BASE PROMPT" "$CAPTURE"; then
60
+ echo "error: base prompt missing in $LABEL" >&2
61
+ exit 1
62
+ fi
63
+
64
+ if [ "$EXPECT_HEADER" = "yes" ]; then
65
+ grep -q "## Additional Instructions" "$CAPTURE"
66
+ grep -q "Always reset the database before integration tests." "$CAPTURE"
67
+ else
68
+ if grep -q "## Additional Instructions" "$CAPTURE"; then
69
+ echo "error: header appended unexpectedly in $LABEL" >&2
70
+ exit 1
71
+ fi
72
+ fi
73
+
74
+ rm -rf "$TMP_DIR"
75
+ trap - EXIT INT TERM
76
+ }
77
+
78
+ run_case "comment-only" "comment-only" "no"
79
+
80
+ run_case "with-content" "with-content" "yes"
81
+
82
+ echo "test-additional-instructions: ok"
@@ -0,0 +1,104 @@
1
+ #!/bin/bash
2
+ #
3
+ # Wile - Autonomous AI coding loop
4
+ # Pipes prompt to Claude Code repeatedly until all tasks complete
5
+ #
6
+
7
+ set -e
8
+
9
+ MAX_ITERATIONS=${1:-25}
10
+ CLAUDE_MODEL=${CC_CLAUDE_MODEL:-sonnet}
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ PROMPT_FILE="$SCRIPT_DIR/prompt.md"
13
+ SETUP_PROMPT_FILE="$SCRIPT_DIR/prompt-setup.md"
14
+ ADDITIONAL_PROMPT_FILE="${WILE_ADDITIONAL_INSTRUCTIONS:-}"
15
+
16
+ echo "══════════════════════════════════════════════════════"
17
+ echo " 🗡️ WILE - Autonomous Coding Agent"
18
+ echo "══════════════════════════════════════════════════════"
19
+ echo " Max iterations: $MAX_ITERATIONS"
20
+ echo " Model: $CLAUDE_MODEL"
21
+ echo " Prompt file: $PROMPT_FILE"
22
+ echo "══════════════════════════════════════════════════════"
23
+ echo ""
24
+
25
+ if [ ! -f "$PROMPT_FILE" ]; then
26
+ echo "ERROR: Prompt file not found: $PROMPT_FILE"
27
+ exit 1
28
+ fi
29
+
30
+ if [ -n "$ADDITIONAL_PROMPT_FILE" ] && [ -f "$ADDITIONAL_PROMPT_FILE" ]; then
31
+ if [ -s "$ADDITIONAL_PROMPT_FILE" ]; then
32
+ STRIPPED_CONTENT=$(sed '/<!--/,/-->/d' "$ADDITIONAL_PROMPT_FILE" | tr -d '[:space:]')
33
+ if [ -n "$STRIPPED_CONTENT" ]; then
34
+ PROMPT_FILE="/tmp/wile-prompt.md"
35
+ cat "$SCRIPT_DIR/prompt.md" > "$PROMPT_FILE"
36
+ printf "\n\n## Additional Instructions\n\n" >> "$PROMPT_FILE"
37
+ cat "$ADDITIONAL_PROMPT_FILE" >> "$PROMPT_FILE"
38
+ fi
39
+ fi
40
+ fi
41
+
42
+ # ════════════════════════════════════════════════════════════
43
+ # ITERATION 0: Setup
44
+ # ════════════════════════════════════════════════════════════
45
+ echo ""
46
+ echo "══════════════════════════════════════════════════════"
47
+ echo " Iteration 0 - Setup"
48
+ echo "══════════════════════════════════════════════════════"
49
+ echo ""
50
+
51
+ if [ -f "$SETUP_PROMPT_FILE" ]; then
52
+ OUTPUT=$(cat "$SETUP_PROMPT_FILE" | claude --model "$CLAUDE_MODEL" --dangerously-skip-permissions 2>&1 | tee /dev/stderr) || true
53
+
54
+ # Check if setup failed critically
55
+ if echo "$OUTPUT" | grep -q "<promise>SETUP_FAILED</promise>"; then
56
+ echo ""
57
+ echo "══════════════════════════════════════════════════════"
58
+ echo " ❌ SETUP FAILED - Cannot continue"
59
+ echo "══════════════════════════════════════════════════════"
60
+ exit 2
61
+ fi
62
+
63
+ echo ""
64
+ echo "Setup complete. Starting main loop..."
65
+ sleep 2
66
+ else
67
+ echo "No setup prompt found, skipping iteration 0..."
68
+ fi
69
+
70
+ # ════════════════════════════════════════════════════════════
71
+ # ITERATIONS 1-N: Main loop
72
+ # ════════════════════════════════════════════════════════════
73
+ for i in $(seq 1 $MAX_ITERATIONS); do
74
+ echo ""
75
+ echo "══════════════════════════════════════════════════════"
76
+ echo " Iteration $i of $MAX_ITERATIONS"
77
+ echo "══════════════════════════════════════════════════════"
78
+ echo ""
79
+
80
+ # Pipe prompt to Claude Code
81
+ # --dangerously-skip-permissions allows autonomous operation
82
+ # Capture output while also displaying it (tee to stderr)
83
+ OUTPUT=$(cat "$PROMPT_FILE" | claude --model "$CLAUDE_MODEL" --dangerously-skip-permissions 2>&1 | tee /dev/stderr) || true
84
+
85
+ # Check for completion signal
86
+ if echo "$OUTPUT" | grep -q "<promise>COMPLETE</promise>"; then
87
+ echo ""
88
+ echo "══════════════════════════════════════════════════════"
89
+ echo " ✅ ALL TASKS COMPLETE"
90
+ echo "══════════════════════════════════════════════════════"
91
+ exit 0
92
+ fi
93
+
94
+ echo ""
95
+ echo "Iteration $i complete. Continuing..."
96
+ sleep 2
97
+ done
98
+
99
+ echo ""
100
+ echo "══════════════════════════════════════════════════════"
101
+ echo " ⚠️ MAX ITERATIONS REACHED ($MAX_ITERATIONS)"
102
+ echo " Some tasks may still be pending."
103
+ echo "══════════════════════════════════════════════════════"
104
+ exit 1
package/dist/cli.js CHANGED
@@ -7904,6 +7904,10 @@ var resolveAgentDir = () => {
7904
7904
  return foundFromCwd;
7905
7905
  }
7906
7906
  const here = dirname(fileURLToPath(import.meta.url));
7907
+ const bundledAgentDir = join3(here, "agent");
7908
+ if (existsSync3(bundledAgentDir)) {
7909
+ return bundledAgentDir;
7910
+ }
7907
7911
  const cliRoot = resolve(here, "..", "..", "..");
7908
7912
  const agentDir = join3(cliRoot, "agent");
7909
7913
  if (!existsSync3(agentDir)) {
@@ -8006,12 +8010,17 @@ var runWile = (options) => {
8006
8010
  const logsDir = join3(paths.wileDir, "logs");
8007
8011
  mkdirSync(logsDir, { recursive: true });
8008
8012
  const logPath = join3(logsDir, `run-${getTimestamp()}.log`);
8013
+ writeFileSync(logPath, "", { flag: "w" });
8009
8014
  if (options.test) {
8010
8015
  const message = `TEST MODE: running mocked agent inside Docker.
8011
8016
  `;
8012
8017
  process.stdout.write(message);
8013
8018
  writeFileSync(logPath, message, { flag: "a" });
8014
8019
  }
8020
+ if (options.debug) {
8021
+ console.log(`- logsDir: ${logsDir}`);
8022
+ console.log(`- logPath: ${logPath}`);
8023
+ }
8015
8024
  runDockerWithLogging(dockerArgs, logPath);
8016
8025
  const finishMessage = `Wile run complete. Monitor progress with git log in your repo.
8017
8026
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wile",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Autonomous AI coding agent that ships features while you sleep",
5
5
  "type": "module",
6
6
  "bin": {