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.
- package/README.md +13 -2
- package/dist/service-worker/index.js +11 -11
- package/dist/service-worker/index.js.map +1 -1
- package/native/chatgpt-client.cjs +29 -22
- package/native/cli.cjs +23 -1
- package/native/config.cjs +12 -0
- package/native/grok-client.cjs +906 -0
- package/native/host-helpers.cjs +58 -1
- package/native/host.cjs +198 -0
- package/native/perplexity-client.cjs +26 -22
- package/package.json +1 -1
package/native/host-helpers.cjs
CHANGED
|
@@ -51,12 +51,51 @@ function formatToolContent(result, log = () => {}) {
|
|
|
51
51
|
return text(output);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
// Handle
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
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
|
-
|
|
233
|
-
return { success: true, model: bestMatch.textContent?.trim() };
|
|
231
|
+
return { found: true, success: true, model: bestMatch.textContent?.trim() };
|
|
234
232
|
}
|
|
235
233
|
|
|
236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
// ============================================================================
|