@pulso/companion 0.4.5 → 0.4.7
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 +849 -101
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9899,6 +9899,395 @@ 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_infer") {
|
|
9959
|
+
const {
|
|
9960
|
+
messages,
|
|
9961
|
+
model,
|
|
9962
|
+
max_tokens = 2e3,
|
|
9963
|
+
temperature = 0.7,
|
|
9964
|
+
stream = false
|
|
9965
|
+
} = params;
|
|
9966
|
+
const localEndpoints = [];
|
|
9967
|
+
for (const [url, provider] of [
|
|
9968
|
+
["http://localhost:11434", "ollama"],
|
|
9969
|
+
["http://localhost:1234", "lmstudio"],
|
|
9970
|
+
["http://localhost:1337", "jan"]
|
|
9971
|
+
]) {
|
|
9972
|
+
try {
|
|
9973
|
+
const tagsUrl = provider === "ollama" ? `${url}/api/tags` : `${url}/v1/models`;
|
|
9974
|
+
const res = await fetch(tagsUrl, { signal: AbortSignal.timeout(1500) });
|
|
9975
|
+
if (res.ok) {
|
|
9976
|
+
const data = await res.json();
|
|
9977
|
+
const models = data.models?.map((m) => m.name) ?? data.data?.map((m) => m.id) ?? [];
|
|
9978
|
+
localEndpoints.push({ url, provider, models });
|
|
9979
|
+
}
|
|
9980
|
+
} catch {
|
|
9981
|
+
}
|
|
9982
|
+
}
|
|
9983
|
+
if (localEndpoints.length === 0) {
|
|
9984
|
+
return {
|
|
9985
|
+
success: false,
|
|
9986
|
+
error: "No local AI running. Please start Ollama, LM Studio, or Jan."
|
|
9987
|
+
};
|
|
9988
|
+
}
|
|
9989
|
+
let chosenEndpoint = localEndpoints[0];
|
|
9990
|
+
let chosenModel = model;
|
|
9991
|
+
if (model) {
|
|
9992
|
+
for (const ep of localEndpoints) {
|
|
9993
|
+
if (ep.models.some((m) => m.includes(model) || model.includes(m))) {
|
|
9994
|
+
chosenEndpoint = ep;
|
|
9995
|
+
chosenModel = ep.models.find((m) => m.includes(model) || model.includes(m));
|
|
9996
|
+
break;
|
|
9997
|
+
}
|
|
9998
|
+
}
|
|
9999
|
+
}
|
|
10000
|
+
if (!chosenModel && chosenEndpoint.models.length > 0) {
|
|
10001
|
+
const paramPriority = ["70b", "32b", "14b", "13b", "8b", "7b", "3b", "1b"];
|
|
10002
|
+
let found = false;
|
|
10003
|
+
for (const size of paramPriority) {
|
|
10004
|
+
const match = chosenEndpoint.models.find(
|
|
10005
|
+
(m) => m.toLowerCase().includes(size)
|
|
10006
|
+
);
|
|
10007
|
+
if (match) {
|
|
10008
|
+
chosenModel = match;
|
|
10009
|
+
found = true;
|
|
10010
|
+
break;
|
|
10011
|
+
}
|
|
10012
|
+
}
|
|
10013
|
+
if (!found) chosenModel = chosenEndpoint.models[0];
|
|
10014
|
+
}
|
|
10015
|
+
const apiUrl = chosenEndpoint.provider === "ollama" ? `${chosenEndpoint.url}/v1/chat/completions` : `${chosenEndpoint.url}/v1/chat/completions`;
|
|
10016
|
+
try {
|
|
10017
|
+
const inferRes = await fetch(apiUrl, {
|
|
10018
|
+
method: "POST",
|
|
10019
|
+
headers: { "Content-Type": "application/json" },
|
|
10020
|
+
body: JSON.stringify({
|
|
10021
|
+
model: chosenModel,
|
|
10022
|
+
messages,
|
|
10023
|
+
max_tokens,
|
|
10024
|
+
temperature,
|
|
10025
|
+
stream: false
|
|
10026
|
+
// Companion returns full response
|
|
10027
|
+
}),
|
|
10028
|
+
signal: AbortSignal.timeout(12e4)
|
|
10029
|
+
// 2 min max
|
|
10030
|
+
});
|
|
10031
|
+
if (!inferRes.ok) {
|
|
10032
|
+
const errText = await inferRes.text().catch(() => `HTTP ${inferRes.status}`);
|
|
10033
|
+
return { success: false, error: `Local AI error: ${errText.slice(0, 200)}` };
|
|
10034
|
+
}
|
|
10035
|
+
const data = await inferRes.json();
|
|
10036
|
+
const content = data.choices?.[0]?.message?.content ?? "";
|
|
10037
|
+
return {
|
|
10038
|
+
success: true,
|
|
10039
|
+
data: {
|
|
10040
|
+
content,
|
|
10041
|
+
model: data.model ?? chosenModel,
|
|
10042
|
+
provider: chosenEndpoint.provider,
|
|
10043
|
+
endpoint: chosenEndpoint.url,
|
|
10044
|
+
cost_usd: 0,
|
|
10045
|
+
// Local = always free
|
|
10046
|
+
usage: data.usage
|
|
10047
|
+
}
|
|
10048
|
+
};
|
|
10049
|
+
} catch (err) {
|
|
10050
|
+
return {
|
|
10051
|
+
success: false,
|
|
10052
|
+
error: `Inference failed: ${err.message}`
|
|
10053
|
+
};
|
|
10054
|
+
}
|
|
10055
|
+
}
|
|
10056
|
+
if (command === "llm_browser_claude") {
|
|
10057
|
+
const { prompt, system } = params;
|
|
10058
|
+
try {
|
|
10059
|
+
const tabsResult = await adapter.browserListTabs();
|
|
10060
|
+
const tabs = Array.isArray(tabsResult) ? tabsResult : tabsResult?.data ?? [];
|
|
10061
|
+
const claudeTab = tabs.find(
|
|
10062
|
+
(t) => t.url?.includes("claude.ai")
|
|
10063
|
+
);
|
|
10064
|
+
if (!claudeTab) {
|
|
10065
|
+
await adapter.browserNavigate("https://claude.ai/new");
|
|
10066
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
10067
|
+
}
|
|
10068
|
+
const fullPrompt = system ? `[Context: ${system}]
|
|
10069
|
+
|
|
10070
|
+
${prompt}` : prompt;
|
|
10071
|
+
const submitScript = `
|
|
10072
|
+
(async () => {
|
|
10073
|
+
// Navigate to new conversation if needed
|
|
10074
|
+
const url = window.location.href;
|
|
10075
|
+
if (!url.includes('/chat/') && !url.includes('/new')) {
|
|
10076
|
+
window.location.href = 'https://claude.ai/new';
|
|
10077
|
+
return { navigating: true };
|
|
10078
|
+
}
|
|
10079
|
+
|
|
10080
|
+
// Find the input field
|
|
10081
|
+
const editor = document.querySelector('[contenteditable="true"]') ||
|
|
10082
|
+
document.querySelector('textarea[placeholder]') ||
|
|
10083
|
+
document.querySelector('.ProseMirror');
|
|
10084
|
+
|
|
10085
|
+
if (!editor) return { error: 'Input field not found' };
|
|
10086
|
+
|
|
10087
|
+
// Clear and type the prompt
|
|
10088
|
+
editor.focus();
|
|
10089
|
+
document.execCommand('selectAll', false, null);
|
|
10090
|
+
document.execCommand('delete', false, null);
|
|
10091
|
+
|
|
10092
|
+
// Type the prompt
|
|
10093
|
+
const text = ${JSON.stringify(fullPrompt)};
|
|
10094
|
+
document.execCommand('insertText', false, text);
|
|
10095
|
+
|
|
10096
|
+
// Trigger React state update
|
|
10097
|
+
editor.dispatchEvent(new InputEvent('input', { bubbles: true }));
|
|
10098
|
+
|
|
10099
|
+
// Small delay for state update
|
|
10100
|
+
await new Promise(r => setTimeout(r, 500));
|
|
10101
|
+
|
|
10102
|
+
// Find and click send button
|
|
10103
|
+
const sendBtn = document.querySelector('button[aria-label*="Send"]') ||
|
|
10104
|
+
document.querySelector('button[data-testid="send-button"]') ||
|
|
10105
|
+
[...document.querySelectorAll('button')].find(
|
|
10106
|
+
b => b.getAttribute('aria-label')?.toLowerCase().includes('send')
|
|
10107
|
+
);
|
|
10108
|
+
|
|
10109
|
+
if (sendBtn && !sendBtn.disabled) {
|
|
10110
|
+
sendBtn.click();
|
|
10111
|
+
return { submitted: true };
|
|
10112
|
+
}
|
|
10113
|
+
|
|
10114
|
+
// Fallback: Enter key
|
|
10115
|
+
editor.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
10116
|
+
return { submitted: true, method: 'enter' };
|
|
10117
|
+
})()
|
|
10118
|
+
`;
|
|
10119
|
+
await adapter.browserExecuteJs(submitScript);
|
|
10120
|
+
const pollScript = `
|
|
10121
|
+
(() => {
|
|
10122
|
+
const stopBtn = document.querySelector('button[aria-label*="Stop"]') ||
|
|
10123
|
+
document.querySelector('[data-testid="stop-button"]');
|
|
10124
|
+
const isStreaming = !!stopBtn && !stopBtn.disabled;
|
|
10125
|
+
|
|
10126
|
+
// Get the last assistant message
|
|
10127
|
+
const msgs = document.querySelectorAll('[data-is-streaming]');
|
|
10128
|
+
const lastMsg = msgs[msgs.length - 1];
|
|
10129
|
+
const streaming = lastMsg?.getAttribute('data-is-streaming') === 'true';
|
|
10130
|
+
|
|
10131
|
+
// Get message text
|
|
10132
|
+
const allMsgs = document.querySelectorAll('.font-claude-message, [data-message-author-role="assistant"]');
|
|
10133
|
+
const lastContent = allMsgs[allMsgs.length - 1]?.innerText ?? '';
|
|
10134
|
+
|
|
10135
|
+
return {
|
|
10136
|
+
isStreaming: isStreaming || streaming,
|
|
10137
|
+
hasContent: lastContent.length > 10,
|
|
10138
|
+
preview: lastContent.slice(0, 100)
|
|
10139
|
+
};
|
|
10140
|
+
})()
|
|
10141
|
+
`;
|
|
10142
|
+
let responseText = "";
|
|
10143
|
+
const maxWait = 6e4;
|
|
10144
|
+
const pollInterval = 1500;
|
|
10145
|
+
const startTime = Date.now();
|
|
10146
|
+
while (Date.now() - startTime < maxWait) {
|
|
10147
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
10148
|
+
const pollResult = await adapter.browserExecuteJs(pollScript);
|
|
10149
|
+
const status = pollResult?.data ?? pollResult;
|
|
10150
|
+
if (!status.isStreaming && status.hasContent) {
|
|
10151
|
+
const extractScript = `
|
|
10152
|
+
(() => {
|
|
10153
|
+
const msgs = document.querySelectorAll(
|
|
10154
|
+
'.font-claude-message, [data-message-author-role="assistant"]'
|
|
10155
|
+
);
|
|
10156
|
+
const last = msgs[msgs.length - 1];
|
|
10157
|
+
return last ? last.innerText : '';
|
|
10158
|
+
})()
|
|
10159
|
+
`;
|
|
10160
|
+
const extracted = await adapter.browserExecuteJs(extractScript);
|
|
10161
|
+
responseText = String(extracted?.data ?? extracted ?? "").trim();
|
|
10162
|
+
if (responseText.length > 20) break;
|
|
10163
|
+
}
|
|
10164
|
+
}
|
|
10165
|
+
if (!responseText) {
|
|
10166
|
+
return {
|
|
10167
|
+
success: false,
|
|
10168
|
+
error: "Timeout waiting for Claude.ai response. Is claude.ai open and logged in?"
|
|
10169
|
+
};
|
|
10170
|
+
}
|
|
10171
|
+
return {
|
|
10172
|
+
success: true,
|
|
10173
|
+
data: {
|
|
10174
|
+
content: responseText,
|
|
10175
|
+
provider: "claude.ai",
|
|
10176
|
+
model: "claude-3-5-sonnet",
|
|
10177
|
+
// Claude.ai uses latest model
|
|
10178
|
+
cost_usd: 0,
|
|
10179
|
+
// Uses subscription — free per-message
|
|
10180
|
+
note: "Response via Claude.ai browser session"
|
|
10181
|
+
}
|
|
10182
|
+
};
|
|
10183
|
+
} catch (err) {
|
|
10184
|
+
return {
|
|
10185
|
+
success: false,
|
|
10186
|
+
error: `Browser Claude bridge failed: ${err.message}`
|
|
10187
|
+
};
|
|
10188
|
+
}
|
|
10189
|
+
}
|
|
10190
|
+
if (command === "llm_browser_chatgpt") {
|
|
10191
|
+
const { prompt, system } = params;
|
|
10192
|
+
try {
|
|
10193
|
+
const tabsResult = await adapter.browserListTabs();
|
|
10194
|
+
const tabs = Array.isArray(tabsResult) ? tabsResult : tabsResult?.data ?? [];
|
|
10195
|
+
const gptTab = tabs.find(
|
|
10196
|
+
(t) => t.url?.includes("chatgpt.com") || t.url?.includes("chat.openai.com")
|
|
10197
|
+
);
|
|
10198
|
+
if (!gptTab) {
|
|
10199
|
+
await adapter.browserNavigate("https://chatgpt.com/");
|
|
10200
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
10201
|
+
}
|
|
10202
|
+
const fullPrompt = system ? `${system}
|
|
10203
|
+
|
|
10204
|
+
${prompt}` : prompt;
|
|
10205
|
+
const submitScript = `
|
|
10206
|
+
(async () => {
|
|
10207
|
+
const editor = document.getElementById('prompt-textarea') ||
|
|
10208
|
+
document.querySelector('textarea[placeholder]') ||
|
|
10209
|
+
document.querySelector('[contenteditable="true"][id*="prompt"]');
|
|
10210
|
+
|
|
10211
|
+
if (!editor) return { error: 'Input not found' };
|
|
10212
|
+
|
|
10213
|
+
editor.focus();
|
|
10214
|
+
const nativeInputSetter = Object.getOwnPropertyDescriptor(
|
|
10215
|
+
editor.tagName === 'TEXTAREA'
|
|
10216
|
+
? window.HTMLTextAreaElement.prototype
|
|
10217
|
+
: window.HTMLDivElement.prototype,
|
|
10218
|
+
'value'
|
|
10219
|
+
);
|
|
10220
|
+
if (nativeInputSetter?.set) {
|
|
10221
|
+
nativeInputSetter.set.call(editor, ${JSON.stringify(fullPrompt)});
|
|
10222
|
+
} else {
|
|
10223
|
+
editor.textContent = ${JSON.stringify(fullPrompt)};
|
|
10224
|
+
}
|
|
10225
|
+
editor.dispatchEvent(new Event('input', { bubbles: true }));
|
|
10226
|
+
|
|
10227
|
+
await new Promise(r => setTimeout(r, 300));
|
|
10228
|
+
|
|
10229
|
+
const sendBtn = document.querySelector('button[data-testid="send-button"]') ||
|
|
10230
|
+
document.querySelector('[aria-label="Send prompt"]');
|
|
10231
|
+
if (sendBtn) { sendBtn.click(); return { submitted: true }; }
|
|
10232
|
+
|
|
10233
|
+
editor.dispatchEvent(new KeyboardEvent('keydown', {
|
|
10234
|
+
key: 'Enter', keyCode: 13, bubbles: true
|
|
10235
|
+
}));
|
|
10236
|
+
return { submitted: true, method: 'enter' };
|
|
10237
|
+
})()
|
|
10238
|
+
`;
|
|
10239
|
+
await adapter.browserExecuteJs(submitScript);
|
|
10240
|
+
const maxWait = 9e4;
|
|
10241
|
+
const pollInterval = 2e3;
|
|
10242
|
+
const startTime = Date.now();
|
|
10243
|
+
let responseText = "";
|
|
10244
|
+
while (Date.now() - startTime < maxWait) {
|
|
10245
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
10246
|
+
const pollResult = await adapter.browserExecuteJs(`
|
|
10247
|
+
(() => {
|
|
10248
|
+
const isGenerating = !!document.querySelector(
|
|
10249
|
+
'button[aria-label="Stop streaming"], [data-testid="stop-button"]'
|
|
10250
|
+
);
|
|
10251
|
+
const msgs = document.querySelectorAll(
|
|
10252
|
+
'[data-message-author-role="assistant"] .markdown'
|
|
10253
|
+
);
|
|
10254
|
+
const last = msgs[msgs.length - 1];
|
|
10255
|
+
return {
|
|
10256
|
+
isGenerating,
|
|
10257
|
+
content: last ? last.innerText : '',
|
|
10258
|
+
count: msgs.length
|
|
10259
|
+
};
|
|
10260
|
+
})()
|
|
10261
|
+
`);
|
|
10262
|
+
const st = pollResult?.data ?? pollResult;
|
|
10263
|
+
if (!st.isGenerating && String(st.content ?? "").length > 20) {
|
|
10264
|
+
responseText = String(st.content).trim();
|
|
10265
|
+
break;
|
|
10266
|
+
}
|
|
10267
|
+
}
|
|
10268
|
+
if (!responseText) {
|
|
10269
|
+
return {
|
|
10270
|
+
success: false,
|
|
10271
|
+
error: "Timeout waiting for ChatGPT response. Is chatgpt.com open and logged in?"
|
|
10272
|
+
};
|
|
10273
|
+
}
|
|
10274
|
+
return {
|
|
10275
|
+
success: true,
|
|
10276
|
+
data: {
|
|
10277
|
+
content: responseText,
|
|
10278
|
+
provider: "chatgpt.com",
|
|
10279
|
+
model: "gpt-4o",
|
|
10280
|
+
cost_usd: 0,
|
|
10281
|
+
note: "Response via ChatGPT browser session"
|
|
10282
|
+
}
|
|
10283
|
+
};
|
|
10284
|
+
} catch (err) {
|
|
10285
|
+
return {
|
|
10286
|
+
success: false,
|
|
10287
|
+
error: `Browser ChatGPT bridge failed: ${err.message}`
|
|
10288
|
+
};
|
|
10289
|
+
}
|
|
10290
|
+
}
|
|
9902
10291
|
if (command === "ollama_detect") {
|
|
9903
10292
|
try {
|
|
9904
10293
|
const controller = new AbortController();
|
|
@@ -10965,118 +11354,457 @@ print(result.stdout[:5000])
|
|
|
10965
11354
|
return { success: false, error: err.message };
|
|
10966
11355
|
}
|
|
10967
11356
|
}
|
|
11357
|
+
// ── LLM Browser Sessions ───────────────────────────────
|
|
11358
|
+
case "sys_llm_list_sessions": {
|
|
11359
|
+
const LLM_PROVIDERS = [
|
|
11360
|
+
{ domain: "claude.ai", name: "Claude" },
|
|
11361
|
+
{ domain: "chatgpt.com", name: "ChatGPT" },
|
|
11362
|
+
{ domain: "chat.openai.com", name: "ChatGPT" },
|
|
11363
|
+
{ domain: "gemini.google.com", name: "Gemini" },
|
|
11364
|
+
{ domain: "perplexity.ai", name: "Perplexity" },
|
|
11365
|
+
{ domain: "copilot.microsoft.com", name: "Copilot" },
|
|
11366
|
+
{ domain: "grok.com", name: "Grok" },
|
|
11367
|
+
{ domain: "aistudio.google.com", name: "AI Studio" },
|
|
11368
|
+
{ domain: "poe.com", name: "Poe" },
|
|
11369
|
+
{ domain: "mistral.ai/chat", name: "Mistral" },
|
|
11370
|
+
{ domain: "deepseek.com/chat", name: "DeepSeek" }
|
|
11371
|
+
];
|
|
11372
|
+
const allTabs = await adapter.browserListTabs();
|
|
11373
|
+
const sessions = allTabs.filter((tab) => {
|
|
11374
|
+
const url = tab.url.toLowerCase();
|
|
11375
|
+
return LLM_PROVIDERS.some((p) => url.includes(p.domain));
|
|
11376
|
+
}).map((tab) => {
|
|
11377
|
+
const provider = LLM_PROVIDERS.find(
|
|
11378
|
+
(p) => tab.url.toLowerCase().includes(p.domain)
|
|
11379
|
+
);
|
|
11380
|
+
return {
|
|
11381
|
+
provider: provider?.name ?? "Unknown",
|
|
11382
|
+
browser: tab.browser,
|
|
11383
|
+
title: tab.title,
|
|
11384
|
+
url: tab.url,
|
|
11385
|
+
active: tab.active ?? false
|
|
11386
|
+
};
|
|
11387
|
+
});
|
|
11388
|
+
return {
|
|
11389
|
+
success: true,
|
|
11390
|
+
data: {
|
|
11391
|
+
sessions,
|
|
11392
|
+
count: sessions.length,
|
|
11393
|
+
note: sessions.length === 0 ? "No LLM tabs open. Open Claude, ChatGPT, Gemini, or Perplexity in your browser." : void 0
|
|
11394
|
+
}
|
|
11395
|
+
};
|
|
11396
|
+
}
|
|
11397
|
+
case "sys_llm_read_session": {
|
|
11398
|
+
const targetUrl = params.url;
|
|
11399
|
+
const targetIndex = params.index !== void 0 ? Number(params.index) : void 0;
|
|
11400
|
+
const LLM_PROVIDERS = [
|
|
11401
|
+
{ domain: "claude.ai", name: "Claude" },
|
|
11402
|
+
{ domain: "chatgpt.com", name: "ChatGPT" },
|
|
11403
|
+
{ domain: "chat.openai.com", name: "ChatGPT" },
|
|
11404
|
+
{ domain: "gemini.google.com", name: "Gemini" },
|
|
11405
|
+
{ domain: "perplexity.ai", name: "Perplexity" },
|
|
11406
|
+
{ domain: "copilot.microsoft.com", name: "Copilot" },
|
|
11407
|
+
{ domain: "grok.com", name: "Grok" },
|
|
11408
|
+
{ domain: "aistudio.google.com", name: "AI Studio" },
|
|
11409
|
+
{ domain: "poe.com", name: "Poe" },
|
|
11410
|
+
{ domain: "mistral.ai/chat", name: "Mistral" },
|
|
11411
|
+
{ domain: "deepseek.com/chat", name: "DeepSeek" }
|
|
11412
|
+
];
|
|
11413
|
+
const allTabs = await adapter.browserListTabs();
|
|
11414
|
+
const llmTabs = allTabs.filter(
|
|
11415
|
+
(tab) => LLM_PROVIDERS.some((p) => tab.url.toLowerCase().includes(p.domain))
|
|
11416
|
+
);
|
|
11417
|
+
if (llmTabs.length === 0) {
|
|
11418
|
+
return { success: false, error: "No LLM tabs found. Open Claude, ChatGPT, or Gemini in your browser." };
|
|
11419
|
+
}
|
|
11420
|
+
let targetTab = llmTabs[0];
|
|
11421
|
+
if (targetUrl) {
|
|
11422
|
+
const found = llmTabs.find((t) => t.url.toLowerCase().includes(targetUrl.toLowerCase()));
|
|
11423
|
+
if (found) targetTab = found;
|
|
11424
|
+
} else if (targetIndex !== void 0 && llmTabs[targetIndex]) {
|
|
11425
|
+
targetTab = llmTabs[targetIndex];
|
|
11426
|
+
} else {
|
|
11427
|
+
const active = llmTabs.find((t) => t.active);
|
|
11428
|
+
const claude = llmTabs.find((t) => t.url.includes("claude.ai"));
|
|
11429
|
+
targetTab = active ?? claude ?? llmTabs[0];
|
|
11430
|
+
}
|
|
11431
|
+
const provider = LLM_PROVIDERS.find(
|
|
11432
|
+
(p) => targetTab.url.toLowerCase().includes(p.domain)
|
|
11433
|
+
);
|
|
11434
|
+
const JS_EXTRACTORS = {
|
|
11435
|
+
Claude: `(function(){
|
|
11436
|
+
const msgs=[];
|
|
11437
|
+
const turns=document.querySelectorAll('[data-testid="human-turn"],[data-testid="ai-turn"]');
|
|
11438
|
+
turns.forEach(el=>{
|
|
11439
|
+
const role=el.getAttribute('data-testid')==='human-turn'?'user':'assistant';
|
|
11440
|
+
const text=(el.innerText||'').trim().slice(0,600);
|
|
11441
|
+
if(text) msgs.push({role,text});
|
|
11442
|
+
});
|
|
11443
|
+
if(!msgs.length){
|
|
11444
|
+
document.querySelectorAll('.font-claude-message,.prose-claude').forEach(el=>{
|
|
11445
|
+
const text=(el.innerText||'').trim().slice(0,600);
|
|
11446
|
+
if(text) msgs.push({role:'assistant',text});
|
|
11447
|
+
});
|
|
11448
|
+
}
|
|
11449
|
+
return JSON.stringify({provider:'Claude',messages:msgs.slice(-8),url:location.href,title:document.title});
|
|
11450
|
+
})()`,
|
|
11451
|
+
ChatGPT: `(function(){
|
|
11452
|
+
const msgs=[];
|
|
11453
|
+
document.querySelectorAll('[data-message-author-role]').forEach(el=>{
|
|
11454
|
+
const role=el.getAttribute('data-message-author-role')||'unknown';
|
|
11455
|
+
const content=el.querySelector('.markdown,.text-message,[data-message-content]');
|
|
11456
|
+
const text=((content||el).innerText||'').trim().slice(0,600);
|
|
11457
|
+
if(text) msgs.push({role,text});
|
|
11458
|
+
});
|
|
11459
|
+
return JSON.stringify({provider:'ChatGPT',messages:msgs.slice(-8),url:location.href,title:document.title});
|
|
11460
|
+
})()`,
|
|
11461
|
+
Gemini: `(function(){
|
|
11462
|
+
const msgs=[];
|
|
11463
|
+
document.querySelectorAll('user-query,model-response').forEach(el=>{
|
|
11464
|
+
const role=el.tagName.toLowerCase().includes('user')?'user':'assistant';
|
|
11465
|
+
const text=(el.innerText||'').trim().slice(0,600);
|
|
11466
|
+
if(text) msgs.push({role,text});
|
|
11467
|
+
});
|
|
11468
|
+
if(!msgs.length){
|
|
11469
|
+
document.querySelectorAll('[class*="user-query"],[class*="model-response"]').forEach(el=>{
|
|
11470
|
+
const isUser=el.className.toLowerCase().includes('user');
|
|
11471
|
+
const text=(el.innerText||'').trim().slice(0,600);
|
|
11472
|
+
if(text) msgs.push({role:isUser?'user':'assistant',text});
|
|
11473
|
+
});
|
|
11474
|
+
}
|
|
11475
|
+
return JSON.stringify({provider:'Gemini',messages:msgs.slice(-8),url:location.href,title:document.title});
|
|
11476
|
+
})()`,
|
|
11477
|
+
Perplexity: `(function(){
|
|
11478
|
+
const msgs=[];
|
|
11479
|
+
document.querySelectorAll('[class*="prose"],[class*="AnswerBody"],[class*="UserMessage"]').forEach(el=>{
|
|
11480
|
+
if(el.children.length>2){
|
|
11481
|
+
const text=(el.innerText||'').trim().slice(0,600);
|
|
11482
|
+
if(text.length>30) msgs.push({role:'assistant',text});
|
|
11483
|
+
}
|
|
11484
|
+
});
|
|
11485
|
+
return JSON.stringify({provider:'Perplexity',messages:msgs.slice(-4),url:location.href,title:document.title});
|
|
11486
|
+
})()`
|
|
11487
|
+
};
|
|
11488
|
+
const extractorKey = provider?.name ?? "Claude";
|
|
11489
|
+
const jsCode = JS_EXTRACTORS[extractorKey] ?? JS_EXTRACTORS.Claude;
|
|
11490
|
+
try {
|
|
11491
|
+
const browserName = targetTab.browser;
|
|
11492
|
+
if (browserName !== "Safari") {
|
|
11493
|
+
const switchScript = `
|
|
11494
|
+
tell application "${browserName.replace(/"/g, '\\"')}"
|
|
11495
|
+
repeat with w in windows
|
|
11496
|
+
set tabIdx to 1
|
|
11497
|
+
repeat with t in tabs of w
|
|
11498
|
+
if URL of t contains "${targetTab.url.slice(0, 80).replace(/"/g, '\\"')}" then
|
|
11499
|
+
set active tab index of w to tabIdx
|
|
11500
|
+
activate
|
|
11501
|
+
return "switched"
|
|
11502
|
+
end if
|
|
11503
|
+
set tabIdx to tabIdx + 1
|
|
11504
|
+
end repeat
|
|
11505
|
+
end repeat
|
|
11506
|
+
end tell`;
|
|
11507
|
+
await runAppleScript2(switchScript).catch(() => {
|
|
11508
|
+
});
|
|
11509
|
+
}
|
|
11510
|
+
const raw = await adapter.browserExecuteJs(jsCode, browserName);
|
|
11511
|
+
if (!raw.success) {
|
|
11512
|
+
return { success: false, error: `Could not read ${extractorKey} session: ${raw.error}` };
|
|
11513
|
+
}
|
|
11514
|
+
let parsed;
|
|
11515
|
+
try {
|
|
11516
|
+
parsed = JSON.parse(raw.data);
|
|
11517
|
+
} catch {
|
|
11518
|
+
return { success: false, error: "Failed to parse conversation content from browser." };
|
|
11519
|
+
}
|
|
11520
|
+
return {
|
|
11521
|
+
success: true,
|
|
11522
|
+
data: {
|
|
11523
|
+
provider: parsed.provider,
|
|
11524
|
+
browser: browserName,
|
|
11525
|
+
title: parsed.title ?? targetTab.title,
|
|
11526
|
+
url: parsed.url ?? targetTab.url,
|
|
11527
|
+
messages: parsed.messages,
|
|
11528
|
+
messageCount: parsed.messages.length
|
|
11529
|
+
}
|
|
11530
|
+
};
|
|
11531
|
+
} catch (err) {
|
|
11532
|
+
return { success: false, error: `Failed to read session: ${err.message}` };
|
|
11533
|
+
}
|
|
11534
|
+
}
|
|
11535
|
+
case "sys_llm_send_message": {
|
|
11536
|
+
const message = params.message;
|
|
11537
|
+
if (!message) return { success: false, error: "Missing message" };
|
|
11538
|
+
const targetUrl = params.url;
|
|
11539
|
+
const targetIndex = params.index !== void 0 ? Number(params.index) : void 0;
|
|
11540
|
+
const LLM_PROVIDERS = [
|
|
11541
|
+
{ domain: "claude.ai", name: "Claude" },
|
|
11542
|
+
{ domain: "chatgpt.com", name: "ChatGPT" },
|
|
11543
|
+
{ domain: "chat.openai.com", name: "ChatGPT" },
|
|
11544
|
+
{ domain: "gemini.google.com", name: "Gemini" },
|
|
11545
|
+
{ domain: "perplexity.ai", name: "Perplexity" }
|
|
11546
|
+
];
|
|
11547
|
+
const allTabs = await adapter.browserListTabs();
|
|
11548
|
+
const llmTabs = allTabs.filter(
|
|
11549
|
+
(tab) => LLM_PROVIDERS.some((p) => tab.url.toLowerCase().includes(p.domain))
|
|
11550
|
+
);
|
|
11551
|
+
if (llmTabs.length === 0) {
|
|
11552
|
+
return { success: false, error: "No LLM tabs found. Open Claude or ChatGPT in your browser first." };
|
|
11553
|
+
}
|
|
11554
|
+
let targetTab = llmTabs[0];
|
|
11555
|
+
if (targetUrl) {
|
|
11556
|
+
const found = llmTabs.find((t) => t.url.toLowerCase().includes(targetUrl.toLowerCase()));
|
|
11557
|
+
if (found) targetTab = found;
|
|
11558
|
+
} else if (targetIndex !== void 0 && llmTabs[targetIndex]) {
|
|
11559
|
+
targetTab = llmTabs[targetIndex];
|
|
11560
|
+
} else {
|
|
11561
|
+
const active = llmTabs.find((t) => t.active);
|
|
11562
|
+
const claude = llmTabs.find((t) => t.url.includes("claude.ai"));
|
|
11563
|
+
targetTab = active ?? claude ?? llmTabs[0];
|
|
11564
|
+
}
|
|
11565
|
+
const provider = LLM_PROVIDERS.find(
|
|
11566
|
+
(p) => targetTab.url.toLowerCase().includes(p.domain)
|
|
11567
|
+
);
|
|
11568
|
+
const safe = message.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
11569
|
+
const JS_SENDERS = {
|
|
11570
|
+
Claude: `(function(){
|
|
11571
|
+
const msg=\`${safe}\`;
|
|
11572
|
+
const input=document.querySelector('[contenteditable="true"][data-placeholder]')||
|
|
11573
|
+
document.querySelector('div[contenteditable="true"]');
|
|
11574
|
+
if(!input) return JSON.stringify({success:false,error:'Input field not found'});
|
|
11575
|
+
input.focus();
|
|
11576
|
+
input.innerText=msg;
|
|
11577
|
+
input.dispatchEvent(new InputEvent('input',{bubbles:true,inputType:'insertText',data:msg}));
|
|
11578
|
+
setTimeout(()=>{
|
|
11579
|
+
const btn=document.querySelector('button[aria-label*="Send"]')||
|
|
11580
|
+
document.querySelector('button[data-testid*="send"]')||
|
|
11581
|
+
document.querySelector('button[aria-label*="send"]');
|
|
11582
|
+
if(btn&&!btn.disabled){btn.click();return JSON.stringify({success:true,sent:msg});}
|
|
11583
|
+
},300);
|
|
11584
|
+
return JSON.stringify({success:true,sent:msg,note:'Typed message, attempting submit'});
|
|
11585
|
+
})()`,
|
|
11586
|
+
ChatGPT: `(function(){
|
|
11587
|
+
const msg=\`${safe}\`;
|
|
11588
|
+
const input=document.querySelector('#prompt-textarea')||
|
|
11589
|
+
document.querySelector('[contenteditable="true"]')||
|
|
11590
|
+
document.querySelector('textarea');
|
|
11591
|
+
if(!input) return JSON.stringify({success:false,error:'Input field not found'});
|
|
11592
|
+
input.focus();
|
|
11593
|
+
if(input.tagName==='TEXTAREA'){
|
|
11594
|
+
const setter=Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype,'value').set;
|
|
11595
|
+
setter.call(input,msg);
|
|
11596
|
+
} else {
|
|
11597
|
+
input.innerText=msg;
|
|
11598
|
+
}
|
|
11599
|
+
input.dispatchEvent(new Event('input',{bubbles:true}));
|
|
11600
|
+
setTimeout(()=>{
|
|
11601
|
+
const btn=document.querySelector('[data-testid="send-button"]')||
|
|
11602
|
+
document.querySelector('button[aria-label*="Send"]');
|
|
11603
|
+
if(btn&&!btn.disabled) btn.click();
|
|
11604
|
+
},300);
|
|
11605
|
+
return JSON.stringify({success:true,sent:msg});
|
|
11606
|
+
})()`,
|
|
11607
|
+
Gemini: `(function(){
|
|
11608
|
+
const msg=\`${safe}\`;
|
|
11609
|
+
const input=document.querySelector('.ql-editor')||
|
|
11610
|
+
document.querySelector('[contenteditable="true"]')||
|
|
11611
|
+
document.querySelector('textarea');
|
|
11612
|
+
if(!input) return JSON.stringify({success:false,error:'Input field not found'});
|
|
11613
|
+
input.focus();
|
|
11614
|
+
input.innerText=msg;
|
|
11615
|
+
input.dispatchEvent(new InputEvent('input',{bubbles:true}));
|
|
11616
|
+
setTimeout(()=>{
|
|
11617
|
+
const btn=document.querySelector('button[aria-label*="Send"]')||
|
|
11618
|
+
document.querySelector('button.send-button');
|
|
11619
|
+
if(btn&&!btn.disabled) btn.click();
|
|
11620
|
+
},300);
|
|
11621
|
+
return JSON.stringify({success:true,sent:msg});
|
|
11622
|
+
})()`
|
|
11623
|
+
};
|
|
11624
|
+
const senderKey = provider?.name ?? "Claude";
|
|
11625
|
+
const jsCode = JS_SENDERS[senderKey] ?? JS_SENDERS.Claude;
|
|
11626
|
+
try {
|
|
11627
|
+
const browserName = targetTab.browser;
|
|
11628
|
+
if (browserName !== "Safari") {
|
|
11629
|
+
const switchScript = `
|
|
11630
|
+
tell application "${browserName.replace(/"/g, '\\"')}"
|
|
11631
|
+
repeat with w in windows
|
|
11632
|
+
set tabIdx to 1
|
|
11633
|
+
repeat with t in tabs of w
|
|
11634
|
+
if URL of t contains "${targetTab.url.slice(0, 80).replace(/"/g, '\\"')}" then
|
|
11635
|
+
set active tab index of w to tabIdx
|
|
11636
|
+
activate
|
|
11637
|
+
return "switched"
|
|
11638
|
+
end if
|
|
11639
|
+
set tabIdx to tabIdx + 1
|
|
11640
|
+
end repeat
|
|
11641
|
+
end repeat
|
|
11642
|
+
end tell`;
|
|
11643
|
+
await runAppleScript2(switchScript).catch(() => {
|
|
11644
|
+
});
|
|
11645
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
11646
|
+
}
|
|
11647
|
+
const result = await adapter.browserExecuteJs(jsCode, browserName);
|
|
11648
|
+
if (!result.success) {
|
|
11649
|
+
return { success: false, error: `Could not send to ${senderKey}: ${result.error}` };
|
|
11650
|
+
}
|
|
11651
|
+
return {
|
|
11652
|
+
success: true,
|
|
11653
|
+
data: {
|
|
11654
|
+
provider: senderKey,
|
|
11655
|
+
browser: browserName,
|
|
11656
|
+
tab: targetTab.title,
|
|
11657
|
+
url: targetTab.url,
|
|
11658
|
+
message,
|
|
11659
|
+
note: "Message typed and submitted. Check browser to confirm."
|
|
11660
|
+
}
|
|
11661
|
+
};
|
|
11662
|
+
} catch (err) {
|
|
11663
|
+
return { success: false, error: `Failed to send message: ${err.message}` };
|
|
11664
|
+
}
|
|
11665
|
+
}
|
|
10968
11666
|
// ── IDE Integration ────────────────────────────────────
|
|
11667
|
+
// Helper: extract open workspace paths from a VS Code/Cursor/Windsurf storage.json
|
|
11668
|
+
// Storage format: windowsState.lastActiveWindow / openedWindows
|
|
11669
|
+
// Each window has workspaceIdentifier.configURIPath (workspace file) OR folderUri (folder)
|
|
10969
11670
|
case "sys_ide_list_open": {
|
|
10970
|
-
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
|
|
10976
|
-
|
|
10977
|
-
|
|
11671
|
+
let readIdeWorkspaces2 = function(storagePath) {
|
|
11672
|
+
const storage = JSON.parse(readFileSync4(storagePath, "utf-8"));
|
|
11673
|
+
const ws2 = storage["windowsState"] ?? {};
|
|
11674
|
+
const allWindows = [
|
|
11675
|
+
ws2["lastActiveWindow"],
|
|
11676
|
+
...Array.isArray(ws2["openedWindows"]) ? ws2["openedWindows"] : []
|
|
11677
|
+
].filter(Boolean);
|
|
11678
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11679
|
+
const paths = [];
|
|
11680
|
+
for (const w of allWindows) {
|
|
11681
|
+
const configURI = w["workspaceIdentifier"]?.["configURIPath"] ?? "";
|
|
11682
|
+
const folderURI = w["folderUri"] ?? "";
|
|
11683
|
+
for (const uri of [configURI, folderURI]) {
|
|
11684
|
+
if (!uri) continue;
|
|
11685
|
+
let p = uri.replace(/^file:\/\//, "");
|
|
11686
|
+
if (p.endsWith(".code-workspace")) p = dirname(p);
|
|
11687
|
+
if (p && !seen.has(p)) {
|
|
11688
|
+
seen.add(p);
|
|
11689
|
+
paths.push(p);
|
|
10978
11690
|
}
|
|
10979
|
-
|
|
10980
|
-
|
|
10981
|
-
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
10991
|
-
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
11008
|
-
|
|
11691
|
+
}
|
|
11692
|
+
}
|
|
11693
|
+
const activePath = (() => {
|
|
11694
|
+
const aw = ws2["lastActiveWindow"];
|
|
11695
|
+
if (!aw) return null;
|
|
11696
|
+
const configURI = aw["workspaceIdentifier"]?.["configURIPath"] ?? "";
|
|
11697
|
+
const folderURI = aw["folderUri"] ?? "";
|
|
11698
|
+
const raw = configURI || folderURI;
|
|
11699
|
+
if (!raw) return null;
|
|
11700
|
+
let p = raw.replace(/^file:\/\//, "");
|
|
11701
|
+
if (p.endsWith(".code-workspace")) p = dirname(p);
|
|
11702
|
+
return p || null;
|
|
11703
|
+
})();
|
|
11704
|
+
return { active: activePath, all: paths };
|
|
11705
|
+
};
|
|
11706
|
+
var readIdeWorkspaces = readIdeWorkspaces2;
|
|
11707
|
+
return new Promise((resolve5) => {
|
|
11708
|
+
exec5("ps aux", { timeout: 5e3 }, (err, stdout) => {
|
|
11709
|
+
if (err) {
|
|
11710
|
+
resolve5({ success: false, error: err.message });
|
|
11711
|
+
return;
|
|
11712
|
+
}
|
|
11713
|
+
const IDE_PATTERNS = {
|
|
11714
|
+
"Cursor Helper": "Cursor",
|
|
11715
|
+
"Cursor.app": "Cursor",
|
|
11716
|
+
"Code Helper": "VS Code",
|
|
11717
|
+
"Visual Studio Code": "VS Code",
|
|
11718
|
+
"Windsurf Helper": "Windsurf",
|
|
11719
|
+
"Windsurf.app": "Windsurf",
|
|
11720
|
+
"zed": "Zed",
|
|
11721
|
+
"WebStorm": "WebStorm",
|
|
11722
|
+
"IntelliJ IDEA": "IntelliJ IDEA",
|
|
11723
|
+
"PyCharm": "PyCharm",
|
|
11724
|
+
"GoLand": "GoLand"
|
|
11725
|
+
};
|
|
11726
|
+
const running = /* @__PURE__ */ new Set();
|
|
11727
|
+
for (const line of stdout.split("\n")) {
|
|
11728
|
+
for (const [pattern, ideName] of Object.entries(IDE_PATTERNS)) {
|
|
11729
|
+
if (line.includes(pattern) && !line.includes("grep")) {
|
|
11730
|
+
running.add(ideName);
|
|
11009
11731
|
}
|
|
11010
11732
|
}
|
|
11011
|
-
|
|
11012
|
-
|
|
11013
|
-
|
|
11014
|
-
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
|
|
11025
|
-
if (recentFolders.length > 0) {
|
|
11026
|
-
const existing = found[ideName];
|
|
11027
|
-
if (existing) {
|
|
11028
|
-
for (const folder of recentFolders) {
|
|
11029
|
-
if (!existing.workspaces.includes(folder)) {
|
|
11030
|
-
existing.workspaces.push(folder);
|
|
11031
|
-
}
|
|
11032
|
-
}
|
|
11033
|
-
} else if (ides.find((i) => i.ide === ideName) === void 0) {
|
|
11034
|
-
ides.push({ ide: ideName, workspaces: recentFolders });
|
|
11035
|
-
}
|
|
11036
|
-
}
|
|
11037
|
-
} catch {
|
|
11038
|
-
}
|
|
11733
|
+
}
|
|
11734
|
+
const home = homedir4();
|
|
11735
|
+
const storagePaths = [
|
|
11736
|
+
{ ide: "VS Code", path: join5(home, "Library/Application Support/Code/User/globalStorage/storage.json") },
|
|
11737
|
+
{ ide: "Cursor", path: join5(home, "Library/Application Support/Cursor/User/globalStorage/storage.json") },
|
|
11738
|
+
{ ide: "Windsurf", path: join5(home, "Library/Application Support/Windsurf/User/globalStorage/storage.json") }
|
|
11739
|
+
];
|
|
11740
|
+
const ides = [];
|
|
11741
|
+
for (const { ide: ideName, path: storagePath } of storagePaths) {
|
|
11742
|
+
if (!existsSync4(storagePath)) continue;
|
|
11743
|
+
try {
|
|
11744
|
+
const { active, all } = readIdeWorkspaces2(storagePath);
|
|
11745
|
+
ides.push({ ide: ideName, active, workspaces: all, running: running.has(ideName) });
|
|
11746
|
+
} catch {
|
|
11039
11747
|
}
|
|
11040
|
-
resolve5({
|
|
11041
|
-
success: true,
|
|
11042
|
-
data: {
|
|
11043
|
-
ides: ides.length > 0 ? ides : [],
|
|
11044
|
-
count: ides.length,
|
|
11045
|
-
note: ides.length === 0 ? "No IDEs detected. Open VS Code, Cursor, Windsurf, or Zed." : void 0
|
|
11046
|
-
}
|
|
11047
|
-
});
|
|
11048
11748
|
}
|
|
11049
|
-
|
|
11749
|
+
if (running.has("Zed") && !ides.find((i) => i.ide === "Zed")) {
|
|
11750
|
+
ides.push({ ide: "Zed", active: null, workspaces: [], running: true });
|
|
11751
|
+
}
|
|
11752
|
+
resolve5({
|
|
11753
|
+
success: true,
|
|
11754
|
+
data: {
|
|
11755
|
+
ides: ides.length > 0 ? ides : [],
|
|
11756
|
+
count: ides.length,
|
|
11757
|
+
note: ides.length === 0 ? "No IDEs detected." : void 0
|
|
11758
|
+
}
|
|
11759
|
+
});
|
|
11760
|
+
});
|
|
11050
11761
|
});
|
|
11051
11762
|
}
|
|
11052
11763
|
case "sys_ide_get_context": {
|
|
11053
11764
|
const targetIde = params.ide ?? "";
|
|
11054
11765
|
const home = homedir4();
|
|
11055
11766
|
const storageMap = {
|
|
11056
|
-
vscode: join5(home, "Library/Application Support/Code/User/globalStorage/storage.json"),
|
|
11057
|
-
cursor: join5(home, "Library/Application Support/Cursor/User/globalStorage/storage.json"),
|
|
11058
|
-
windsurf: join5(home, "Library/Application Support/Windsurf/User/globalStorage/storage.json")
|
|
11767
|
+
vscode: { label: "VS Code", path: join5(home, "Library/Application Support/Code/User/globalStorage/storage.json") },
|
|
11768
|
+
cursor: { label: "Cursor", path: join5(home, "Library/Application Support/Cursor/User/globalStorage/storage.json") },
|
|
11769
|
+
windsurf: { label: "Windsurf", path: join5(home, "Library/Application Support/Windsurf/User/globalStorage/storage.json") }
|
|
11059
11770
|
};
|
|
11060
11771
|
const ideKey = targetIde.toLowerCase().replace(/[\s-]/g, "");
|
|
11061
|
-
const pathsToTry = ideKey && storageMap[ideKey] ? [{
|
|
11062
|
-
for (const {
|
|
11772
|
+
const pathsToTry = ideKey && storageMap[ideKey] ? [{ key: ideKey, ...storageMap[ideKey] }] : Object.entries(storageMap).map(([key, v]) => ({ key, ...v }));
|
|
11773
|
+
for (const { key: _key, label, path: storagePath } of pathsToTry) {
|
|
11063
11774
|
if (!existsSync4(storagePath)) continue;
|
|
11064
11775
|
try {
|
|
11065
11776
|
const storage = JSON.parse(readFileSync4(storagePath, "utf-8"));
|
|
11066
|
-
const
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
|
|
11070
|
-
|
|
11071
|
-
|
|
11072
|
-
|
|
11777
|
+
const ws2 = storage["windowsState"] ?? {};
|
|
11778
|
+
const allWindows = [
|
|
11779
|
+
ws2["lastActiveWindow"],
|
|
11780
|
+
...Array.isArray(ws2["openedWindows"]) ? ws2["openedWindows"] : []
|
|
11781
|
+
].filter(Boolean);
|
|
11782
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11783
|
+
const workspaces = [];
|
|
11784
|
+
for (const w of allWindows) {
|
|
11785
|
+
const configURI = w["workspaceIdentifier"]?.["configURIPath"] ?? "";
|
|
11786
|
+
const folderURI = w["folderUri"] ?? "";
|
|
11787
|
+
for (const uri of [configURI, folderURI]) {
|
|
11788
|
+
if (!uri) continue;
|
|
11789
|
+
let p = uri.replace(/^file:\/\//, "");
|
|
11790
|
+
if (p.endsWith(".code-workspace")) p = dirname(p);
|
|
11791
|
+
if (p && !seen.has(p)) {
|
|
11792
|
+
seen.add(p);
|
|
11793
|
+
workspaces.push(p);
|
|
11794
|
+
}
|
|
11795
|
+
}
|
|
11796
|
+
}
|
|
11797
|
+
const activeWindow = ws2["lastActiveWindow"];
|
|
11798
|
+
const activeConfigURI = activeWindow?.["workspaceIdentifier"]?.["configURIPath"] ?? "";
|
|
11799
|
+
const activeFolderURI = activeWindow?.["folderUri"] ?? "";
|
|
11800
|
+
let activeWorkspace = (activeConfigURI || activeFolderURI).replace(/^file:\/\//, "");
|
|
11801
|
+
if (activeWorkspace.endsWith(".code-workspace")) activeWorkspace = dirname(activeWorkspace);
|
|
11073
11802
|
return {
|
|
11074
11803
|
success: true,
|
|
11075
11804
|
data: {
|
|
11076
|
-
ide,
|
|
11077
|
-
activeWorkspace,
|
|
11078
|
-
|
|
11079
|
-
recentFiles
|
|
11805
|
+
ide: label,
|
|
11806
|
+
activeWorkspace: activeWorkspace || null,
|
|
11807
|
+
openWorkspaces: workspaces
|
|
11080
11808
|
}
|
|
11081
11809
|
};
|
|
11082
11810
|
} catch {
|
|
@@ -11084,7 +11812,7 @@ print(result.stdout[:5000])
|
|
|
11084
11812
|
}
|
|
11085
11813
|
return {
|
|
11086
11814
|
success: false,
|
|
11087
|
-
error: "No IDE context found. Make sure VS Code, Cursor, or Windsurf has been
|
|
11815
|
+
error: "No IDE context found. Make sure VS Code, Cursor, or Windsurf has been opened."
|
|
11088
11816
|
};
|
|
11089
11817
|
}
|
|
11090
11818
|
case "sys_ide_run_terminal": {
|
|
@@ -11101,10 +11829,17 @@ print(result.stdout[:5000])
|
|
|
11101
11829
|
if (!existsSync4(storagePath)) continue;
|
|
11102
11830
|
try {
|
|
11103
11831
|
const storage = JSON.parse(readFileSync4(storagePath, "utf-8"));
|
|
11104
|
-
const
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
11832
|
+
const ws2 = storage["windowsState"] ?? {};
|
|
11833
|
+
const aw = ws2["lastActiveWindow"];
|
|
11834
|
+
if (!aw) continue;
|
|
11835
|
+
const configURI = aw["workspaceIdentifier"]?.["configURIPath"] ?? "";
|
|
11836
|
+
const folderURI = aw["folderUri"] ?? "";
|
|
11837
|
+
const raw = configURI || folderURI;
|
|
11838
|
+
if (!raw) continue;
|
|
11839
|
+
let p = raw.replace(/^file:\/\//, "");
|
|
11840
|
+
if (p.endsWith(".code-workspace")) p = dirname(p);
|
|
11841
|
+
if (p) {
|
|
11842
|
+
cwd = p;
|
|
11108
11843
|
break;
|
|
11109
11844
|
}
|
|
11110
11845
|
} catch {
|
|
@@ -11179,10 +11914,17 @@ STDERR: ${stderr}` : "")).slice(0, 1e4),
|
|
|
11179
11914
|
if (!existsSync4(storagePath)) continue;
|
|
11180
11915
|
try {
|
|
11181
11916
|
const storage = JSON.parse(readFileSync4(storagePath, "utf-8"));
|
|
11182
|
-
const
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11917
|
+
const ws2 = storage["windowsState"] ?? {};
|
|
11918
|
+
const aw = ws2["lastActiveWindow"];
|
|
11919
|
+
if (!aw) continue;
|
|
11920
|
+
const configURI = aw["workspaceIdentifier"]?.["configURIPath"] ?? "";
|
|
11921
|
+
const folderURI = aw["folderUri"] ?? "";
|
|
11922
|
+
const raw = configURI || folderURI;
|
|
11923
|
+
if (!raw) continue;
|
|
11924
|
+
let p = raw.replace(/^file:\/\//, "");
|
|
11925
|
+
if (p.endsWith(".code-workspace")) p = dirname(p);
|
|
11926
|
+
if (p) {
|
|
11927
|
+
cwd = p;
|
|
11186
11928
|
break;
|
|
11187
11929
|
}
|
|
11188
11930
|
} catch {
|
|
@@ -11549,6 +12291,12 @@ async function probeCapabilities() {
|
|
|
11549
12291
|
}
|
|
11550
12292
|
}
|
|
11551
12293
|
}
|
|
12294
|
+
tools.add("llm_infer");
|
|
12295
|
+
tools.add("llm_list_local");
|
|
12296
|
+
if (adapter.platform === "macos" || adapter.platform === "windows") {
|
|
12297
|
+
tools.add("llm_browser_claude");
|
|
12298
|
+
tools.add("llm_browser_chatgpt");
|
|
12299
|
+
}
|
|
11552
12300
|
const cap = { available, unavailable, tools: Array.from(tools) };
|
|
11553
12301
|
console.log(
|
|
11554
12302
|
` \u2705 Available: ${available.join(", ") || "all adapter tools"}`
|
|
@@ -12407,7 +13155,7 @@ process.on("exit", () => {
|
|
|
12407
13155
|
});
|
|
12408
13156
|
console.log("");
|
|
12409
13157
|
console.log(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
12410
|
-
console.log(` \u2551 Pulso ${platformName} Companion v0.4.
|
|
13158
|
+
console.log(` \u2551 Pulso ${platformName} Companion v0.4.5 \u2551`);
|
|
12411
13159
|
console.log(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
12412
13160
|
console.log("");
|
|
12413
13161
|
console.log(` Platform: ${currentPlatform}`);
|