eoka-cli 0.2.0

Browser automation CLI for AI agents and pentesting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
---
name: eoka
description: "Drive a real Chrome browser from the shell via the eoka CLI. Persistent daemon keeps Chrome alive between commands (~10ms per call). Use for browser automation, protected-site sessions and AWS WAF CAPTCHA solving, headless screenshots, network recording, HAR export, CDP fetch interception, WASM memory, fake-camera injection, and SPA navigation. Triggers on: eoka, browser cli, AWS WAF, captcha, anti-captcha, headless screenshot, network record, HAR export, intercept request, fake camera, wasm memory, cdp fetch, browser daemon, eoka snapshot, eoka click, eoka fill."
---

# eoka — Browser Automation CLI

Fast browser automation CLI built on the eoka CDP engine. Client-daemon architecture: the first command auto-launches a background daemon that keeps Chrome alive, making subsequent commands instant (~10ms).

## Install

```bash
cargo install --path crates/eoka-cli
# Or run from workspace:
cargo run -p eoka-cli -- <command>
```

## Core Workflow

```bash
eoka open https://example.com          # Navigate (launches browser on first call)
eoka snapshot -i                        # Get accessibility tree with @e1, @e2 refs
eoka click @e2                          # Click by ref from snapshot
eoka fill @e3 "test@example.com"        # Fill input by ref
eoka screenshot -o page.png             # Take screenshot
eoka close                              # Close browser and daemon
```

## Selectors (Target Syntax)

All action commands (`click`, `fill`, `hover`, `scroll`, `select`) accept these target formats:

| Format | Example | Description |
|--------|---------|-------------|
| Ref | `@e1` | From snapshot, resolves via CDP |
| Index | `[0]`, `0`, `index:0` | From observe's full interactive list, not DOM `querySelectorAll()` indices |
| Text | `text:Submit` | Find by visible text |
| Placeholder | `placeholder:Email` | Find by placeholder |
| CSS | `css:#submit-btn` | CSS selector |
| ID | `id:login` | Find by element ID |
| Role | `role:button` | Find by ARIA role |
| Bare text | `"Submit"` | Defaults to text search |

## Commands

### Navigation

```bash
eoka open <url> [--headers '{"Auth": "Bearer ..."}'] [--bypass-csp] [--user-agent UA] [--load-state ./auth.json]
eoka back
eoka forward
eoka reload
```

### Observation (for AI agents)

```bash
eoka snapshot                           # Full accessibility tree with @eN refs
eoka snapshot -i                        # Interactive elements only
eoka observe                            # List elements with indices
eoka observe --filter inputs --max 10   # Only form elements, limit 10
eoka screenshot -o page.png             # Plain screenshot
eoka screenshot --annotate -o page.png  # Annotated with numbered labels
eoka info                               # Current URL and title (JSON)
eoka text                               # All visible text
eoka find "search term"                 # Find elements by text
```

### Actions

```bash
eoka click @e1                          # Click element
eoka fill @e3 "hello"                   # Clear and type into input
eoka select @e5 "Option B"             # Select dropdown option
eoka hover @e2                          # Hover (trigger tooltips/menus)
eoka key Enter                          # Press keyboard key
eoka scroll down                        # Scroll: up/down/top/bottom or element target
eoka double-click @e4                   # Double-click
```

### JavaScript

```bash
eoka eval "document.title"              # Execute JS, return result
eoka eval -f ./script.js                # Execute from file
eoka eval -f ./big_script.js --no-return  # Fire-and-forget (avoids CDP crash on large results)
eoka eval -f ./script.js --max-size 4096  # Truncate result to 4KB (prevents CDP size crash)
eoka exec "localStorage.clear()"       # Execute for side effects (no return)
```

Use `--no-return` or `--max-size` when evaluating scripts that produce large results (e.g. dumping buffers, DOM trees). Without these, CDP can crash on responses exceeding ~10MB.

### Tack TypeScript

```bash
eoka tools manifest --json
eoka tack 'return await tools.invoke({ path: "eoka.info", input: {} });'
eoka tack --raw-json 'const hits = await tools.search({ query: "info", limit: 3 }); return hits.data.map(t => t.path);'
```

`eoka tack` runs Tack TypeScript through `tack-runtime-quickjs` with Eoka tools
auto-registered from `eoka-protocol` through `eoka-tack`. Use `tools.search`,
`tools.describe.tool`, and `tools.invoke` for discovery and calls.

### Network (Pentesting)

