surfagent 1.1.1 → 1.1.2
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/AGENT.md +200 -0
- package/SURFAGENT_BUG_REPORT.md +199 -0
- package/dist/api/act.js +1 -1
- package/dist/api/recon.js +12 -2
- package/dist/api/server.js +1 -1
- package/dist/browser.js +4 -4
- package/dist/cli.js +4 -3
- package/package.json +1 -1
- package/src/api/act.ts +1 -1
- package/src/api/recon.ts +10 -2
- package/src/api/server.ts +1 -1
- package/src/browser.ts +4 -4
- package/src/cli.ts +3 -3
package/AGENT.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
## IMPORTANT: Never kill Chrome
|
|
2
|
+
|
|
3
|
+
`surfagent` launches a **separate Chrome window** with its own profile. The user's personal Chrome stays untouched. NEVER run `pkill Chrome`, `killall Chrome`, or any command that kills Chrome processes. If you need to restart the debug session, use `surfagent start` — it will launch a new one without affecting anything.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Critical Rule: Always Close Unused Tabs
|
|
8
|
+
|
|
9
|
+
After completing any task, close tabs you no longer need:
|
|
10
|
+
```bash
|
|
11
|
+
curl -X POST localhost:3456/click -H 'Content-Type: application/json' -d '{"tab":"0","text":"close"}'
|
|
12
|
+
# Or via CLI:
|
|
13
|
+
node dist/browser.js close all
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
### Start everything (one command)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
surfagent start
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This will:
|
|
27
|
+
1. Check if Chrome debug session is already running on port 9222
|
|
28
|
+
2. If not, launch a **new Chrome window** with a separate profile (`/tmp/surfagent-chrome`)
|
|
29
|
+
3. Copy cookies from the user's default Chrome profile (preserves logins)
|
|
30
|
+
4. Start the API server on `http://localhost:3456`
|
|
31
|
+
|
|
32
|
+
The user's personal Chrome browser is NOT affected. A second Chrome window will appear — this is the debug session that the API controls.
|
|
33
|
+
|
|
34
|
+
### Other commands
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
surfagent start # Chrome + API (recommended)
|
|
38
|
+
surfagent chrome # Launch Chrome debug session only
|
|
39
|
+
surfagent api # Start API only (Chrome must already be running)
|
|
40
|
+
surfagent health # Check if Chrome and API are running
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## API Endpoints (preferred for agents)
|
|
46
|
+
|
|
47
|
+
The API is the primary interface. Always **recon first, then act**.
|
|
48
|
+
|
|
49
|
+
See `API.md` for full documentation with examples.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Recon a page — get full map of elements, forms, selectors
|
|
53
|
+
curl -X POST localhost:3456/recon -H 'Content-Type: application/json' -d '{"url":"https://example.com","keepTab":true}'
|
|
54
|
+
|
|
55
|
+
# Read page content — structured text, tables, notifications
|
|
56
|
+
curl -X POST localhost:3456/read -H 'Content-Type: application/json' -d '{"tab":"0"}'
|
|
57
|
+
|
|
58
|
+
# Fill form fields — real CDP keystrokes
|
|
59
|
+
curl -X POST localhost:3456/fill -H 'Content-Type: application/json' -d '{"tab":"0","fields":[{"selector":"#email","value":"test@example.com"}],"submit":"enter"}'
|
|
60
|
+
|
|
61
|
+
# Click an element
|
|
62
|
+
curl -X POST localhost:3456/click -H 'Content-Type: application/json' -d '{"tab":"0","text":"Submit"}'
|
|
63
|
+
|
|
64
|
+
# Scroll
|
|
65
|
+
curl -X POST localhost:3456/scroll -H 'Content-Type: application/json' -d '{"tab":"0","direction":"down","amount":1000}'
|
|
66
|
+
|
|
67
|
+
# Navigate (same tab)
|
|
68
|
+
curl -X POST localhost:3456/navigate -H 'Content-Type: application/json' -d '{"tab":"0","url":"https://example.com"}'
|
|
69
|
+
|
|
70
|
+
# Go back
|
|
71
|
+
curl -X POST localhost:3456/navigate -H 'Content-Type: application/json' -d '{"tab":"0","back":true}'
|
|
72
|
+
|
|
73
|
+
# Run JavaScript in a tab or iframe
|
|
74
|
+
curl -X POST localhost:3456/eval -H 'Content-Type: application/json' -d '{"tab":"0","expression":"document.title"}'
|
|
75
|
+
|
|
76
|
+
# Bring tab to front
|
|
77
|
+
curl -X POST localhost:3456/focus -H 'Content-Type: application/json' -d '{"tab":"0"}'
|
|
78
|
+
|
|
79
|
+
# Raw key typing — no clear step, for Google Sheets / contenteditable / canvas
|
|
80
|
+
curl -X POST localhost:3456/type -H 'Content-Type: application/json' -d '{"tab":"0","keys":"Hello","submit":"tab"}'
|
|
81
|
+
|
|
82
|
+
# Captcha detection and interaction (experimental)
|
|
83
|
+
curl -X POST localhost:3456/captcha -H 'Content-Type: application/json' -d '{"tab":"0","action":"detect"}'
|
|
84
|
+
|
|
85
|
+
# List tabs
|
|
86
|
+
curl localhost:3456/tabs
|
|
87
|
+
|
|
88
|
+
# Health check
|
|
89
|
+
curl localhost:3456/health
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Google Sheets
|
|
93
|
+
|
|
94
|
+
Google Sheets requires `/type` instead of `/fill` for cell input (because `/fill` does Ctrl+A which selects all cells). Use the name box to navigate, then `/type` to enter data:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# 1. Click the name box
|
|
98
|
+
curl -X POST localhost:3456/click -H 'Content-Type: application/json' -d '{"tab":"sheets","selector":"#t-name-box"}'
|
|
99
|
+
|
|
100
|
+
# 2. Navigate to a cell
|
|
101
|
+
curl -X POST localhost:3456/fill -H 'Content-Type: application/json' -d '{"tab":"sheets","fields":[{"selector":"#t-name-box","value":"A1","clear":true}],"submit":"enter"}'
|
|
102
|
+
|
|
103
|
+
# 3. Type into the cell (Tab moves right, Enter moves down)
|
|
104
|
+
curl -X POST localhost:3456/type -H 'Content-Type: application/json' -d '{"tab":"sheets","keys":"=SUM(B2:B10)","submit":"tab"}'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Some Sheets buttons (Add Sheet +, toolbar) only respond to CDP mouse events, not DOM clicks. See `API.md` for the CDP mouse click pattern.
|
|
108
|
+
|
|
109
|
+
**Warning:** Navigating away from unsaved Sheets triggers a native Chrome "Leave page?" dialog that blocks ALL CDP commands. See `API.md` > "Native Chrome Dialogs" for detection and dismissal via Swift AX API.
|
|
110
|
+
|
|
111
|
+
### Tab Targeting
|
|
112
|
+
|
|
113
|
+
All endpoints accept a `tab` field:
|
|
114
|
+
- `"0"` — by index
|
|
115
|
+
- `"github"` — partial URL/title match
|
|
116
|
+
- `"cdpn.io"` — matches cross-origin iframes too
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## CLI Commands
|
|
121
|
+
|
|
122
|
+
The CLI is useful for quick manual operations and debugging.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
node dist/browser.js list # List all open tabs
|
|
126
|
+
node dist/browser.js content <tab> # Get text content from a tab
|
|
127
|
+
node dist/browser.js content <tab> -s "sel" # Get text from specific CSS selector
|
|
128
|
+
node dist/browser.js content-all # Get content from all tabs
|
|
129
|
+
node dist/browser.js elements <tab> # List interactive elements
|
|
130
|
+
node dist/browser.js click <tab> <text> # Click element by text
|
|
131
|
+
node dist/browser.js type <tab> <text> # Type into input field
|
|
132
|
+
node dist/browser.js open <url> # Navigate to URL
|
|
133
|
+
node dist/browser.js open <url> --new-tab # Open in new tab
|
|
134
|
+
node dist/browser.js screenshot <tab> -o f # Take screenshot
|
|
135
|
+
node dist/browser.js search <query> # Search across all tabs
|
|
136
|
+
node dist/browser.js html <tab> # Get full HTML of page
|
|
137
|
+
node dist/browser.js html <tab> -s "sel" # Get HTML of specific element
|
|
138
|
+
node dist/browser.js desc <tab> # Get item description from JSON-LD/meta
|
|
139
|
+
node dist/browser.js close <tab> # Close a specific tab
|
|
140
|
+
node dist/browser.js close all # Close all tabs except first
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Architecture
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
surfagent start
|
|
149
|
+
│
|
|
150
|
+
├── Launches Chrome (separate window, separate profile)
|
|
151
|
+
│ └── --remote-debugging-port=9222
|
|
152
|
+
│ --user-data-dir=/tmp/surfagent-chrome
|
|
153
|
+
│
|
|
154
|
+
└── Starts API Server (:3456)
|
|
155
|
+
│
|
|
156
|
+
├── src/api/recon.ts (page reconnaissance)
|
|
157
|
+
├── src/api/act.ts (fill, click, scroll, read, navigate, eval, captcha)
|
|
158
|
+
└── src/api/server.ts (HTTP routing)
|
|
159
|
+
│
|
|
160
|
+
└── src/chrome/ (CDP connection layer)
|
|
161
|
+
│
|
|
162
|
+
▼
|
|
163
|
+
Chrome (:9222) ← separate window, user's Chrome untouched
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Troubleshooting
|
|
169
|
+
|
|
170
|
+
**"Cannot connect to Chrome"**
|
|
171
|
+
- Run `surfagent start` — it handles everything
|
|
172
|
+
- If Chrome is already running with debug mode, use `surfagent api` to just start the API
|
|
173
|
+
|
|
174
|
+
**A second Chrome window appeared**
|
|
175
|
+
- This is expected. `surfagent` runs its own Chrome with a separate profile. Your personal Chrome is not affected. Close the surfagent Chrome window when you're done.
|
|
176
|
+
|
|
177
|
+
**Elements not found**
|
|
178
|
+
- Always `/recon` first to see what's available
|
|
179
|
+
- Try partial text matching with `/click`
|
|
180
|
+
- Some elements are `role="option"` or `li` with `aria-label` — use selector from recon
|
|
181
|
+
|
|
182
|
+
**Form fields not filling**
|
|
183
|
+
- Use the API `/fill` endpoint — it uses real CDP keystrokes
|
|
184
|
+
- For SPAs, use `"submit": "enter"` instead of clicking submit buttons
|
|
185
|
+
|
|
186
|
+
**Links opening new tabs instead of navigating**
|
|
187
|
+
- The API `/click` handles `target="_blank"` automatically
|
|
188
|
+
- It overrides the target and navigates in the same tab
|
|
189
|
+
|
|
190
|
+
**Cross-origin iframes**
|
|
191
|
+
- Target them by their domain: `"tab": "cdpn.io"`
|
|
192
|
+
- CDP connects to iframes as separate targets
|
|
193
|
+
|
|
194
|
+
**Tab hidden behind other tabs**
|
|
195
|
+
- Use `/focus` to bring it to front
|
|
196
|
+
- `/navigate` does this automatically
|
|
197
|
+
|
|
198
|
+
**Too many tabs open**
|
|
199
|
+
- Use `close all` to close all but first tab
|
|
200
|
+
- Always clean up after tasks
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Surfagent v1.1.0 — QA Bug Report
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-04-13
|
|
4
|
+
**Tester:** Claude (automated QA)
|
|
5
|
+
**Environment:** macOS Darwin 24.5.0, Chrome via CDP port 9222, API port 3500
|
|
6
|
+
**Scope:** All API endpoints, CLI commands, error handling, edge cases (single-tab browsing)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## BUGS
|
|
11
|
+
|
|
12
|
+
### BUG-1: `/click` response `clicked` field returns empty text [Medium]
|
|
13
|
+
|
|
14
|
+
**Steps:** Click any element by text or selector on any page.
|
|
15
|
+
**Expected:** `clicked` field should contain the element tag + visible text (e.g. `"A: Show HN"`).
|
|
16
|
+
**Actual:** Returns empty text like `"A: "`, `"TEXTAREA: "` on many elements.
|
|
17
|
+
|
|
18
|
+
**Root cause:** In `act.ts:219`, the click handler uses `el.innerText || el.value` but many elements (links with nested spans, textareas before user input) return empty `innerText`. The code also doesn't check `el.textContent` or `el.getAttribute('aria-label')` as fallbacks.
|
|
19
|
+
|
|
20
|
+
**File:** `src/api/act.ts`, lines 218-219
|
|
21
|
+
**Suggested fix:** Use the same `getText()` helper from `recon.ts` which checks `innerText`, `textContent`, `value`, `aria-label`, `title`, `alt`, and `name`.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
### BUG-2: HTML entities in tab titles not decoded [Low]
|
|
26
|
+
|
|
27
|
+
**Steps:** Open a page with `&` in the title (e.g. BBC). Call `GET /tabs` or CLI `list`.
|
|
28
|
+
**Expected:** Title shows `&` (decoded).
|
|
29
|
+
**Actual:** Title shows `&` (HTML entity).
|
|
30
|
+
|
|
31
|
+
**Example:** `"BBC Home - Breaking News, World News, US News, Sports, Business, Innovation, Climate, Culture, Travel, Video & Audio"`
|
|
32
|
+
|
|
33
|
+
**Root cause:** Chrome CDP returns HTML-encoded titles for some pages. `tabs.ts` passes them through without decoding.
|
|
34
|
+
|
|
35
|
+
**File:** `src/chrome/tabs.ts`
|
|
36
|
+
**Suggested fix:** Decode HTML entities in title after receiving from CDP (e.g. replace `&` -> `&`, `<` -> `<`, etc.).
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### BUG-3: `reconUrl` title discrepancy vs `reconTab` [Low]
|
|
41
|
+
|
|
42
|
+
**Steps:** Call `/recon` with `{"url":"https://httpbin.org/html", "keepTab":true}`. Then call `/recon` with `{"tab":"httpbin"}`.
|
|
43
|
+
**Expected:** Both return the same title.
|
|
44
|
+
**Actual:**
|
|
45
|
+
- `reconUrl` returns `title: ""` (uses `document.title` which is empty)
|
|
46
|
+
- `reconTab` returns `title: "httpbin.org/html"` (uses CDP target title, which Chrome auto-generates)
|
|
47
|
+
|
|
48
|
+
**Root cause:** `reconUrl` reads title via `document.title` in JS. `reconTab` reads from CDP target metadata. When a page has no `<title>` tag, these diverge.
|
|
49
|
+
|
|
50
|
+
**File:** `src/api/recon.ts`, lines 345-348 vs 434
|
|
51
|
+
**Suggested fix:** In `reconUrl`, fall back to CDP target title when `document.title` is empty.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### BUG-4: Windows platform: `getChromePath()` uses Unix-only `test -f` [Medium]
|
|
56
|
+
|
|
57
|
+
**Steps:** Run `surfagent start` on Windows.
|
|
58
|
+
**Expected:** Chrome is found at standard Windows paths.
|
|
59
|
+
**Actual:** `execSync('test -f "..."')` always fails on Windows (no `test` command in cmd.exe).
|
|
60
|
+
|
|
61
|
+
**File:** `src/cli.ts`, lines 56-58
|
|
62
|
+
**Suggested fix:** Use `fs.existsSync()` instead of `execSync('test -f ...')` for cross-platform compatibility.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### BUG-5: `-h` flag conflict in CLI — `--host` shadows `--help` [Medium]
|
|
67
|
+
|
|
68
|
+
**Steps:** Run `node dist/browser.js -h`
|
|
69
|
+
**Expected:** Shows help output.
|
|
70
|
+
**Actual:** Error: `option '-h, --host <string>' argument missing`
|
|
71
|
+
|
|
72
|
+
**Root cause:** Commander.js reserves `-h` for `--help` by default, but the program overrides it with `-h, --host`.
|
|
73
|
+
|
|
74
|
+
**File:** `src/browser.ts`, line 27
|
|
75
|
+
**Suggested fix:** Change host shorthand to `-H` or `--host` only (no shorthand). Keep `-h` for help.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
### BUG-6: CLI version mismatch [Low]
|
|
80
|
+
|
|
81
|
+
**Steps:** Run `node dist/browser.js --version`
|
|
82
|
+
**Expected:** Shows `1.1.0` (matching package.json).
|
|
83
|
+
**Actual:** Shows `1.0.0`.
|
|
84
|
+
|
|
85
|
+
**File:** `src/browser.ts`, line 20 — `.version('1.0.0')` should be `1.1.0`
|
|
86
|
+
**Suggested fix:** Read version from package.json or update hardcoded string.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### BUG-7: CLI program name is "browser" not "surfagent" [Low]
|
|
91
|
+
|
|
92
|
+
**Steps:** Run `node dist/browser.js --help`
|
|
93
|
+
**Expected:** `Usage: surfagent [options] [command]`
|
|
94
|
+
**Actual:** `Usage: browser [options] [command]`
|
|
95
|
+
|
|
96
|
+
**File:** `src/browser.ts`, line 19 — `.name('browser')` should be `.name('surfagent')`
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### BUG-8: Help text references wrong repo URL [Low]
|
|
101
|
+
|
|
102
|
+
**Steps:** Run `npx surfagent help`
|
|
103
|
+
**Actual output:** `Full API docs: https://github.com/AllAboutAI-YT/webpilot#readme`
|
|
104
|
+
**Expected:** Should reference `https://github.com/AllAboutAI-YT/surfagent#readme`
|
|
105
|
+
|
|
106
|
+
**File:** `src/cli.ts`, line 133
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### BUG-9: 404 error message lists only 3 of 12+ endpoints [Low]
|
|
111
|
+
|
|
112
|
+
**Steps:** Hit any unknown endpoint (e.g. `GET /notreal`).
|
|
113
|
+
**Actual:** `"Not found. Endpoints: POST /recon, GET /tabs, GET /health"`
|
|
114
|
+
**Expected:** Should list all endpoints or link to docs.
|
|
115
|
+
|
|
116
|
+
**File:** `src/api/server.ts`, line 211
|
|
117
|
+
**Suggested fix:** List all endpoints or say `"Not found. See GET /health for status or docs at <url>"`
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### BUG-10: Error messages echo back full user input (info leak / response bloat) [Low]
|
|
122
|
+
|
|
123
|
+
**Steps:** Send a request with an extremely long tab pattern (e.g. 10,000 chars).
|
|
124
|
+
**Actual:** Error response includes the full 10K string: `"Tab not found: AAAA...AAAA"`
|
|
125
|
+
**Expected:** Error should truncate the echoed input.
|
|
126
|
+
|
|
127
|
+
**File:** `src/api/act.ts`, line 25
|
|
128
|
+
**Suggested fix:** Truncate `tabPattern` in error messages to ~100 chars.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## IMPROVEMENTS
|
|
133
|
+
|
|
134
|
+
### IMP-1: No `/close` API endpoint [High]
|
|
135
|
+
|
|
136
|
+
The CLI has a `close` command but there is no HTTP API equivalent. Agents using the API cannot close tabs without using the CLI. The CLAUDE.md docs suggest using `/click` with text "close" which is unreliable.
|
|
137
|
+
|
|
138
|
+
**Suggested:** Add `POST /close` endpoint with `{"tab": "...", "all": false}`.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
### IMP-2: `/fill` returns HTTP 200 even when all fields fail [Medium]
|
|
143
|
+
|
|
144
|
+
When every field in a `/fill` request fails (e.g. all selectors not found), the response is HTTP 200 with `success: false` in each field. An agent checking only HTTP status would think the operation succeeded.
|
|
145
|
+
|
|
146
|
+
**Suggested:** Return HTTP 207 (Multi-Status) or at minimum add a top-level `"allSucceeded": false` field.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### IMP-3: No timeout parameter on `/click` with `waitAfter` [Low]
|
|
151
|
+
|
|
152
|
+
`/click` supports `waitAfter` but caps it at 10 seconds silently. There's no feedback that the wait was capped.
|
|
153
|
+
|
|
154
|
+
**Suggested:** Return `"waitCapped": true` in response when `waitAfter` exceeds 10s.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
### IMP-4: `/scroll` contentPreview unreliable on pages with fixed/sticky sidebars [Low]
|
|
159
|
+
|
|
160
|
+
The scroll content preview skips `position: fixed/sticky` elements via `getComputedStyle` check on `el.closest('nav, aside, ...')`, but many sticky sidebars use `position: sticky` on a non-nav/aside element. Some content may be incorrectly filtered.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### IMP-5: `/dismiss` has limited cookie banner coverage [Low]
|
|
165
|
+
|
|
166
|
+
Tested on BBC — no cookie banner dismissed despite BBC having a consent banner. The consent patterns list is good but may miss banners that use custom markup (not standard `<button>` elements, or that appear inside shadow DOM or iframes).
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### IMP-6: No `POST /screenshot` API endpoint [Medium]
|
|
171
|
+
|
|
172
|
+
The CLI has a `screenshot` command but there's no HTTP API equivalent. Screenshots are essential for agent visual verification.
|
|
173
|
+
|
|
174
|
+
**Suggested:** Add `POST /screenshot` returning base64-encoded image or saving to a path.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
### IMP-7: `startChrome()` copies cookies but not LocalStorage/IndexedDB [Low]
|
|
179
|
+
|
|
180
|
+
Cookies are copied from the default Chrome profile, preserving some logins. But many modern apps use LocalStorage or IndexedDB for auth tokens (e.g. SPAs), which are not copied.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### IMP-8: No rate limiting or request queuing [Low]
|
|
185
|
+
|
|
186
|
+
Concurrent CDP connections to the same tab could cause race conditions. While basic parallel reads worked in testing, concurrent writes (fill + click on same tab) could conflict.
|
|
187
|
+
|
|
188
|
+
**Suggested:** Add optional request queuing per tab to serialize mutations.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## SUMMARY
|
|
193
|
+
|
|
194
|
+
| Category | Count | Critical | Medium | Low |
|
|
195
|
+
|----------|-------|----------|--------|-----|
|
|
196
|
+
| Bugs | 10 | 0 | 3 | 7 |
|
|
197
|
+
| Improvements | 8 | 0 | 3 | 5 |
|
|
198
|
+
|
|
199
|
+
**Overall assessment:** The core functionality (recon, navigate, fill, click, scroll, read, eval) works reliably. Error handling is solid across all endpoints with proper HTTP status codes and descriptive messages. The main issues are cosmetic/documentation bugs and missing API parity with the CLI. The most impactful improvements would be adding `/close` and `/screenshot` API endpoints.
|
package/dist/api/act.js
CHANGED
|
@@ -20,7 +20,7 @@ async function resolveTab(tabPattern, port, host) {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
if (!tab)
|
|
23
|
-
throw new Error(`Tab not found: ${tabPattern}`);
|
|
23
|
+
throw new Error(`Tab not found: ${tabPattern.length > 100 ? tabPattern.substring(0, 100) + '...' : tabPattern}`);
|
|
24
24
|
return tab;
|
|
25
25
|
}
|
|
26
26
|
export async function fillFields(request, options) {
|
package/dist/api/recon.js
CHANGED
|
@@ -286,7 +286,17 @@ export async function reconUrl(url, options) {
|
|
|
286
286
|
expression: 'document.title',
|
|
287
287
|
returnByValue: true
|
|
288
288
|
});
|
|
289
|
-
|
|
289
|
+
let title = titleResult.result.value || '';
|
|
290
|
+
// Fall back to CDP target title when document.title is empty (e.g. pages with no <title> tag)
|
|
291
|
+
if (!title) {
|
|
292
|
+
try {
|
|
293
|
+
const targets = await CDP.List({ port, host });
|
|
294
|
+
const t = targets.find((t) => t.id === target.id);
|
|
295
|
+
if (t?.title)
|
|
296
|
+
title = t.title;
|
|
297
|
+
}
|
|
298
|
+
catch { }
|
|
299
|
+
}
|
|
290
300
|
// Get current URL (may have redirected)
|
|
291
301
|
const urlResult = await client.Runtime.evaluate({
|
|
292
302
|
expression: 'window.location.href',
|
|
@@ -307,7 +317,7 @@ export async function reconUrl(url, options) {
|
|
|
307
317
|
}
|
|
308
318
|
return {
|
|
309
319
|
url: finalUrl,
|
|
310
|
-
title,
|
|
320
|
+
title: title || target.title || '',
|
|
311
321
|
tabId: target.id,
|
|
312
322
|
timestamp: new Date().toISOString(),
|
|
313
323
|
meta: data.meta,
|
package/dist/api/server.js
CHANGED
|
@@ -180,7 +180,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
180
180
|
return json(res, 503, { status: 'error', cdpConnected: false });
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
|
-
json(res, 404, { error: 'Not found. Endpoints: POST /recon,
|
|
183
|
+
json(res, 404, { error: 'Not found. Endpoints: POST /recon, /read, /fill, /click, /type, /scroll, /navigate, /eval, /dismiss, /captcha, /focus | GET /tabs, /health' });
|
|
184
184
|
}
|
|
185
185
|
catch (error) {
|
|
186
186
|
const message = error instanceof Error ? error.message : String(error);
|
package/dist/browser.js
CHANGED
|
@@ -14,13 +14,13 @@ import { descCommand } from './commands/desc.js';
|
|
|
14
14
|
import { closeCommand } from './commands/close.js';
|
|
15
15
|
const program = new Command();
|
|
16
16
|
program
|
|
17
|
-
.name('
|
|
18
|
-
.description('CLI
|
|
19
|
-
.version('1.
|
|
17
|
+
.name('surfagent')
|
|
18
|
+
.description('Browser automation CLI for AI agents — interact with Chrome tabs via CDP')
|
|
19
|
+
.version('1.1.1');
|
|
20
20
|
// Global options
|
|
21
21
|
program
|
|
22
22
|
.option('-p, --port <number>', 'Chrome debugging port', '9222')
|
|
23
|
-
.option('-
|
|
23
|
+
.option('-H, --host <string>', 'Chrome debugging host', 'localhost');
|
|
24
24
|
// List command
|
|
25
25
|
program
|
|
26
26
|
.command('list')
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync, spawn } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
3
4
|
import http from 'node:http';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
@@ -49,8 +50,8 @@ function getChromePath() {
|
|
|
49
50
|
};
|
|
50
51
|
for (const p of paths[os] || []) {
|
|
51
52
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
if (fs.existsSync(p))
|
|
54
|
+
return p;
|
|
54
55
|
}
|
|
55
56
|
catch {
|
|
56
57
|
continue;
|
|
@@ -120,7 +121,7 @@ Environment variables:
|
|
|
120
121
|
CHROME_USER_DATA_DIR Chrome profile directory (default: /tmp/surfagent-chrome)
|
|
121
122
|
|
|
122
123
|
After starting, your AI agent can call http://localhost:3456
|
|
123
|
-
Full API docs: https://github.com/AllAboutAI-YT/
|
|
124
|
+
Full API docs: https://github.com/AllAboutAI-YT/surfagent#readme
|
|
124
125
|
`);
|
|
125
126
|
return;
|
|
126
127
|
}
|
package/package.json
CHANGED
package/src/api/act.ts
CHANGED
|
@@ -22,7 +22,7 @@ async function resolveTab(tabPattern: string, port: number, host: string): Promi
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
if (!tab) throw new Error(`Tab not found: ${tabPattern}`);
|
|
25
|
+
if (!tab) throw new Error(`Tab not found: ${tabPattern.length > 100 ? tabPattern.substring(0, 100) + '...' : tabPattern}`);
|
|
26
26
|
return tab;
|
|
27
27
|
}
|
|
28
28
|
|
package/src/api/recon.ts
CHANGED
|
@@ -343,7 +343,15 @@ export async function reconUrl(
|
|
|
343
343
|
expression: 'document.title',
|
|
344
344
|
returnByValue: true
|
|
345
345
|
});
|
|
346
|
-
|
|
346
|
+
let title = titleResult.result.value as string || '';
|
|
347
|
+
// Fall back to CDP target title when document.title is empty (e.g. pages with no <title> tag)
|
|
348
|
+
if (!title) {
|
|
349
|
+
try {
|
|
350
|
+
const targets = await CDP.List({ port, host });
|
|
351
|
+
const t = targets.find((t: any) => t.id === target.id);
|
|
352
|
+
if (t?.title) title = t.title;
|
|
353
|
+
} catch {}
|
|
354
|
+
}
|
|
347
355
|
|
|
348
356
|
// Get current URL (may have redirected)
|
|
349
357
|
const urlResult = await client.Runtime.evaluate({
|
|
@@ -370,7 +378,7 @@ export async function reconUrl(
|
|
|
370
378
|
|
|
371
379
|
return {
|
|
372
380
|
url: finalUrl,
|
|
373
|
-
title,
|
|
381
|
+
title: title || target.title || '',
|
|
374
382
|
tabId: target.id,
|
|
375
383
|
timestamp: new Date().toISOString(),
|
|
376
384
|
meta: data.meta,
|
package/src/api/server.ts
CHANGED
|
@@ -208,7 +208,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
json(res, 404, { error: 'Not found. Endpoints: POST /recon,
|
|
211
|
+
json(res, 404, { error: 'Not found. Endpoints: POST /recon, /read, /fill, /click, /type, /scroll, /navigate, /eval, /dismiss, /captcha, /focus | GET /tabs, /health' });
|
|
212
212
|
} catch (error) {
|
|
213
213
|
const message = error instanceof Error ? error.message : String(error);
|
|
214
214
|
console.error(`[${new Date().toISOString()}] Error:`, message);
|
package/src/browser.ts
CHANGED
|
@@ -17,14 +17,14 @@ import { closeCommand } from './commands/close.js';
|
|
|
17
17
|
const program = new Command();
|
|
18
18
|
|
|
19
19
|
program
|
|
20
|
-
.name('
|
|
21
|
-
.description('CLI
|
|
22
|
-
.version('1.
|
|
20
|
+
.name('surfagent')
|
|
21
|
+
.description('Browser automation CLI for AI agents — interact with Chrome tabs via CDP')
|
|
22
|
+
.version('1.1.1');
|
|
23
23
|
|
|
24
24
|
// Global options
|
|
25
25
|
program
|
|
26
26
|
.option('-p, --port <number>', 'Chrome debugging port', '9222')
|
|
27
|
-
.option('-
|
|
27
|
+
.option('-H, --host <string>', 'Chrome debugging host', 'localhost');
|
|
28
28
|
|
|
29
29
|
// List command
|
|
30
30
|
program
|
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { execSync, spawn } from 'node:child_process';
|
|
4
|
+
import fs from 'node:fs';
|
|
4
5
|
import http from 'node:http';
|
|
5
6
|
import path from 'node:path';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
@@ -54,8 +55,7 @@ function getChromePath(): string | null {
|
|
|
54
55
|
|
|
55
56
|
for (const p of paths[os] || []) {
|
|
56
57
|
try {
|
|
57
|
-
|
|
58
|
-
return p;
|
|
58
|
+
if (fs.existsSync(p)) return p;
|
|
59
59
|
} catch {
|
|
60
60
|
continue;
|
|
61
61
|
}
|
|
@@ -130,7 +130,7 @@ Environment variables:
|
|
|
130
130
|
CHROME_USER_DATA_DIR Chrome profile directory (default: /tmp/surfagent-chrome)
|
|
131
131
|
|
|
132
132
|
After starting, your AI agent can call http://localhost:3456
|
|
133
|
-
Full API docs: https://github.com/AllAboutAI-YT/
|
|
133
|
+
Full API docs: https://github.com/AllAboutAI-YT/surfagent#readme
|
|
134
134
|
`);
|
|
135
135
|
return;
|
|
136
136
|
}
|