surf-cli 2.2.0 → 2.3.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.
@@ -150,22 +150,23 @@ async function selectModel(cdp, desiredModel, timeoutMs = 8000) {
150
150
  })()`
151
151
  );
152
152
  await delay(300);
153
+ // Select from menu - loop in Node.js to avoid CDP timeout issues
153
154
  const normalizedModel = desiredModel.toLowerCase().replace(/[^a-z0-9]/g, "");
154
- const result = await evaluate(
155
- cdp,
156
- `(async () => {
157
- ${buildClickDispatcher()}
158
- const TIMEOUT_MS = ${timeoutMs};
159
- const targetModel = ${JSON.stringify(normalizedModel)};
160
- const menuSelector = '${SELECTORS.menuContainer}';
161
- const itemSelector = '${SELECTORS.menuItem}';
162
- const normalize = (text) => (text || '').toLowerCase().replace(/[^a-z0-9]/g, '');
163
- const deadline = Date.now() + TIMEOUT_MS;
164
- while (Date.now() < deadline) {
155
+ const deadline = Date.now() + timeoutMs;
156
+
157
+ while (Date.now() < deadline) {
158
+ const result = await evaluate(
159
+ cdp,
160
+ `(() => {
161
+ ${buildClickDispatcher()}
162
+ const targetModel = ${JSON.stringify(normalizedModel)};
163
+ const menuSelector = '${SELECTORS.menuContainer}';
164
+ const itemSelector = '${SELECTORS.menuItem}';
165
+ const normalize = (text) => (text || '').toLowerCase().replace(/[^a-z0-9]/g, '');
166
+
165
167
  const menu = document.querySelector(menuSelector);
166
168
  if (!menu) {
167
- await new Promise(r => setTimeout(r, 100));
168
- continue;
169
+ return { found: false, waiting: true };
169
170
  }
170
171
  const items = Array.from(menu.querySelectorAll(itemSelector));
171
172
  let bestMatch = null;
@@ -183,18 +184,24 @@ async function selectModel(cdp, desiredModel, timeoutMs = 8000) {
183
184
  }
184
185
  if (bestMatch) {
185
186
  dispatchClickSequence(bestMatch);
186
- await new Promise(r => setTimeout(r, 200));
187
- return { success: true, label: bestMatch.textContent?.trim() };
187
+ return { found: true, success: true, label: bestMatch.textContent?.trim() };
188
188
  }
189
- await new Promise(r => setTimeout(r, 100));
189
+ return { found: true, success: false, error: 'No matching model in menu' };
190
+ })()`
191
+ );
192
+
193
+ if (result && result.found) {
194
+ if (result.success) {
195
+ await delay(200);
196
+ return result.label;
190
197
  }
191
- return { success: false, error: 'Model option not found' };
192
- })()`
193
- );
194
- if (!result || !result.success) {
195
- throw new Error(`Model not found: ${desiredModel}`);
198
+ throw new Error(`Model not found: ${desiredModel}`);
199
+ }
200
+
201
+ await delay(100);
196
202
  }
197
- return result.label;
203
+
204
+ throw new Error(`Model not found: ${desiredModel} (timeout)`);
198
205
  }
199
206
 
200
207
  async function typePrompt(cdp, inputCdp, prompt) {
package/native/cli.cjs CHANGED
@@ -146,6 +146,27 @@ const TOOLS = {
146
146
  { cmd: 'perplexity "latest AI news" --model sonar', desc: "Specify model (Pro)" },
147
147
  ]
148
148
  },
149
+ "grok": {
150
+ desc: "Query Grok AI with real-time X/Twitter data access (uses browser session)",
151
+ args: ["query"],
152
+ opts: {
153
+ "with-page": "Include current page context",
154
+ model: "Model: auto, fast, expert, thinking (default)",
155
+ "deep-search": "Enable DeepSearch for X post searching",
156
+ timeout: "Timeout in seconds (default: 300 for thinking models)",
157
+ validate: "Check Grok UI and scrape available models (no query sent)",
158
+ "save-models": "Save discovered models to surf.json config"
159
+ },
160
+ examples: [
161
+ { cmd: 'grok "what are the latest AI agent trends on X"', desc: "Search X posts" },
162
+ { cmd: 'grok "analyze @username recent activity"', desc: "Profile analysis" },
163
+ { cmd: 'grok "summarize this page" --with-page', desc: "With page context" },
164
+ { cmd: 'grok "find viral AI posts" --deep-search', desc: "DeepSearch mode" },
165
+ { cmd: 'grok "quick question" --model fast', desc: "Faster model" },
166
+ { cmd: 'grok --validate', desc: "Check UI and list available models" },
167
+ { cmd: 'grok --validate --save-models', desc: "Save discovered models to settings" },
168
+ ]
169
+ },
149
170
  "ai": {
150
171
  desc: "Analyze page with AI (requires GOOGLE_API_KEY)",
151
172
  args: ["query"],
@@ -1802,6 +1823,7 @@ const PRIMARY_ARG_MAP = {
1802
1823
  gemini: "query",
1803
1824
  chatgpt: "query",
1804
1825
  perplexity: "query",
1826
+ grok: "query",
1805
1827
  navigate: "url",
1806
1828
  go: "url",
1807
1829
  js: "code",
@@ -2168,7 +2190,7 @@ const socket = net.createConnection(SOCKET_PATH, () => {
2168
2190
  socket.write(JSON.stringify(request) + "\n");
2169
2191
  });
2170
2192
 
2171
- const AI_TOOLS = ["smoke", "chatgpt", "gemini", "perplexity", "ai"];
2193
+ const AI_TOOLS = ["smoke", "chatgpt", "gemini", "perplexity", "grok", "ai"];
2172
2194
  const requestTimeout = AI_TOOLS.includes(tool) ? 300000 : 30000;
2173
2195
  const timeout = setTimeout(() => {
2174
2196
  console.error(`Error: Request timed out (${requestTimeout / 1000}s)`);
package/native/config.cjs CHANGED
@@ -21,6 +21,18 @@ const STARTER_CONFIG = {
21
21
  }
22
22
  };
23
23
 
24
+ // Grok models can be customized in surf.json if X.com UI changes:
25
+ // {
26
+ // "grok": {
27
+ // "models": {
28
+ // "thinking": { "id": "thinking", "name": "Grok 4.1 Thinking" },
29
+ // "auto": { "id": "auto", "name": "Auto" },
30
+ // "fast": { "id": "fast", "name": "Fast" },
31
+ // "expert": { "id": "expert", "name": "Expert" }
32
+ // }
33
+ // }
34
+ // }
35
+
24
36
  function findConfigPath() {
25
37
  const cwdPath = path.join(process.cwd(), CONFIG_NAME);
26
38
  if (fs.existsSync(cwdPath)) {