```bash
eoka fetch https://api.target.com/me
eoka fetch https://api.target.com/data -m POST --headers '{"Content-Type": "application/json"}' -b '{"key": "value"}'
eoka fetch https://target.com/oauth --redirect manual
eoka fetch https://target.com/app.js --body-only
eoka network record start
eoka network record start --pattern "*/api/*" --max-body-bytes 10485760 --clear
eoka network record status
eoka network log --limit 50 --method POST
eoka network log --since 12 --compact
eoka network show 12
eoka network show 12 --body --max-body 65536
eoka network wait --pattern "*/api/*" --status 200 --timeout 10000
eoka network har /tmp/session.har
eoka network export /tmp/session.json --format json
eoka network record stop
eoka network clear
eoka network intercept add "*/api/data*"
eoka network intercept add "*/biometrics/*" --capture /tmp/req.json
eoka network intercept add "*/config" --respond /tmp/mock.json --status 200
eoka network intercept list
eoka network intercept log
eoka network intercept log --clear
eoka network intercept remove 1
eoka network intercept remove all
```

`fetch` runs from the browser context with the active session's cookies and TLS fingerprint. `network record start` passively records the active tab and follows explicit `eoka tab switch`, `tab attach`, `tab new`, and `open` operations. Recording appends until `network clear`, or starts fresh with `network record start --clear`. `network har` and `network export --format har` snapshot without stopping or clearing; `network export --format json` writes the same captured entries in eoka JSON form. Bodies are captured unredacted by default up to 10 MiB each, with a shared 512 MiB in-memory body pool and bounded concurrent body capture.

`network intercept` uses the CDP Fetch domain to pause matching requests. Intercept logs include `network_entry_id` when passive recording is active, so agents can jump from an intercepted request to `eoka network show <id>` or the exported HAR entry.

### Cookies

```bash
eoka cookies                            # Get all cookies (JSON)
eoka set-cookie session abc123 --domain .target.com
eoka delete-cookie tracking
eoka clear-cookies
```

### CAPTCHA

For install and AWS WAF usage, read [references/captcha.md](references/captcha.md).

```bash
eoka captcha solve --captcha-type recaptcha_v3 --website-url https://target.com --website-key SITE_KEY --page-action submit
eoka captcha inject TOKEN --captcha-type recaptcha
eoka captcha solve --captcha-type recaptcha_v3 --website-url https://target.com --website-key SITE_KEY --page-action submit --inject
eoka captcha inject TOKEN --captcha-type recaptcha --click-after "text:Continue Booking"
```

`captcha inject` sets common response fields and calls discovered grecaptcha/hCaptcha callbacks. Use `--callback window.someCallback` when the page exposes a specific callback. Some pages do not automatically retry after a callback fires; use `--click-after` to click the continuation control after injection.

### Storage

```bash
eoka storage                            # All localStorage
eoka storage token                      # Get specific key
eoka storage --session-storage          # sessionStorage instead
eoka set-storage theme dark
eoka dump-storage                       # Both local and session storage
```

### State Persistence

```bash
eoka save-state ./auth.json             # Save cookies + storage + URL
eoka load-state ./auth.json             # Restore, navigate to saved URL, then reload so app auth initializes
eoka load-state ./auth.json --no-navigate  # Restore into current origin; reloads current web page when possible
eoka open /camping/campsites/71576 --load-state ./auth.json  # Restore before this deep-link navigation
```

### Extra Headers

```bash
eoka headers '{"Authorization": "Bearer eyJ...", "X-Custom": "value"}'
```

### Console / Errors

```bash
eoka console                            # Read console output
eoka console --level error              # Filter by level
eoka console --clear                    # Clear after reading
eoka errors                             # Read uncaught JS errors
```

### Tabs

```bash
eoka tab list                           # List tabs (* = current)
eoka tab new https://other.com          # Open new tab
eoka tab switch <tab-id>                # Switch to tab
eoka tab close <tab-id>                 # Close tab
```

### Wait

```bash
eoka wait 2000                          # Wait 2 seconds
eoka wait --text "Welcome"              # Wait for text to appear
eoka wait --url "**/dashboard"          # Wait for URL pattern
eoka wait --load networkidle            # Wait for network idle
```

### Fake Camera

Inject a fake video stream into `getUserMedia` — replaces the real camera with frames from a video file. No OS virtual cam driver needed; works at the JS level.

