@pulso/companion 0.4.6 → 0.4.8
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/dist/index.js +756 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9899,6 +9899,428 @@ function runClaudePipeSerial(task) {
|
|
|
9899
9899
|
}
|
|
9900
9900
|
async function handleCommand(command, params, streamCb) {
|
|
9901
9901
|
try {
|
|
9902
|
+
if (command === "llm_list_local") {
|
|
9903
|
+
const endpoints = [];
|
|
9904
|
+
try {
|
|
9905
|
+
const res = await fetch("http://localhost:11434/api/tags", {
|
|
9906
|
+
signal: AbortSignal.timeout(2e3)
|
|
9907
|
+
});
|
|
9908
|
+
if (res.ok) {
|
|
9909
|
+
const data = await res.json();
|
|
9910
|
+
endpoints.push({
|
|
9911
|
+
provider: "ollama",
|
|
9912
|
+
url: "http://localhost:11434",
|
|
9913
|
+
models: (data.models ?? []).map((m) => m.name),
|
|
9914
|
+
available: true
|
|
9915
|
+
});
|
|
9916
|
+
}
|
|
9917
|
+
} catch {
|
|
9918
|
+
}
|
|
9919
|
+
try {
|
|
9920
|
+
const res = await fetch("http://localhost:1234/v1/models", {
|
|
9921
|
+
signal: AbortSignal.timeout(2e3)
|
|
9922
|
+
});
|
|
9923
|
+
if (res.ok) {
|
|
9924
|
+
const data = await res.json();
|
|
9925
|
+
endpoints.push({
|
|
9926
|
+
provider: "lmstudio",
|
|
9927
|
+
url: "http://localhost:1234",
|
|
9928
|
+
models: (data.data ?? []).map((m) => m.id),
|
|
9929
|
+
available: true
|
|
9930
|
+
});
|
|
9931
|
+
}
|
|
9932
|
+
} catch {
|
|
9933
|
+
}
|
|
9934
|
+
try {
|
|
9935
|
+
const res = await fetch("http://localhost:1337/v1/models", {
|
|
9936
|
+
signal: AbortSignal.timeout(2e3)
|
|
9937
|
+
});
|
|
9938
|
+
if (res.ok) {
|
|
9939
|
+
const data = await res.json();
|
|
9940
|
+
endpoints.push({
|
|
9941
|
+
provider: "jan",
|
|
9942
|
+
url: "http://localhost:1337",
|
|
9943
|
+
models: (data.data ?? []).map((m) => m.id),
|
|
9944
|
+
available: true
|
|
9945
|
+
});
|
|
9946
|
+
}
|
|
9947
|
+
} catch {
|
|
9948
|
+
}
|
|
9949
|
+
return {
|
|
9950
|
+
success: true,
|
|
9951
|
+
data: {
|
|
9952
|
+
endpoints,
|
|
9953
|
+
total_models: endpoints.reduce((n, e) => n + e.models.length, 0),
|
|
9954
|
+
has_local_ai: endpoints.length > 0
|
|
9955
|
+
}
|
|
9956
|
+
};
|
|
9957
|
+
}
|
|
9958
|
+
if (command === "llm_detect_sessions") {
|
|
9959
|
+
const sessions = [];
|
|
9960
|
+
const PROVIDERS = [
|
|
9961
|
+
{ provider: "claude", url: "claude.ai", login_indicator: "/login" },
|
|
9962
|
+
{ provider: "chatgpt", url: "chatgpt.com", login_indicator: "/auth/login" },
|
|
9963
|
+
{ provider: "perplexity", url: "perplexity.ai", login_indicator: "/login" },
|
|
9964
|
+
{ provider: "gemini", url: "gemini.google.com", login_indicator: "/signin" }
|
|
9965
|
+
];
|
|
9966
|
+
try {
|
|
9967
|
+
const tabsResult = await adapter.browserListTabs();
|
|
9968
|
+
const tabs = Array.isArray(tabsResult) ? tabsResult : tabsResult?.data ?? [];
|
|
9969
|
+
const tabList = tabs;
|
|
9970
|
+
for (const p of PROVIDERS) {
|
|
9971
|
+
const matchingTabs = tabList.filter((t) => t.url?.includes(p.url));
|
|
9972
|
+
const tabOpen = matchingTabs.length > 0;
|
|
9973
|
+
const loggedIn = tabOpen && !matchingTabs.some((t) => t.url?.includes(p.login_indicator));
|
|
9974
|
+
sessions.push({ provider: p.provider, url: p.url, logged_in: loggedIn, tab_open: tabOpen });
|
|
9975
|
+
}
|
|
9976
|
+
} catch {
|
|
9977
|
+
for (const p of PROVIDERS) {
|
|
9978
|
+
sessions.push({ provider: p.provider, url: p.url, logged_in: false, tab_open: false });
|
|
9979
|
+
}
|
|
9980
|
+
}
|
|
9981
|
+
const available = sessions.filter((s) => s.logged_in).map((s) => s.provider);
|
|
9982
|
+
return {
|
|
9983
|
+
success: true,
|
|
9984
|
+
data: {
|
|
9985
|
+
sessions,
|
|
9986
|
+
available_providers: available,
|
|
9987
|
+
has_browser_llm: available.length > 0
|
|
9988
|
+
}
|
|
9989
|
+
};
|
|
9990
|
+
}
|
|
9991
|
+
if (command === "llm_infer") {
|
|
9992
|
+
const {
|
|
9993
|
+
messages,
|
|
9994
|
+
model,
|
|
9995
|
+
max_tokens = 2e3,
|
|
9996
|
+
temperature = 0.7,
|
|
9997
|
+
stream = false
|
|
9998
|
+
} = params;
|
|
9999
|
+
const localEndpoints = [];
|
|
10000
|
+
for (const [url, provider] of [
|
|
10001
|
+
["http://localhost:11434", "ollama"],
|
|
10002
|
+
["http://localhost:1234", "lmstudio"],
|
|
10003
|
+
["http://localhost:1337", "jan"]
|
|
10004
|
+
]) {
|
|
10005
|
+
try {
|
|
10006
|
+
const tagsUrl = provider === "ollama" ? `${url}/api/tags` : `${url}/v1/models`;
|
|
10007
|
+
const res = await fetch(tagsUrl, { signal: AbortSignal.timeout(1500) });
|
|
10008
|
+
if (res.ok) {
|
|
10009
|
+
const data = await res.json();
|
|
10010
|
+
const models = data.models?.map((m) => m.name) ?? data.data?.map((m) => m.id) ?? [];
|
|
10011
|
+
localEndpoints.push({ url, provider, models });
|
|
10012
|
+
}
|
|
10013
|
+
} catch {
|
|
10014
|
+
}
|
|
10015
|
+
}
|
|
10016
|
+
if (localEndpoints.length === 0) {
|
|
10017
|
+
return {
|
|
10018
|
+
success: false,
|
|
10019
|
+
error: "No local AI running. Please start Ollama, LM Studio, or Jan."
|
|
10020
|
+
};
|
|
10021
|
+
}
|
|
10022
|
+
let chosenEndpoint = localEndpoints[0];
|
|
10023
|
+
let chosenModel = model;
|
|
10024
|
+
if (model) {
|
|
10025
|
+
for (const ep of localEndpoints) {
|
|
10026
|
+
if (ep.models.some((m) => m.includes(model) || model.includes(m))) {
|
|
10027
|
+
chosenEndpoint = ep;
|
|
10028
|
+
chosenModel = ep.models.find((m) => m.includes(model) || model.includes(m));
|
|
10029
|
+
break;
|
|
10030
|
+
}
|
|
10031
|
+
}
|
|
10032
|
+
}
|
|
10033
|
+
if (!chosenModel && chosenEndpoint.models.length > 0) {
|
|
10034
|
+
const paramPriority = ["70b", "32b", "14b", "13b", "8b", "7b", "3b", "1b"];
|
|
10035
|
+
let found = false;
|
|
10036
|
+
for (const size of paramPriority) {
|
|
10037
|
+
const match = chosenEndpoint.models.find(
|
|
10038
|
+
(m) => m.toLowerCase().includes(size)
|
|
10039
|
+
);
|
|
10040
|
+
if (match) {
|
|
10041
|
+
chosenModel = match;
|
|
10042
|
+
found = true;
|
|
10043
|
+
break;
|
|
10044
|
+
}
|
|
10045
|
+
}
|
|
10046
|
+
if (!found) chosenModel = chosenEndpoint.models[0];
|
|
10047
|
+
}
|
|
10048
|
+
const apiUrl = chosenEndpoint.provider === "ollama" ? `${chosenEndpoint.url}/v1/chat/completions` : `${chosenEndpoint.url}/v1/chat/completions`;
|
|
10049
|
+
try {
|
|
10050
|
+
const inferRes = await fetch(apiUrl, {
|
|
10051
|
+
method: "POST",
|
|
10052
|
+
headers: { "Content-Type": "application/json" },
|
|
10053
|
+
body: JSON.stringify({
|
|
10054
|
+
model: chosenModel,
|
|
10055
|
+
messages,
|
|
10056
|
+
max_tokens,
|
|
10057
|
+
temperature,
|
|
10058
|
+
stream: false
|
|
10059
|
+
// Companion returns full response
|
|
10060
|
+
}),
|
|
10061
|
+
signal: AbortSignal.timeout(12e4)
|
|
10062
|
+
// 2 min max
|
|
10063
|
+
});
|
|
10064
|
+
if (!inferRes.ok) {
|
|
10065
|
+
const errText = await inferRes.text().catch(() => `HTTP ${inferRes.status}`);
|
|
10066
|
+
return { success: false, error: `Local AI error: ${errText.slice(0, 200)}` };
|
|
10067
|
+
}
|
|
10068
|
+
const data = await inferRes.json();
|
|
10069
|
+
const content = data.choices?.[0]?.message?.content ?? "";
|
|
10070
|
+
return {
|
|
10071
|
+
success: true,
|
|
10072
|
+
data: {
|
|
10073
|
+
content,
|
|
10074
|
+
model: data.model ?? chosenModel,
|
|
10075
|
+
provider: chosenEndpoint.provider,
|
|
10076
|
+
endpoint: chosenEndpoint.url,
|
|
10077
|
+
cost_usd: 0,
|
|
10078
|
+
// Local = always free
|
|
10079
|
+
usage: data.usage
|
|
10080
|
+
}
|
|
10081
|
+
};
|
|
10082
|
+
} catch (err) {
|
|
10083
|
+
return {
|
|
10084
|
+
success: false,
|
|
10085
|
+
error: `Inference failed: ${err.message}`
|
|
10086
|
+
};
|
|
10087
|
+
}
|
|
10088
|
+
}
|
|
10089
|
+
if (command === "llm_browser_claude") {
|
|
10090
|
+
const { prompt, system } = params;
|
|
10091
|
+
try {
|
|
10092
|
+
const tabsResult = await adapter.browserListTabs();
|
|
10093
|
+
const tabs = Array.isArray(tabsResult) ? tabsResult : tabsResult?.data ?? [];
|
|
10094
|
+
const claudeTab = tabs.find(
|
|
10095
|
+
(t) => t.url?.includes("claude.ai")
|
|
10096
|
+
);
|
|
10097
|
+
if (!claudeTab) {
|
|
10098
|
+
await adapter.browserNavigate("https://claude.ai/new");
|
|
10099
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
10100
|
+
}
|
|
10101
|
+
const fullPrompt = system ? `[Context: ${system}]
|
|
10102
|
+
|
|
10103
|
+
${prompt}` : prompt;
|
|
10104
|
+
const submitScript = `
|
|
10105
|
+
(async () => {
|
|
10106
|
+
// Navigate to new conversation if needed
|
|
10107
|
+
const url = window.location.href;
|
|
10108
|
+
if (!url.includes('/chat/') && !url.includes('/new')) {
|
|
10109
|
+
window.location.href = 'https://claude.ai/new';
|
|
10110
|
+
return { navigating: true };
|
|
10111
|
+
}
|
|
10112
|
+
|
|
10113
|
+
// Find the input field
|
|
10114
|
+
const editor = document.querySelector('[contenteditable="true"]') ||
|
|
10115
|
+
document.querySelector('textarea[placeholder]') ||
|
|
10116
|
+
document.querySelector('.ProseMirror');
|
|
10117
|
+
|
|
10118
|
+
if (!editor) return { error: 'Input field not found' };
|
|
10119
|
+
|
|
10120
|
+
// Clear and type the prompt
|
|
10121
|
+
editor.focus();
|
|
10122
|
+
document.execCommand('selectAll', false, null);
|
|
10123
|
+
document.execCommand('delete', false, null);
|
|
10124
|
+
|
|
10125
|
+
// Type the prompt
|
|
10126
|
+
const text = ${JSON.stringify(fullPrompt)};
|
|
10127
|
+
document.execCommand('insertText', false, text);
|
|
10128
|
+
|
|
10129
|
+
// Trigger React state update
|
|
10130
|
+
editor.dispatchEvent(new InputEvent('input', { bubbles: true }));
|
|
10131
|
+
|
|
10132
|
+
// Small delay for state update
|
|
10133
|
+
await new Promise(r => setTimeout(r, 500));
|
|
10134
|
+
|
|
10135
|
+
// Find and click send button
|
|
10136
|
+
const sendBtn = document.querySelector('button[aria-label*="Send"]') ||
|
|
10137
|
+
document.querySelector('button[data-testid="send-button"]') ||
|
|
10138
|
+
[...document.querySelectorAll('button')].find(
|
|
10139
|
+
b => b.getAttribute('aria-label')?.toLowerCase().includes('send')
|
|
10140
|
+
);
|
|
10141
|
+
|
|
10142
|
+
if (sendBtn && !sendBtn.disabled) {
|
|
10143
|
+
sendBtn.click();
|
|
10144
|
+
return { submitted: true };
|
|
10145
|
+
}
|
|
10146
|
+
|
|
10147
|
+
// Fallback: Enter key
|
|
10148
|
+
editor.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
10149
|
+
return { submitted: true, method: 'enter' };
|
|
10150
|
+
})()
|
|
10151
|
+
`;
|
|
10152
|
+
await adapter.browserExecuteJs(submitScript);
|
|
10153
|
+
const pollScript = `
|
|
10154
|
+
(() => {
|
|
10155
|
+
const stopBtn = document.querySelector('button[aria-label*="Stop"]') ||
|
|
10156
|
+
document.querySelector('[data-testid="stop-button"]');
|
|
10157
|
+
const isStreaming = !!stopBtn && !stopBtn.disabled;
|
|
10158
|
+
|
|
10159
|
+
// Get the last assistant message
|
|
10160
|
+
const msgs = document.querySelectorAll('[data-is-streaming]');
|
|
10161
|
+
const lastMsg = msgs[msgs.length - 1];
|
|
10162
|
+
const streaming = lastMsg?.getAttribute('data-is-streaming') === 'true';
|
|
10163
|
+
|
|
10164
|
+
// Get message text
|
|
10165
|
+
const allMsgs = document.querySelectorAll('.font-claude-message, [data-message-author-role="assistant"]');
|
|
10166
|
+
const lastContent = allMsgs[allMsgs.length - 1]?.innerText ?? '';
|
|
10167
|
+
|
|
10168
|
+
return {
|
|
10169
|
+
isStreaming: isStreaming || streaming,
|
|
10170
|
+
hasContent: lastContent.length > 10,
|
|
10171
|
+
preview: lastContent.slice(0, 100)
|
|
10172
|
+
};
|
|
10173
|
+
})()
|
|
10174
|
+
`;
|
|
10175
|
+
let responseText = "";
|
|
10176
|
+
const maxWait = 6e4;
|
|
10177
|
+
const pollInterval = 1500;
|
|
10178
|
+
const startTime = Date.now();
|
|
10179
|
+
while (Date.now() - startTime < maxWait) {
|
|
10180
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
10181
|
+
const pollResult = await adapter.browserExecuteJs(pollScript);
|
|
10182
|
+
const status = pollResult?.data ?? pollResult;
|
|
10183
|
+
if (!status.isStreaming && status.hasContent) {
|
|
10184
|
+
const extractScript = `
|
|
10185
|
+
(() => {
|
|
10186
|
+
const msgs = document.querySelectorAll(
|
|
10187
|
+
'.font-claude-message, [data-message-author-role="assistant"]'
|
|
10188
|
+
);
|
|
10189
|
+
const last = msgs[msgs.length - 1];
|
|
10190
|
+
return last ? last.innerText : '';
|
|
10191
|
+
})()
|
|
10192
|
+
`;
|
|
10193
|
+
const extracted = await adapter.browserExecuteJs(extractScript);
|
|
10194
|
+
responseText = String(extracted?.data ?? extracted ?? "").trim();
|
|
10195
|
+
if (responseText.length > 20) break;
|
|
10196
|
+
}
|
|
10197
|
+
}
|
|
10198
|
+
if (!responseText) {
|
|
10199
|
+
return {
|
|
10200
|
+
success: false,
|
|
10201
|
+
error: "Timeout waiting for Claude.ai response. Is claude.ai open and logged in?"
|
|
10202
|
+
};
|
|
10203
|
+
}
|
|
10204
|
+
return {
|
|
10205
|
+
success: true,
|
|
10206
|
+
data: {
|
|
10207
|
+
content: responseText,
|
|
10208
|
+
provider: "claude.ai",
|
|
10209
|
+
model: "claude-3-5-sonnet",
|
|
10210
|
+
// Claude.ai uses latest model
|
|
10211
|
+
cost_usd: 0,
|
|
10212
|
+
// Uses subscription — free per-message
|
|
10213
|
+
note: "Response via Claude.ai browser session"
|
|
10214
|
+
}
|
|
10215
|
+
};
|
|
10216
|
+
} catch (err) {
|
|
10217
|
+
return {
|
|
10218
|
+
success: false,
|
|
10219
|
+
error: `Browser Claude bridge failed: ${err.message}`
|
|
10220
|
+
};
|
|
10221
|
+
}
|
|
10222
|
+
}
|
|
10223
|
+
if (command === "llm_browser_chatgpt") {
|
|
10224
|
+
const { prompt, system } = params;
|
|
10225
|
+
try {
|
|
10226
|
+
const tabsResult = await adapter.browserListTabs();
|
|
10227
|
+
const tabs = Array.isArray(tabsResult) ? tabsResult : tabsResult?.data ?? [];
|
|
10228
|
+
const gptTab = tabs.find(
|
|
10229
|
+
(t) => t.url?.includes("chatgpt.com") || t.url?.includes("chat.openai.com")
|
|
10230
|
+
);
|
|
10231
|
+
if (!gptTab) {
|
|
10232
|
+
await adapter.browserNavigate("https://chatgpt.com/");
|
|
10233
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
10234
|
+
}
|
|
10235
|
+
const fullPrompt = system ? `${system}
|
|
10236
|
+
|
|
10237
|
+
${prompt}` : prompt;
|
|
10238
|
+
const submitScript = `
|
|
10239
|
+
(async () => {
|
|
10240
|
+
const editor = document.getElementById('prompt-textarea') ||
|
|
10241
|
+
document.querySelector('textarea[placeholder]') ||
|
|
10242
|
+
document.querySelector('[contenteditable="true"][id*="prompt"]');
|
|
10243
|
+
|
|
10244
|
+
if (!editor) return { error: 'Input not found' };
|
|
10245
|
+
|
|
10246
|
+
editor.focus();
|
|
10247
|
+
const nativeInputSetter = Object.getOwnPropertyDescriptor(
|
|
10248
|
+
editor.tagName === 'TEXTAREA'
|
|
10249
|
+
? window.HTMLTextAreaElement.prototype
|
|
10250
|
+
: window.HTMLDivElement.prototype,
|
|
10251
|
+
'value'
|
|
10252
|
+
);
|
|
10253
|
+
if (nativeInputSetter?.set) {
|
|
10254
|
+
nativeInputSetter.set.call(editor, ${JSON.stringify(fullPrompt)});
|
|
10255
|
+
} else {
|
|
10256
|
+
editor.textContent = ${JSON.stringify(fullPrompt)};
|
|
10257
|
+
}
|
|
10258
|
+
editor.dispatchEvent(new Event('input', { bubbles: true }));
|
|
10259
|
+
|
|
10260
|
+
await new Promise(r => setTimeout(r, 300));
|
|
10261
|
+
|
|
10262
|
+
const sendBtn = document.querySelector('button[data-testid="send-button"]') ||
|
|
10263
|
+
document.querySelector('[aria-label="Send prompt"]');
|
|
10264
|
+
if (sendBtn) { sendBtn.click(); return { submitted: true }; }
|
|
10265
|
+
|
|
10266
|
+
editor.dispatchEvent(new KeyboardEvent('keydown', {
|
|
10267
|
+
key: 'Enter', keyCode: 13, bubbles: true
|
|
10268
|
+
}));
|
|
10269
|
+
return { submitted: true, method: 'enter' };
|
|
10270
|
+
})()
|
|
10271
|
+
`;
|
|
10272
|
+
await adapter.browserExecuteJs(submitScript);
|
|
10273
|
+
const maxWait = 9e4;
|
|
10274
|
+
const pollInterval = 2e3;
|
|
10275
|
+
const startTime = Date.now();
|
|
10276
|
+
let responseText = "";
|
|
10277
|
+
while (Date.now() - startTime < maxWait) {
|
|
10278
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
10279
|
+
const pollResult = await adapter.browserExecuteJs(`
|
|
10280
|
+
(() => {
|
|
10281
|
+
const isGenerating = !!document.querySelector(
|
|
10282
|
+
'button[aria-label="Stop streaming"], [data-testid="stop-button"]'
|
|
10283
|
+
);
|
|
10284
|
+
const msgs = document.querySelectorAll(
|
|
10285
|
+
'[data-message-author-role="assistant"] .markdown'
|
|
10286
|
+
);
|
|
10287
|
+
const last = msgs[msgs.length - 1];
|
|
10288
|
+
return {
|
|
10289
|
+
isGenerating,
|
|
10290
|
+
content: last ? last.innerText : '',
|
|
10291
|
+
count: msgs.length
|
|
10292
|
+
};
|
|
10293
|
+
})()
|
|
10294
|
+
`);
|
|
10295
|
+
const st = pollResult?.data ?? pollResult;
|
|
10296
|
+
if (!st.isGenerating && String(st.content ?? "").length > 20) {
|
|
10297
|
+
responseText = String(st.content).trim();
|
|
10298
|
+
break;
|
|
10299
|
+
}
|
|
10300
|
+
}
|
|
10301
|
+
if (!responseText) {
|
|
10302
|
+
return {
|
|
10303
|
+
success: false,
|
|
10304
|
+
error: "Timeout waiting for ChatGPT response. Is chatgpt.com open and logged in?"
|
|
10305
|
+
};
|
|
10306
|
+
}
|
|
10307
|
+
return {
|
|
10308
|
+
success: true,
|
|
10309
|
+
data: {
|
|
10310
|
+
content: responseText,
|
|
10311
|
+
provider: "chatgpt.com",
|
|
10312
|
+
model: "gpt-4o",
|
|
10313
|
+
cost_usd: 0,
|
|
10314
|
+
note: "Response via ChatGPT browser session"
|
|
10315
|
+
}
|
|
10316
|
+
};
|
|
10317
|
+
} catch (err) {
|
|
10318
|
+
return {
|
|
10319
|
+
success: false,
|
|
10320
|
+
error: `Browser ChatGPT bridge failed: ${err.message}`
|
|
10321
|
+
};
|
|
10322
|
+
}
|
|
10323
|
+
}
|
|
9902
10324
|
if (command === "ollama_detect") {
|
|
9903
10325
|
try {
|
|
9904
10326
|
const controller = new AbortController();
|
|
@@ -10811,12 +11233,28 @@ print(result.stdout[:5000])
|
|
|
10811
11233
|
);
|
|
10812
11234
|
}
|
|
10813
11235
|
case "sys_claude_status": {
|
|
11236
|
+
const _home = process.env.HOME || "";
|
|
11237
|
+
const _claudePaths = [
|
|
11238
|
+
`${_home}/.local/bin/claude`,
|
|
11239
|
+
`${_home}/.local/share/claude/versions/latest/claude`,
|
|
11240
|
+
"/usr/local/bin/claude",
|
|
11241
|
+
"/opt/homebrew/bin/claude"
|
|
11242
|
+
];
|
|
11243
|
+
let _claudeBin = "claude";
|
|
11244
|
+
for (const _p of _claudePaths) {
|
|
11245
|
+
try {
|
|
11246
|
+
execSync3(`test -x "${_p}"`, { stdio: "ignore" });
|
|
11247
|
+
_claudeBin = _p;
|
|
11248
|
+
break;
|
|
11249
|
+
} catch {
|
|
11250
|
+
}
|
|
11251
|
+
}
|
|
10814
11252
|
try {
|
|
10815
|
-
const version = await runShell4("
|
|
11253
|
+
const version = await runShell4(`"${_claudeBin}" --version 2>/dev/null`, 5e3);
|
|
10816
11254
|
let authStatus = "unknown";
|
|
10817
11255
|
let authDetails;
|
|
10818
11256
|
try {
|
|
10819
|
-
const status = await runShell4("
|
|
11257
|
+
const status = await runShell4(`"${_claudeBin}" auth status 2>&1`, 1e4);
|
|
10820
11258
|
const trimmed = status.trim();
|
|
10821
11259
|
let parsed = null;
|
|
10822
11260
|
try {
|
|
@@ -10965,6 +11403,315 @@ print(result.stdout[:5000])
|
|
|
10965
11403
|
return { success: false, error: err.message };
|
|
10966
11404
|
}
|
|
10967
11405
|
}
|
|
11406
|
+
// ── LLM Browser Sessions ───────────────────────────────
|
|
11407
|
+
case "sys_llm_list_sessions": {
|
|
11408
|
+
const LLM_PROVIDERS = [
|
|
11409
|
+
{ domain: "claude.ai", name: "Claude" },
|
|
11410
|
+
{ domain: "chatgpt.com", name: "ChatGPT" },
|
|
11411
|
+
{ domain: "chat.openai.com", name: "ChatGPT" },
|
|
11412
|
+
{ domain: "gemini.google.com", name: "Gemini" },
|
|
11413
|
+
{ domain: "perplexity.ai", name: "Perplexity" },
|
|
11414
|
+
{ domain: "copilot.microsoft.com", name: "Copilot" },
|
|
11415
|
+
{ domain: "grok.com", name: "Grok" },
|
|
11416
|
+
{ domain: "aistudio.google.com", name: "AI Studio" },
|
|
11417
|
+
{ domain: "poe.com", name: "Poe" },
|
|
11418
|
+
{ domain: "mistral.ai/chat", name: "Mistral" },
|
|
11419
|
+
{ domain: "deepseek.com/chat", name: "DeepSeek" }
|
|
11420
|
+
];
|
|
11421
|
+
const allTabs = await adapter.browserListTabs();
|
|
11422
|
+
const sessions = allTabs.filter((tab) => {
|
|
11423
|
+
const url = tab.url.toLowerCase();
|
|
11424
|
+
return LLM_PROVIDERS.some((p) => url.includes(p.domain));
|
|
11425
|
+
}).map((tab) => {
|
|
11426
|
+
const provider = LLM_PROVIDERS.find(
|
|
11427
|
+
(p) => tab.url.toLowerCase().includes(p.domain)
|
|
11428
|
+
);
|
|
11429
|
+
return {
|
|
11430
|
+
provider: provider?.name ?? "Unknown",
|
|
11431
|
+
browser: tab.browser,
|
|
11432
|
+
title: tab.title,
|
|
11433
|
+
url: tab.url,
|
|
11434
|
+
active: tab.active ?? false
|
|
11435
|
+
};
|
|
11436
|
+
});
|
|
11437
|
+
return {
|
|
11438
|
+
success: true,
|
|
11439
|
+
data: {
|
|
11440
|
+
sessions,
|
|
11441
|
+
count: sessions.length,
|
|
11442
|
+
note: sessions.length === 0 ? "No LLM tabs open. Open Claude, ChatGPT, Gemini, or Perplexity in your browser." : void 0
|
|
11443
|
+
}
|
|
11444
|
+
};
|
|
11445
|
+
}
|
|
11446
|
+
case "sys_llm_read_session": {
|
|
11447
|
+
const targetUrl = params.url;
|
|
11448
|
+
const targetIndex = params.index !== void 0 ? Number(params.index) : void 0;
|
|
11449
|
+
const LLM_PROVIDERS = [
|
|
11450
|
+
{ domain: "claude.ai", name: "Claude" },
|
|
11451
|
+
{ domain: "chatgpt.com", name: "ChatGPT" },
|
|
11452
|
+
{ domain: "chat.openai.com", name: "ChatGPT" },
|
|
11453
|
+
{ domain: "gemini.google.com", name: "Gemini" },
|
|
11454
|
+
{ domain: "perplexity.ai", name: "Perplexity" },
|
|
11455
|
+
{ domain: "copilot.microsoft.com", name: "Copilot" },
|
|
11456
|
+
{ domain: "grok.com", name: "Grok" },
|
|
11457
|
+
{ domain: "aistudio.google.com", name: "AI Studio" },
|
|
11458
|
+
{ domain: "poe.com", name: "Poe" },
|
|
11459
|
+
{ domain: "mistral.ai/chat", name: "Mistral" },
|
|
11460
|
+
{ domain: "deepseek.com/chat", name: "DeepSeek" }
|
|
11461
|
+
];
|
|
11462
|
+
const allTabs = await adapter.browserListTabs();
|
|
11463
|
+
const llmTabs = allTabs.filter(
|
|
11464
|
+
(tab) => LLM_PROVIDERS.some((p) => tab.url.toLowerCase().includes(p.domain))
|
|
11465
|
+
);
|
|
11466
|
+
if (llmTabs.length === 0) {
|
|
11467
|
+
return { success: false, error: "No LLM tabs found. Open Claude, ChatGPT, or Gemini in your browser." };
|
|
11468
|
+
}
|
|
11469
|
+
let targetTab = llmTabs[0];
|
|
11470
|
+
if (targetUrl) {
|
|
11471
|
+
const found = llmTabs.find((t) => t.url.toLowerCase().includes(targetUrl.toLowerCase()));
|
|
11472
|
+
if (found) targetTab = found;
|
|
11473
|
+
} else if (targetIndex !== void 0 && llmTabs[targetIndex]) {
|
|
11474
|
+
targetTab = llmTabs[targetIndex];
|
|
11475
|
+
} else {
|
|
11476
|
+
const active = llmTabs.find((t) => t.active);
|
|
11477
|
+
const claude = llmTabs.find((t) => t.url.includes("claude.ai"));
|
|
11478
|
+
targetTab = active ?? claude ?? llmTabs[0];
|
|
11479
|
+
}
|
|
11480
|
+
const provider = LLM_PROVIDERS.find(
|
|
11481
|
+
(p) => targetTab.url.toLowerCase().includes(p.domain)
|
|
11482
|
+
);
|
|
11483
|
+
const JS_EXTRACTORS = {
|
|
11484
|
+
Claude: `(function(){
|
|
11485
|
+
const msgs=[];
|
|
11486
|
+
const turns=document.querySelectorAll('[data-testid="human-turn"],[data-testid="ai-turn"]');
|
|
11487
|
+
turns.forEach(el=>{
|
|
11488
|
+
const role=el.getAttribute('data-testid')==='human-turn'?'user':'assistant';
|
|
11489
|
+
const text=(el.innerText||'').trim().slice(0,600);
|
|
11490
|
+
if(text) msgs.push({role,text});
|
|
11491
|
+
});
|
|
11492
|
+
if(!msgs.length){
|
|
11493
|
+
document.querySelectorAll('.font-claude-message,.prose-claude').forEach(el=>{
|
|
11494
|
+
const text=(el.innerText||'').trim().slice(0,600);
|
|
11495
|
+
if(text) msgs.push({role:'assistant',text});
|
|
11496
|
+
});
|
|
11497
|
+
}
|
|
11498
|
+
return JSON.stringify({provider:'Claude',messages:msgs.slice(-8),url:location.href,title:document.title});
|
|
11499
|
+
})()`,
|
|
11500
|
+
ChatGPT: `(function(){
|
|
11501
|
+
const msgs=[];
|
|
11502
|
+
document.querySelectorAll('[data-message-author-role]').forEach(el=>{
|
|
11503
|
+
const role=el.getAttribute('data-message-author-role')||'unknown';
|
|
11504
|
+
const content=el.querySelector('.markdown,.text-message,[data-message-content]');
|
|
11505
|
+
const text=((content||el).innerText||'').trim().slice(0,600);
|
|
11506
|
+
if(text) msgs.push({role,text});
|
|
11507
|
+
});
|
|
11508
|
+
return JSON.stringify({provider:'ChatGPT',messages:msgs.slice(-8),url:location.href,title:document.title});
|
|
11509
|
+
})()`,
|
|
11510
|
+
Gemini: `(function(){
|
|
11511
|
+
const msgs=[];
|
|
11512
|
+
document.querySelectorAll('user-query,model-response').forEach(el=>{
|
|
11513
|
+
const role=el.tagName.toLowerCase().includes('user')?'user':'assistant';
|
|
11514
|
+
const text=(el.innerText||'').trim().slice(0,600);
|
|
11515
|
+
if(text) msgs.push({role,text});
|
|
11516
|
+
});
|
|
11517
|
+
if(!msgs.length){
|
|
11518
|
+
document.querySelectorAll('[class*="user-query"],[class*="model-response"]').forEach(el=>{
|
|
11519
|
+
const isUser=el.className.toLowerCase().includes('user');
|
|
11520
|
+
const text=(el.innerText||'').trim().slice(0,600);
|
|
11521
|
+
if(text) msgs.push({role:isUser?'user':'assistant',text});
|
|
11522
|
+
});
|
|
11523
|
+
}
|
|
11524
|
+
return JSON.stringify({provider:'Gemini',messages:msgs.slice(-8),url:location.href,title:document.title});
|
|
11525
|
+
})()`,
|
|
11526
|
+
Perplexity: `(function(){
|
|
11527
|
+
const msgs=[];
|
|
11528
|
+
document.querySelectorAll('[class*="prose"],[class*="AnswerBody"],[class*="UserMessage"]').forEach(el=>{
|
|
11529
|
+
if(el.children.length>2){
|
|
11530
|
+
const text=(el.innerText||'').trim().slice(0,600);
|
|
11531
|
+
if(text.length>30) msgs.push({role:'assistant',text});
|
|
11532
|
+
}
|
|
11533
|
+
});
|
|
11534
|
+
return JSON.stringify({provider:'Perplexity',messages:msgs.slice(-4),url:location.href,title:document.title});
|
|
11535
|
+
})()`
|
|
11536
|
+
};
|
|
11537
|
+
const extractorKey = provider?.name ?? "Claude";
|
|
11538
|
+
const jsCode = JS_EXTRACTORS[extractorKey] ?? JS_EXTRACTORS.Claude;
|
|
11539
|
+
try {
|
|
11540
|
+
const browserName = targetTab.browser;
|
|
11541
|
+
if (browserName !== "Safari") {
|
|
11542
|
+
const switchScript = `
|
|
11543
|
+
tell application "${browserName.replace(/"/g, '\\"')}"
|
|
11544
|
+
repeat with w in windows
|
|
11545
|
+
set tabIdx to 1
|
|
11546
|
+
repeat with t in tabs of w
|
|
11547
|
+
if URL of t contains "${targetTab.url.slice(0, 80).replace(/"/g, '\\"')}" then
|
|
11548
|
+
set active tab index of w to tabIdx
|
|
11549
|
+
activate
|
|
11550
|
+
return "switched"
|
|
11551
|
+
end if
|
|
11552
|
+
set tabIdx to tabIdx + 1
|
|
11553
|
+
end repeat
|
|
11554
|
+
end repeat
|
|
11555
|
+
end tell`;
|
|
11556
|
+
await runAppleScript2(switchScript).catch(() => {
|
|
11557
|
+
});
|
|
11558
|
+
}
|
|
11559
|
+
const raw = await adapter.browserExecuteJs(jsCode, browserName);
|
|
11560
|
+
if (!raw.success) {
|
|
11561
|
+
return { success: false, error: `Could not read ${extractorKey} session: ${raw.error}` };
|
|
11562
|
+
}
|
|
11563
|
+
let parsed;
|
|
11564
|
+
try {
|
|
11565
|
+
parsed = JSON.parse(raw.data);
|
|
11566
|
+
} catch {
|
|
11567
|
+
return { success: false, error: "Failed to parse conversation content from browser." };
|
|
11568
|
+
}
|
|
11569
|
+
return {
|
|
11570
|
+
success: true,
|
|
11571
|
+
data: {
|
|
11572
|
+
provider: parsed.provider,
|
|
11573
|
+
browser: browserName,
|
|
11574
|
+
title: parsed.title ?? targetTab.title,
|
|
11575
|
+
url: parsed.url ?? targetTab.url,
|
|
11576
|
+
messages: parsed.messages,
|
|
11577
|
+
messageCount: parsed.messages.length
|
|
11578
|
+
}
|
|
11579
|
+
};
|
|
11580
|
+
} catch (err) {
|
|
11581
|
+
return { success: false, error: `Failed to read session: ${err.message}` };
|
|
11582
|
+
}
|
|
11583
|
+
}
|
|
11584
|
+
case "sys_llm_send_message": {
|
|
11585
|
+
const message = params.message;
|
|
11586
|
+
if (!message) return { success: false, error: "Missing message" };
|
|
11587
|
+
const targetUrl = params.url;
|
|
11588
|
+
const targetIndex = params.index !== void 0 ? Number(params.index) : void 0;
|
|
11589
|
+
const LLM_PROVIDERS = [
|
|
11590
|
+
{ domain: "claude.ai", name: "Claude" },
|
|
11591
|
+
{ domain: "chatgpt.com", name: "ChatGPT" },
|
|
11592
|
+
{ domain: "chat.openai.com", name: "ChatGPT" },
|
|
11593
|
+
{ domain: "gemini.google.com", name: "Gemini" },
|
|
11594
|
+
{ domain: "perplexity.ai", name: "Perplexity" }
|
|
11595
|
+
];
|
|
11596
|
+
const allTabs = await adapter.browserListTabs();
|
|
11597
|
+
const llmTabs = allTabs.filter(
|
|
11598
|
+
(tab) => LLM_PROVIDERS.some((p) => tab.url.toLowerCase().includes(p.domain))
|
|
11599
|
+
);
|
|
11600
|
+
if (llmTabs.length === 0) {
|
|
11601
|
+
return { success: false, error: "No LLM tabs found. Open Claude or ChatGPT in your browser first." };
|
|
11602
|
+
}
|
|
11603
|
+
let targetTab = llmTabs[0];
|
|
11604
|
+
if (targetUrl) {
|
|
11605
|
+
const found = llmTabs.find((t) => t.url.toLowerCase().includes(targetUrl.toLowerCase()));
|
|
11606
|
+
if (found) targetTab = found;
|
|
11607
|
+
} else if (targetIndex !== void 0 && llmTabs[targetIndex]) {
|
|
11608
|
+
targetTab = llmTabs[targetIndex];
|
|
11609
|
+
} else {
|
|
11610
|
+
const active = llmTabs.find((t) => t.active);
|
|
11611
|
+
const claude = llmTabs.find((t) => t.url.includes("claude.ai"));
|
|
11612
|
+
targetTab = active ?? claude ?? llmTabs[0];
|
|
11613
|
+
}
|
|
11614
|
+
const provider = LLM_PROVIDERS.find(
|
|
11615
|
+
(p) => targetTab.url.toLowerCase().includes(p.domain)
|
|
11616
|
+
);
|
|
11617
|
+
const safe = message.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
11618
|
+
const JS_SENDERS = {
|
|
11619
|
+
Claude: `(function(){
|
|
11620
|
+
const msg=\`${safe}\`;
|
|
11621
|
+
const input=document.querySelector('[contenteditable="true"][data-placeholder]')||
|
|
11622
|
+
document.querySelector('div[contenteditable="true"]');
|
|
11623
|
+
if(!input) return JSON.stringify({success:false,error:'Input field not found'});
|
|
11624
|
+
input.focus();
|
|
11625
|
+
input.innerText=msg;
|
|
11626
|
+
input.dispatchEvent(new InputEvent('input',{bubbles:true,inputType:'insertText',data:msg}));
|
|
11627
|
+
setTimeout(()=>{
|
|
11628
|
+
const btn=document.querySelector('button[aria-label*="Send"]')||
|
|
11629
|
+
document.querySelector('button[data-testid*="send"]')||
|
|
11630
|
+
document.querySelector('button[aria-label*="send"]');
|
|
11631
|
+
if(btn&&!btn.disabled){btn.click();return JSON.stringify({success:true,sent:msg});}
|
|
11632
|
+
},300);
|
|
11633
|
+
return JSON.stringify({success:true,sent:msg,note:'Typed message, attempting submit'});
|
|
11634
|
+
})()`,
|
|
11635
|
+
ChatGPT: `(function(){
|
|
11636
|
+
const msg=\`${safe}\`;
|
|
11637
|
+
const input=document.querySelector('#prompt-textarea')||
|
|
11638
|
+
document.querySelector('[contenteditable="true"]')||
|
|
11639
|
+
document.querySelector('textarea');
|
|
11640
|
+
if(!input) return JSON.stringify({success:false,error:'Input field not found'});
|
|
11641
|
+
input.focus();
|
|
11642
|
+
if(input.tagName==='TEXTAREA'){
|
|
11643
|
+
const setter=Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype,'value').set;
|
|
11644
|
+
setter.call(input,msg);
|
|
11645
|
+
} else {
|
|
11646
|
+
input.innerText=msg;
|
|
11647
|
+
}
|
|
11648
|
+
input.dispatchEvent(new Event('input',{bubbles:true}));
|
|
11649
|
+
setTimeout(()=>{
|
|
11650
|
+
const btn=document.querySelector('[data-testid="send-button"]')||
|
|
11651
|
+
document.querySelector('button[aria-label*="Send"]');
|
|
11652
|
+
if(btn&&!btn.disabled) btn.click();
|
|
11653
|
+
},300);
|
|
11654
|
+
return JSON.stringify({success:true,sent:msg});
|
|
11655
|
+
})()`,
|
|
11656
|
+
Gemini: `(function(){
|
|
11657
|
+
const msg=\`${safe}\`;
|
|
11658
|
+
const input=document.querySelector('.ql-editor')||
|
|
11659
|
+
document.querySelector('[contenteditable="true"]')||
|
|
11660
|
+
document.querySelector('textarea');
|
|
11661
|
+
if(!input) return JSON.stringify({success:false,error:'Input field not found'});
|
|
11662
|
+
input.focus();
|
|
11663
|
+
input.innerText=msg;
|
|
11664
|
+
input.dispatchEvent(new InputEvent('input',{bubbles:true}));
|
|
11665
|
+
setTimeout(()=>{
|
|
11666
|
+
const btn=document.querySelector('button[aria-label*="Send"]')||
|
|
11667
|
+
document.querySelector('button.send-button');
|
|
11668
|
+
if(btn&&!btn.disabled) btn.click();
|
|
11669
|
+
},300);
|
|
11670
|
+
return JSON.stringify({success:true,sent:msg});
|
|
11671
|
+
})()`
|
|
11672
|
+
};
|
|
11673
|
+
const senderKey = provider?.name ?? "Claude";
|
|
11674
|
+
const jsCode = JS_SENDERS[senderKey] ?? JS_SENDERS.Claude;
|
|
11675
|
+
try {
|
|
11676
|
+
const browserName = targetTab.browser;
|
|
11677
|
+
if (browserName !== "Safari") {
|
|
11678
|
+
const switchScript = `
|
|
11679
|
+
tell application "${browserName.replace(/"/g, '\\"')}"
|
|
11680
|
+
repeat with w in windows
|
|
11681
|
+
set tabIdx to 1
|
|
11682
|
+
repeat with t in tabs of w
|
|
11683
|
+
if URL of t contains "${targetTab.url.slice(0, 80).replace(/"/g, '\\"')}" then
|
|
11684
|
+
set active tab index of w to tabIdx
|
|
11685
|
+
activate
|
|
11686
|
+
return "switched"
|
|
11687
|
+
end if
|
|
11688
|
+
set tabIdx to tabIdx + 1
|
|
11689
|
+
end repeat
|
|
11690
|
+
end repeat
|
|
11691
|
+
end tell`;
|
|
11692
|
+
await runAppleScript2(switchScript).catch(() => {
|
|
11693
|
+
});
|
|
11694
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
11695
|
+
}
|
|
11696
|
+
const result = await adapter.browserExecuteJs(jsCode, browserName);
|
|
11697
|
+
if (!result.success) {
|
|
11698
|
+
return { success: false, error: `Could not send to ${senderKey}: ${result.error}` };
|
|
11699
|
+
}
|
|
11700
|
+
return {
|
|
11701
|
+
success: true,
|
|
11702
|
+
data: {
|
|
11703
|
+
provider: senderKey,
|
|
11704
|
+
browser: browserName,
|
|
11705
|
+
tab: targetTab.title,
|
|
11706
|
+
url: targetTab.url,
|
|
11707
|
+
message,
|
|
11708
|
+
note: "Message typed and submitted. Check browser to confirm."
|
|
11709
|
+
}
|
|
11710
|
+
};
|
|
11711
|
+
} catch (err) {
|
|
11712
|
+
return { success: false, error: `Failed to send message: ${err.message}` };
|
|
11713
|
+
}
|
|
11714
|
+
}
|
|
10968
11715
|
// ── IDE Integration ────────────────────────────────────
|
|
10969
11716
|
// Helper: extract open workspace paths from a VS Code/Cursor/Windsurf storage.json
|
|
10970
11717
|
// Storage format: windowsState.lastActiveWindow / openedWindows
|
|
@@ -11593,6 +12340,13 @@ async function probeCapabilities() {
|
|
|
11593
12340
|
}
|
|
11594
12341
|
}
|
|
11595
12342
|
}
|
|
12343
|
+
tools.add("llm_infer");
|
|
12344
|
+
tools.add("llm_list_local");
|
|
12345
|
+
tools.add("llm_detect_sessions");
|
|
12346
|
+
if (adapter.platform === "macos" || adapter.platform === "windows") {
|
|
12347
|
+
tools.add("llm_browser_claude");
|
|
12348
|
+
tools.add("llm_browser_chatgpt");
|
|
12349
|
+
}
|
|
11596
12350
|
const cap = { available, unavailable, tools: Array.from(tools) };
|
|
11597
12351
|
console.log(
|
|
11598
12352
|
` \u2705 Available: ${available.join(", ") || "all adapter tools"}`
|