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 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 `&amp;` (HTML entity).
30
+
31
+ **Example:** `"BBC Home - Breaking News, World News, US News, Sports, Business, Innovation, Climate, Culture, Travel, Video &amp; 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 `&amp;` -> `&`, `&lt;` -> `<`, 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
- const title = titleResult.result.value || '';
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,
@@ -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, GET /tabs, GET /health' });
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('browser')
18
- .description('CLI tool to interact with Chrome tabs via CDP')
19
- .version('1.0.0');
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('-h, --host <string>', 'Chrome debugging host', 'localhost');
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
- execSync(`test -f "${p}"`, { stdio: 'ignore' });
53
- return p;
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/webpilot#readme
124
+ Full API docs: https://github.com/AllAboutAI-YT/surfagent#readme
124
125
  `);
125
126
  return;
126
127
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "surfagent",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Browser automation API for AI agents — structured page recon, form filling, clicking, and navigation via Chrome CDP",
5
5
  "keywords": [
6
6
  "ai-agent",
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
- const title = titleResult.result.value as string || '';
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, GET /tabs, GET /health' });
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('browser')
21
- .description('CLI tool to interact with Chrome tabs via CDP')
22
- .version('1.0.0');
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('-h, --host <string>', 'Chrome debugging host', 'localhost');
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
- execSync(`test -f "${p}"`, { stdio: 'ignore' });
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/webpilot#readme
133
+ Full API docs: https://github.com/AllAboutAI-YT/surfagent#readme
134
134
  `);
135
135
  return;
136
136
  }