```bash
eoka fake-camera /path/to/face.mp4              # Inject fake camera from video
eoka fake-camera /path/to/face.webm --loop-video # Loop the video
eoka open https://app-with-camera.com            # Page sees fake stream from getUserMedia
eoka eval "navigator.mediaDevices.getUserMedia({video:true}).then(s => s.getVideoTracks().length)"
# Returns 1
```

Internally: overrides `navigator.mediaDevices.getUserMedia`, draws video frames to a hidden canvas via `requestAnimationFrame`, returns `canvas.captureStream(30)`. Also fakes `enumerateDevices` to report a camera and grants camera permissions via CDP. Video is base64-encoded inline (works for files up to ~5MB). Persists across navigations via `addScriptToEvaluateOnNewDocument`.

### WASM Memory

Direct access to WebAssembly linear memory without eval gymnastics. Auto-detects memory exports (`window.__ft.mem`, `Module.wasmMemory`, or scans globals).

```bash
eoka wasm info                                   # List detected WASM memory instances
eoka wasm read 0x360000 32                       # Hex dump 32 bytes at address
eoka wasm read 0x360000 1024 --memory "window.__ft.mem"  # Explicit memory path
eoka wasm write 0x360000 deadbeef                # Write bytes at address
eoka wasm write 0x360000 "ff d8 ff e0"           # Spaces in hex are stripped
eoka wasm find "ff d8 ff e0"                     # Search for JPEG magic bytes
eoka wasm find "00 00 00 01" --start 0x300000 --end 0x400000 --max 5
```

Reads are chunked (64KB) to avoid CDP message size limits. Output is formatted as hex dump (xxd-style). Addresses support `0x` hex prefix or decimal.

### SPA Navigation

```bash
eoka spa-info                           # Detect router (React, Next.js, Vue, etc.)
eoka spa-navigate /dashboard            # Navigate without page reload
```

### Script Blocking (NoScript-style)

Disable JS execution by default and allow it only on specific domains — the
same idea as the NoScript extension, applied per top-level navigation (CDP
has no per-frame equivalent, so a third-party iframe on an allowed page runs
JS too; there's no way to allow the main frame while blocking an embedded
one).

```bash
eoka --no-js open https://target.com                   # start blocked (Safest-style)
eoka js allow example.com                               # exception: JS runs here (and subdomains)
eoka js mode allow-all                                  # flip default: run JS everywhere...
eoka js block evil.example                               # ...except this domain
eoka js remove example.com                               # drop an allow/block exception
eoka js list                                             # current mode + exception lists
```

`--js-allow <DOMAIN>` / `--js-block <DOMAIN>` (repeatable) seed exceptions at
launch instead of adding them after the fact. All of `--no-js`,
`--js-allow`, `--js-block` are ignored in `--cdp`/`--auto-connect` mode,
same as `--proxy`/`--headed`/etc. — eoka never touches your own live tabs.

The policy is re-evaluated on `open` and `reload` (against the destination
and current URL respectively). `back`/`forward` don't re-evaluate it — CDP
doesn't expose the destination URL before they complete, so they carry
whatever was last applied.

## Sessions

Run multiple isolated browsers — each `--session` name gets its own daemon,
Chrome instance, and Unix socket, so they never share state:

```bash
eoka --session pentest1 open https://target-a.com
eoka --session pentest2 open https://target-b.com
eoka --session pentest1 snapshot        # Interacts with first browser
```

Combine with `--proxy` to run one session through a proxy and another
direct:

```bash
eoka --session tor --proxy socks5://127.0.0.1:9050 open https://check.torproject.org
eoka --session regular open https://example.com
```

List every session (running or stale), like `tmux ls`:

```bash
eoka sessions
eoka --json sessions                    # structured output
```

## Driving Your Real Chrome

Three ways to use eoka against an existing Chrome instead of a fresh headless one:

### 1. Attach via CDP (live control)

Start Chrome with remote debugging, then point eoka at it:

```bash
google-chrome --remote-debugging-port=9222 &
eoka --cdp 9222 snapshot                # snapshots the front tab
eoka --cdp 9222 click @e3
eoka --cdp 9222 open https://gmail.com  # opens a NEW tab — your tabs stay
eoka --cdp 9222 close                   # disconnects, leaves Chrome running
eoka --auto-connect snapshot            # find any Chrome on 9222-9229
eoka cdp-url --port 9222                # print ws:// URL (for piping)
```

In CDP mode, eoka:
- skips evasion-script injection (your tabs stay clean),
- disables the stealth CDP filter (full DevTools-equivalent control),
- auto-attaches to the most recent user tab on first command,
- never sends `Browser.close` to a Chrome it doesn't own.

