surf-cli 2.2.0 → 2.4.0

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.
@@ -51,12 +51,51 @@ function formatToolContent(result, log = () => {}) {
51
51
  return text(output);
52
52
  }
53
53
 
54
- // Handle ChatGPT/Gemini responses
54
+ // Handle Grok validation results
55
+ if (result.authenticated !== undefined && result.models !== undefined && result.expectedModels !== undefined) {
56
+ let output = "## Grok Validation Results\n\n";
57
+ output += `**Authenticated:** ${result.authenticated ? 'Yes' : 'No'}\n`;
58
+ output += `**Premium:** ${result.premium ? 'Yes' : 'No'}\n`;
59
+ output += `**Input Field:** ${result.inputFound ? 'Found' : 'Not Found'}\n`;
60
+ output += `**Send Button:** ${result.sendButtonFound ? 'Found' : 'Not Found'}\n\n`;
61
+
62
+ output += `**Available Models:** ${result.models.length > 0 ? result.models.join(', ') : 'None found'}\n`;
63
+ output += `**Expected Models:** ${result.expectedModels.join(', ')}\n`;
64
+ output += `**Model Mismatch:** ${result.modelMismatch ? 'Yes' : 'No'}\n\n`;
65
+
66
+ if (result.errors && result.errors.length > 0) {
67
+ output += `**Errors:**\n${result.errors.map(e => `- ${e}`).join('\n')}\n\n`;
68
+ }
69
+
70
+ if (result.savedModels) {
71
+ if (result.savedModels.success) {
72
+ output += `**Models saved to:** ${result.savedModels.path}\n`;
73
+ } else {
74
+ output += `**Failed to save models:** ${result.savedModels.error}\n`;
75
+ }
76
+ }
77
+
78
+ output += `\n*Config: ${result.configPath}*\n`;
79
+ output += `*Completed in ${result.tookMs}ms*`;
80
+
81
+ return text(output);
82
+ }
83
+
84
+ // Handle ChatGPT/Gemini/Grok responses
55
85
  if (result.response !== undefined && result.model !== undefined && result.tookMs !== undefined) {
56
86
  let output = result.response;
57
87
  if (result.imagePath) {
58
88
  output += `\n\n*Image saved to: ${result.imagePath}*`;
59
89
  }
90
+ if (result.thinkingTime) {
91
+ output += `\n\n*Grok thought for ${result.thinkingTime}s*`;
92
+ }
93
+ if (result.partial) {
94
+ output += `\n\n*Warning: Response was truncated due to timeout*`;
95
+ }
96
+ if (result.warnings && result.warnings.length > 0) {
97
+ output += `\n\n**Warnings:**\n${result.warnings.map(w => `- ${w}`).join('\n')}`;
98
+ }
60
99
  return text(output);
61
100
  }
62
101
 
@@ -1013,6 +1052,24 @@ function mapToolToMessage(tool, args, tabId) {
1013
1052
  timeout: a.timeout ? parseInt(a.timeout, 10) * 1000 : 120000,
1014
1053
  ...baseMsg
1015
1054
  };
1055
+ case "grok":
1056
+ if (a.validate) {
1057
+ return {
1058
+ type: "GROK_VALIDATE",
1059
+ saveModels: a["save-models"] || a.saveModels || false,
1060
+ ...baseMsg
1061
+ };
1062
+ }
1063
+ if (!a.query) throw new Error("query required");
1064
+ return {
1065
+ type: "GROK_QUERY",
1066
+ query: a.query,
1067
+ model: a.model,
1068
+ deepSearch: a["deep-search"] || a.deepSearch || false,
1069
+ withPage: a["with-page"],
1070
+ timeout: a.timeout ? parseInt(a.timeout, 10) * 1000 : 300000,
1071
+ ...baseMsg
1072
+ };
1016
1073
  case "window.new":