### 2. Clone state from a running Chrome

Snapshot cookies + storage from your real Chrome, then drive a fresh headless
session with that auth:

```bash
# Either save to a file...
eoka clone-from 9222 --to state.json
eoka --state state.json open https://protected-app.com

# ...or hydrate directly into a launched headless session:
eoka --clone-state-from 9222 open https://protected-app.com
```

Captures HttpOnly cookies via CDP (which JS can't reach), localStorage,
sessionStorage, and the User-Agent.

### 3. Clone the profile directory

Copy your Chrome profile to a tempdir and launch headless against the copy.
Picks up encrypted cookies via the OS keyring as the same user:

```bash
eoka --from-profile auto open https://protected-app.com
eoka --from-profile ~/.config/google-chrome/Default open https://x.com
```

Caveats:
- Chrome refuses two instances on one user-data-dir, hence the copy.
- Chrome 127+ on Windows uses App-Bound Encryption keyed to the binary path —
  cookies may silently fail to decrypt if eoka's Chrome isn't the same install.

## Options

| Flag | Description |
|------|-------------|
| `--session <name>` | Isolated browser session (default: "default") |
| `--json` | JSON output mode (for agent integration) |
| `--headed` | Show browser window (default: headless) |
| `--cdp <PORT\|URL>` | Connect to a running Chrome (`9222` or `ws://...`) instead of launching |
| `--auto-connect` | Discover a Chrome on ports 9222–9229 |
| `--clone-state-from <PORT\|URL>` | After launch, hydrate cookies/storage from a running Chrome |
| `--from-profile <auto\|PATH>` | Clone an existing Chrome profile and launch against the copy |
| `--proxy <URL>` | Proxy for the launched browser: `socks5://host:port` or `http://host:port`, optionally with `user:pass@`. Conflicts with `--proxy-file` |
| `--proxy-file <FILE>` | Pick a proxy at random from a file (one per line, `#` comments allowed). Conflicts with `--proxy` |
| `--no-js` | Start with JS execution blocked by default (see Script Blocking) |
| `--js-allow <DOMAIN>` | Seed a JS allow exception at launch. Repeatable |
| `--js-block <DOMAIN>` | Seed a JS block exception at launch. Repeatable |

## Environment Variables

| Variable | Description |
|----------|-------------|
| `EOKA_HEADLESS` | `false` or `0` to show browser window |
| `EOKA_PROXY` | Fallback for `--proxy` when the flag isn't passed: `socks5://user:pass@host:port` or `http://user:pass@host:port` |
| `EOKA_PROXY_FILE` | Fallback for `--proxy-file` when the flag isn't passed |
| `EOKA_NO_JS` | Fallback for `--no-js` when the flag isn't passed |
| `EOKA_PATCH_BINARY` | `true` to apply stealth binary patches |
| `EOKA_CHROME_ARGS` | Extra Chrome flags, colon-separated (e.g. `--use-fake-ui-for-media-stream:--allow-insecure-localhost`) |
| `EOKA_IDLE_TIMEOUT` | Daemon idle timeout in ms (default: 1800000 = 30min) |
| `EOKA_CDP` | Default `--cdp` value (port or ws:// URL) |
| `EOKA_AUTO_CONNECT` | `1` to default to `--auto-connect` |
| `EOKA_FROM_PROFILE` | Default `--from-profile` value (`auto` or path) |

## Daemon Management

```bash
eoka status                             # Check if daemon is running (for --session)
eoka sessions                           # List every session, running or stale
eoka kill                               # Force-kill daemon
eoka close                              # Graceful close (browser + daemon)
```

The daemon auto-starts on first command and auto-exits after 30 minutes of inactivity. Logs are at `/tmp/eoka/eoka-<session>.log`.

## AI Agent Integration

Use `--json` for structured output:

```bash
eoka snapshot -i --json                 # Returns JSON with tree + refs
eoka info --json                        # Returns {"url": "...", "title": "..."}
eoka cookies --json                     # Returns cookie array
```

### Optimal AI Workflow

```bash
# 1. Navigate and observe
eoka open https://target.com
eoka snapshot -i --json                 # Parse refs

# 2. Interact using refs
eoka click @e2
eoka fill @e3 "input text"

# 3. Re-snapshot after page changes
eoka snapshot -i --json

# 4. Chain commands for efficiency
eoka fill @e1 "user" && eoka fill @e2 "pass" && eoka click @e3
```