1017
1074
  return {
1018
1075
  type: "WINDOW_NEW",
package/native/host.cjs CHANGED
@@ -9,6 +9,7 @@ const { GoogleGenerativeAI } = require("@google/generative-ai");
9
9
  const chatgptClient = require("./chatgpt-client.cjs");
10
10
  const geminiClient = require("./gemini-client.cjs");
11
11
  const perplexityClient = require("./perplexity-client.cjs");
12
+ const grokClient = require("./grok-client.cjs");
12
13
  const { mapToolToMessage, mapComputerAction, formatToolContent } = require("./host-helpers.cjs");
13
14
 
14
15
  const SOCKET_PATH = "/tmp/surf.sock";
@@ -713,6 +714,203 @@ function handleToolRequest(msg, socket) {
713
714
  return;
714
715
  }
715
716
 
717
+ if (extensionMsg.type === "GROK_QUERY") {
718
+ const { query, model, deepSearch, withPage, timeout } = extensionMsg;
719
+
720
+ queueAiRequest(async () => {
721
+ // 1. Get page context if requested
722
+ let pageContext = null;
723
+ if (withPage) {
724
+ const pageResult = await new Promise((resolve) => {
725
+ const pageId = ++requestCounter;
726
+ pendingToolRequests.set(pageId, {
727
+ socket: null,
728
+ originalId: null,
729
+ tool: "get_page_text",
730
+ onComplete: resolve
731
+ });
732
+ writeMessage({ type: "GET_PAGE_TEXT", tabId: extensionMsg.tabId, id: pageId });
733
+ });
734
+ if (pageResult && !pageResult.error) {
735
+ pageContext = {
736
+ url: pageResult.url,
737
+ text: pageResult.text || pageResult.pageContent || ""
738
+ };
739
+ }
740
+ }
741
+
742
+ // 2. Build full prompt
743
+ let fullPrompt = query || "";
744
+ if (pageContext) {
745
+ fullPrompt = `Page: ${pageContext.url}\n\n${pageContext.text}\n\n---\n\n${fullPrompt}`;
746
+ }
747
+
748
+ // 3. Call Grok client
749
+ const result = await grokClient.query({
750
+ prompt: fullPrompt,
751
+ model: model,
752
+ deepSearch: deepSearch || false,
753
+ timeout: timeout || 300000,
754
+ getCookies: () => new Promise((resolve) => {
755
+ const cookieId = ++requestCounter;
756
+ pendingToolRequests.set(cookieId, {
757
+ socket: null,
758
+ originalId: null,
759
+ tool: "get_cookies",
760
+ onComplete: (r) => resolve(r)
761
+ });
762
+ writeMessage({ type: "GET_TWITTER_COOKIES", id: cookieId });
763
+ }),
764
+ createTab: () => new Promise((resolve) => {
765
+ const tabCreateId = ++requestCounter;
766
+ pendingToolRequests.set(tabCreateId, {
767
+ socket: null,
768
+ originalId: null,
769
+ tool: "create_tab",
770
+ onComplete: (r) => resolve(r)
771
+ });
772
+ writeMessage({ type: "GROK_NEW_TAB", id: tabCreateId });
773
+ }),
774
+ closeTab: (tabIdToClose) => new Promise((resolve) => {
775
+ const tabCloseId = ++requestCounter;
776
+ pendingToolRequests.set(tabCloseId, {
777
+ socket: null,
778
+ originalId: null,
779
+ tool: "close_tab",
780
+ onComplete: (r) => resolve(r)
781
+ });
782
+ writeMessage({ type: "GROK_CLOSE_TAB", tabId: tabIdToClose, id: tabCloseId });
783
+ }),
784
+ cdpEvaluate: (tabId, expression) => new Promise((resolve) => {
785
+ const evalId = ++requestCounter;
786
+ pendingToolRequests.set(evalId, {
787
+ socket: null,
788
+ originalId: null,
789
+ tool: "cdp_evaluate",
790
+ onComplete: (r) => resolve(r)
791
+ });
792
+ writeMessage({ type: "GROK_EVALUATE", tabId, expression, id: evalId });
793
+ }),
794
+ cdpCommand: (tabId, method, params) => new Promise((resolve) => {
795
+ const cmdId = ++requestCounter;
796
+ pendingToolRequests.set(cmdId, {
797
+ socket: null,
798
+ originalId: null,
799
+ tool: "cdp_command",
800
+ onComplete: (r) => resolve(r)
801
+ });
802
+ writeMessage({ type: "GROK_CDP_COMMAND", tabId, method, params, id: cmdId });
803
+ }),
804
+ log: (msg) => log(`[grok] ${msg}`)
805
+ });
806
+
807
+ return result;
808
+ }).then((result) => {
809
+ const response = {
810
+ response: result.response,
811
+ model: result.model,
812
+ tookMs: result.tookMs
813
+ };
814
+ if (result.thinkingTime) {
815
+ response.thinkingTime = result.thinkingTime;
816
+ }
817
+ if (result.deepSearch) {
818
+ response.deepSearch = result.deepSearch;
819
+ }
820
+ if (result.partial) {
821
+ response.partial = true;
822
+ }
823
+ if (result.warnings && result.warnings.length > 0) {
824
+ response.warnings = result.warnings;
825
+ }
826
+ if (result.modelSelectionFailed) {
827
+ response.modelSelectionFailed = true;
828
+ }
829
+ sendToolResponse(socket, originalId, response, null);
830
+ }).catch((err) => {
831
+ sendToolResponse(socket, originalId, null, err.message);
832
+ });
833
+
834
+ return;
835
+ }
836
+
837
+ if (extensionMsg.type === "GROK_VALIDATE") {
838
+ const { saveModels } = extensionMsg;
839
+
840
+ queueAiRequest(async () => {
841
+ const result = await grokClient.validate({
842
+ getCookies: () => new Promise((resolve) => {
843
+ const cookieId = ++requestCounter;
844
+ pendingToolRequests.set(cookieId, {
845
+ socket: null,
846
+ originalId: null,
847
+ tool: "get_cookies",
848
+ onComplete: (r) => resolve(r)
849
+ });
850
+ writeMessage({ type: "GET_TWITTER_COOKIES", id: cookieId });
851
+ }),
852
+ createTab: () => new Promise((resolve) => {
853
+ const tabCreateId = ++requestCounter;
854
+ pendingToolRequests.set(tabCreateId, {
855
+ socket: null,
856
+ originalId: null,
857
+ tool: "create_tab",
858
+ onComplete: (r) => resolve(r)
859
+ });
860
+ writeMessage({ type: "GROK_NEW_TAB", id: tabCreateId });
861
+ }),
862
+ closeTab: (tabIdToClose) => new Promise((resolve) => {
863
+ const tabCloseId = ++requestCounter;
864
+ pendingToolRequests.set(tabCloseId, {
865
+ socket: null,
866
+ originalId: null,
867
+ tool: "close_tab",
868
+ onComplete: (r) => resolve(r)
869
+ });
870
+ writeMessage({ type: "GROK_CLOSE_TAB", tabId: tabIdToClose, id: tabCloseId });
871
+ }),
872
+ cdpEvaluate: (tabId, expression) => new Promise((resolve) => {
873
+ const evalId = ++requestCounter;
874
+ pendingToolRequests.set(evalId, {
875
+ socket: null,
876
+ originalId: null,
877
+ tool: "cdp_evaluate",
878
+ onComplete: (r) => resolve(r)
879
+ });
880
+ writeMessage({ type: "GROK_EVALUATE", tabId, expression, id: evalId });
881
+ }),
882
+ log: (msg) => log(`[grok:validate] ${msg}`)
883
+ });
884
+
885
+ return result;
886
+ }).then((result) => {
887
+ // If --save-models flag was passed and we found models, save them
888
+ if (saveModels && result.models && result.models.length > 0) {
889
+ // Convert scraped model names to our format
890
+ const modelMap = {};
891
+ result.models.forEach(name => {
892
+ const nameLower = name.toLowerCase();
893
+ // Match known model keywords to generate consistent short IDs
894
+ let shortId;
895
+ if (nameLower.includes('thinking')) shortId = 'thinking';
896
+ else if (nameLower.includes('expert')) shortId = 'expert';
897
+ else if (nameLower.includes('fast')) shortId = 'fast';
898
+ else if (nameLower.includes('auto')) shortId = 'auto';
899
+ else shortId = nameLower.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
900
+
901
+ modelMap[shortId] = { id: shortId, name: name, desc: "" };
902
+ });
903
+ const saveResult = grokClient.saveModels(modelMap);
904
+ result.savedModels = saveResult;
905
+ }
906
+ sendToolResponse(socket, originalId, result, null);
907
+ }).catch((err) => {
908
+ sendToolResponse(socket, originalId, null, err.message);
909
+ });
910
+
911
+ return;
912
+ }
913
+
716
914
  if (extensionMsg.type === "EXECUTE_KEY_REPEAT") {
717
915
  const { key, repeat, tabId: tid } = extensionMsg;
718
916
  let completed = 0;
@@ -193,22 +193,21 @@ async function selectModel(cdp, model, timeoutMs = 8000) {
193
193
 
194
194
  await delay(500);
195
195
 
196
- // Select from menu
196
+ // Select from menu - loop in Node.js to avoid CDP timeout issues
197
197
  const normalizedModel = model.toLowerCase().replace(/[^a-z0-9]/g, '');
198
+ const deadline = Date.now() + timeoutMs;
198
199
 
199
- const result = await evaluate(cdp, `(async () => {
200
- ${buildClickDispatcher()}
201
-
202
- const targetModel = ${JSON.stringify(normalizedModel)};
203
- const normalize = (text) => (text || '').toLowerCase().replace(/[^a-z0-9]/g, '');
204
- const deadline = Date.now() + ${timeoutMs};
205
-
206
- while (Date.now() < deadline) {
200
+ while (Date.now() < deadline) {
201
+ const result = await evaluate(cdp, `(() => {
202
+ ${buildClickDispatcher()}
203
+
204
+ const targetModel = ${JSON.stringify(normalizedModel)};
205
+ const normalize = (text) => (text || '').toLowerCase().replace(/[^a-z0-9]/g, '');
206
+
207
207
  const menuItems = document.querySelectorAll('[role=menuitem], [role=menuitemradio], [role=option]');
208
208
 
209
209
  if (menuItems.length === 0) {
210
- await new Promise(r => setTimeout(r, 100));
211
- continue;
210
+ return { found: false, waiting: true };
212
211
  }
213
212
 
214
213
  let bestMatch = null;
@@ -229,23 +228,28 @@ async function selectModel(cdp, model, timeoutMs = 8000) {
229
228
 
230
229
  if (bestMatch) {
231
230
  dispatchClickSequence(bestMatch);
232
- await new Promise(r => setTimeout(r, 200));
233
- return { success: true, model: bestMatch.textContent?.trim() };
231
+ return { found: true, success: true, model: bestMatch.textContent?.trim() };
234
232
  }
235
233
 
236
- await new Promise(r => setTimeout(r, 100));
234
+ return { found: true, success: false, error: 'No matching model in menu' };
235
+ })()`);
236
+
237
+ if (result && result.found) {
238
+ if (result.success) {
239
+ await delay(200);
240
+ return result.model;
241
+ }
242
+ // Items found but no match - close menu and throw
243
+ await evaluate(cdp, `document.body.click()`);
244
+ throw new Error(`Failed to select model: ${result?.error}`);
237
245
  }
238
246
 
239
- // Close menu by clicking elsewhere
240
- document.body.click();
241
- return { success: false, error: 'Model not found in menu' };
242
- })()`);
243
-
244
- if (!result || !result.success) {
245
- throw new Error(`Failed to select model: ${result?.error}`);
247
+ await delay(100);
246
248
  }
247
249
 
248
- return result.model;
250
+ // Timeout - close menu
251
+ await evaluate(cdp, `document.body.click()`);
252
+ throw new Error(`Failed to select model: timeout waiting for menu`);
249
253
  }
250
254
 
251
255
  // ============================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "surf-cli",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "CLI for AI agents to control Chrome. Zero config, agent-agnostic, battle-tested.",
5
5
  "keywords": [
6
6
  "chrome",
@@ -1,136 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to surf CLI will be documented in this file.
4
-
5
- ## [2.2.0] - 2026-01-07
6
-
7
- ### Added
8
- - Network capture: `network`, `network.stats`, `network.origins`, `network.get`, `network.clear`, `network.export`
9
- - Filtering by method, status, URL, content-type
10
- - Export formats: curl, raw JSON, URL list
11
- - Persistence to `/tmp/surf/` (configurable via `SURF_NETWORK_PATH`)
12
-
13
- ## [2.1.0] - 2025-12-30
14
-
15
- ### Added
16
-
17
- **ChatGPT Integration**
18
- - `chatgpt <query>` - Send prompt to ChatGPT using browser cookies (no API key)
19
- - `--with-page` - Include current page context
20
- - `--model` - Specify model (gpt-4o, o1, etc.)
21
- - `--timeout` - Custom timeout (default: 45 minutes)
22
- - File attachments coming soon
23
-
24
- **Gemini Integration (Coming Soon)**
25
- - `gemini <query>` - Command structure ready, implementation pending
26
-
27
- **Request Queue**
28
- - AI requests are queued sequentially with 2s minimum delay between requests
29
- - Prevents rate limiting when making multiple AI queries
30
-
31
- ### Technical Changes
32
- - New `chatgpt-client.cjs` module for ChatGPT browser automation
33
- - Extension: `GET_CHATGPT_COOKIES`, `GET_GOOGLE_COOKIES` handlers
34
- - Extension: `CHATGPT_NEW_TAB`, `CHATGPT_CLOSE_TAB`, `CHATGPT_CDP_COMMAND`, `CHATGPT_EVALUATE` handlers
35
- - CDP controller: Added public `sendCommand()` method
36
-
37
- ## [2.0.0] - 2025-12-27
38
-
39
- ### Breaking Changes
40
- - Removed snake_case command aliases (use dot-notation instead)
41
- - `read_page` -> `page.read`
42
- - `list_tabs` -> `tab.list`
43
- - `wait_for_element` -> `wait.element`
44
- - `javascript_tool` -> `js`
45
- - And others (see REMOVED_COMMANDS in cli.cjs for full list)
46
- - Removed all single-letter short flags for consistency
47
- - Use `--output` instead of `-o`
48
- - Use `--ref` instead of `-r`
49
- - Use `--annotate` instead of `-a`
50
- - Use `--fullpage` instead of `-f`
51
- - Migration hints shown when using old command names
52
-
53
- ### Added
54
-
55
- **Navigation**
56
- - `back` - Go back in browser history
57
- - `forward` - Go forward in browser history
58
- - `tab.reload` - Reload tab (with `--hard` for cache bypass)
59
-
60
- **Tab Groups**
61
- - `tab.group` - Create or add to tab group
62
- - `tab.ungroup` - Remove tabs from group
63
- - `tab.groups` - List all tab groups
64
-
65
- **Zoom Control**
66
- - `zoom` - Get current zoom level
67
- - `zoom <level>` - Set zoom (e.g., `zoom 1.5` for 150%)
68
- - `zoom --reset` - Reset to default zoom
69
-
70
- **Cookies**
71
- - `cookie.list` - List cookies for current domain
72
- - `cookie.get` - Get specific cookie by name
73
- - `cookie.set` - Set a cookie
74
- - `cookie.clear` - Clear specific cookie or all (`--all`)
75
-
76
- **Search**
77
- - `search <term>` - Search for text in page (alias: `find`)
78
- - Returns match refs, context, and element associations
79
-
80
- **Batch Execution**
81
- - `batch --actions '[...]'` - Execute multiple actions
82
- - `batch --file workflow.json` - Load actions from file
83
-
84
- **Bookmarks**
85
- - `bookmark.add` - Bookmark current page
86
- - `bookmark.remove` - Remove bookmark for current page
87
- - `bookmark.list` - List bookmarks
88
-
89
- **History**
90
- - `history.list` - Recent browser history
91
- - `history.search <query>` - Search history
92
-
93
- **Screenshot Enhancements**
94
- - `--annotate` - Draw element labels on screenshot
95
- - `--fullpage` - Capture entire scrollable page
96
- - `--max-height` - Limit fullpage capture height (default: 4000px)
97
- - Extension UI automatically hidden during capture
98
-
99
- **Aliases**
100
- - `snap` -> `screenshot` (auto-saves to /tmp if no output specified)
101
- - `read` -> `page.read`
102
- - `find` -> `search`
103
- - `go` -> `navigate`
104
-
105
- **Discovery Features**
106
- - `--find <query>` - Fuzzy search for commands
107
- - `--about <topic>` - Learn about a topic
108
-
109
- **Help System**
110
- - `--help` - Basic help with common commands
111
- - `--help-full` - Complete command reference
112
- - `--help-topic <topic>` - Topic-specific guide
113
- - Command-level help with examples
114
-
115
- **Other**
116
- - `--version` - Show version
117
- - `click 100 200` - Positional coordinates for click
118
- - `click --selector ".btn" --index 2` - Click nth element matching selector
119
-
120
- ### Changed
121
- - Primary argument support for commands:
122
- - `wait.element <selector>` (was `--selector`)
123
- - `wait.url <pattern>` (was `--pattern`)
124
- - `click <ref>` with e-prefix detection (e.g., `click e5`)
125
- - Help output includes usage examples for all commands
126
- - `dialog.dismiss --all` for repeatedly dismissing dialogs
127
- - Fullpage screenshot delay increased to 300ms for lazy-loaded content
128
- - Error messages standardized to terse format for AI consumption
129
-
130
- ### Fixed
131
- - `--limit 0` now correctly returns empty results (was defaulting to max)
132
- - Screenshot always hides extension UI (was conditional on `--clean` flag)
133
-
134
- ## [1.x] - Previous Releases
135
-
136
- See git history for changes before v2.0.0.
package/native/README.md DELETED
@@ -1,141 +0,0 @@
1
- # Surf Native Host
2
-
3
- Native messaging host that bridges CLI commands to the Chrome extension via Unix socket.
4
-
5
- ## Architecture
6
-
7
- ```
8
- CLI (surf) → Unix Socket (/tmp/surf.sock) → Native Host → Chrome Extension → CDP
9
- ```
10
-
11
- ## Files
12
-
13
- | File | Purpose |
14
- |------|---------|
15
- | `host.cjs` | Main native host with socket server and tool handling |
16
- | `cli.cjs` | CLI tool for browser automation |
17
- | `chatgpt-client.cjs` | ChatGPT browser automation client |
18
- | `protocol.cjs` | Chrome native messaging protocol helpers |
19
- | `host-wrapper.py` | Python wrapper for native host execution |
20
- | `host.sh` | Shell script to start the host |
21
-
22
- ## Setup
23
-
24
- 1. Install the native host manifest:
25
- ```bash
26
- npm run install:native <extension-id>
27
- ```
28
-
29
- Or manually:
30
- ```bash
31
- mkdir -p ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts
32
- cat > ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic.pi_chrome.json << EOF
33
- {
34
- "name": "com.anthropic.pi_chrome",
35
- "description": "Surf CLI Native Host",
36
- "path": "$PWD/host-wrapper.py",
37
- "type": "stdio",
38
- "allowed_origins": ["chrome-extension://YOUR_EXTENSION_ID/"]
39
- }
40
- EOF
41
- ```
42
-
43
- 2. Start the native host:
44
- ```bash
45
- node host.cjs
46
- ```
47
-
48
- The host creates a Unix socket at `/tmp/surf.sock`.
49
-
50
- ## CLI Reference
51
-
52
- See the main [README](../README.md) for full CLI documentation.
53
-
54
- ### Quick Reference
55
-
56
- ```bash
57
- surf go "https://example.com" # Navigate
58
- surf read # Get accessibility tree
59
- surf click e5 # Click element
60
- surf type "hello" --submit # Type and submit
61
- surf snap # Screenshot to /tmp
62
- surf chatgpt "explain this" # Query ChatGPT
63
- ```
64
-
65
- ### Global Options
66
-
67
- ```bash
68
- --tab-id <id> # Target specific tab
69
- --json # Output raw JSON
70
- --soft-fail # Warn instead of error on restricted pages
71
- --no-screenshot # Skip auto-screenshot after actions
72
- --full # Full resolution screenshots
73
- ```
74
-
75
- ## Protocol
76
-
77
- ### Tool Request
78
-
79
- ```json
80
- {
81
- "type": "tool_request",
82
- "method": "execute_tool",
83
- "params": {
84
- "tool": "TOOL_NAME",
85
- "args": { ... },
86
- "tabId": 123
87
- },
88
- "id": "unique-request-id"
89
- }
90
- ```
91
-
92
- ### Tool Response (Success)
93
-
94
- ```json
95
- {
96
- "type": "tool_response",
97
- "id": "unique-request-id",
98
- "result": {
99
- "content": [
100
- { "type": "text", "text": "Result message" }
101
- ]
102
- }
103
- }
104
- ```
105
-
106
- ### Tool Response (With Image)
107
-
108
- ```json
109
- {
110
- "type": "tool_response",
111
- "id": "unique-request-id",
112
- "result": {
113
- "content": [
114
- { "type": "text", "text": "Screenshot captured" },
115
- { "type": "image", "data": "base64...", "mimeType": "image/png" }
116
- ]
117
- }
118
- }
119
- ```
120
-
121
- ### Tool Response (Error)
122
-
123
- ```json
124
- {
125
- "type": "tool_response",
126
- "id": "unique-request-id",
127
- "error": {
128
- "content": [{ "type": "text", "text": "Error message" }]
129
- }
130
- }
131
- ```
132
-
133
- ## Troubleshooting
134
-
135
- | Issue | Solution |
136
- |-------|----------|
137
- | Socket not found | Ensure `node host.cjs` is running |
138
- | No response | Check extension is loaded in Chrome |
139
- | "Content script not loaded" | Navigate to a page first |
140
- | "Cannot control this page" | Page is restricted (chrome://, extensions) - use `--soft-fail` |
141
- | Slow first operation | Normal - CDP debugger attachment takes ~100-500ms |