@pulso/companion 0.4.3 → 0.4.4
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 +594 -45
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -346,7 +346,7 @@ var require_dist = __commonJS({
|
|
|
346
346
|
|
|
347
347
|
// src/index.ts
|
|
348
348
|
import WebSocket from "ws";
|
|
349
|
-
import { exec as exec5, execSync as execSync3 } from "child_process";
|
|
349
|
+
import { exec as exec5, execSync as execSync3, spawn } from "child_process";
|
|
350
350
|
import { createRequire } from "module";
|
|
351
351
|
import { createInterface } from "readline/promises";
|
|
352
352
|
import { stdin as input, stdout as output } from "process";
|
|
@@ -1835,6 +1835,57 @@ var LinuxAdapter = class {
|
|
|
1835
1835
|
};
|
|
1836
1836
|
}
|
|
1837
1837
|
}
|
|
1838
|
+
async browserListProfiles() {
|
|
1839
|
+
const home = process.env.HOME || "";
|
|
1840
|
+
const browserPaths = [
|
|
1841
|
+
{ browser: "Google Chrome", dir: `${home}/.config/google-chrome` },
|
|
1842
|
+
{ browser: "Chromium", dir: `${home}/.config/chromium` },
|
|
1843
|
+
{ browser: "Microsoft Edge", dir: `${home}/.config/microsoft-edge` },
|
|
1844
|
+
{ browser: "Brave Browser", dir: `${home}/.config/BraveSoftware/Brave-Browser` }
|
|
1845
|
+
];
|
|
1846
|
+
const profiles = [];
|
|
1847
|
+
for (const { browser, dir } of browserPaths) {
|
|
1848
|
+
if (!existsSync(dir)) continue;
|
|
1849
|
+
let entries = [];
|
|
1850
|
+
try {
|
|
1851
|
+
entries = readdirSync(dir);
|
|
1852
|
+
} catch {
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
for (const entry of entries) {
|
|
1856
|
+
if (entry !== "Default" && !entry.startsWith("Profile ")) continue;
|
|
1857
|
+
const prefsPath = `${dir}/${entry}/Preferences`;
|
|
1858
|
+
if (!existsSync(prefsPath)) continue;
|
|
1859
|
+
try {
|
|
1860
|
+
const prefs = JSON.parse(readFileSync(prefsPath, "utf-8"));
|
|
1861
|
+
profiles.push({
|
|
1862
|
+
browser,
|
|
1863
|
+
profileDir: `${dir}/${entry}`,
|
|
1864
|
+
name: prefs.profile?.name || entry,
|
|
1865
|
+
email: prefs.account_info?.[0]?.email,
|
|
1866
|
+
isDefault: entry === "Default"
|
|
1867
|
+
});
|
|
1868
|
+
} catch {
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
const ffDir = `${home}/.mozilla/firefox`;
|
|
1873
|
+
if (existsSync(ffDir)) {
|
|
1874
|
+
try {
|
|
1875
|
+
for (const entry of readdirSync(ffDir)) {
|
|
1876
|
+
if (!existsSync(`${ffDir}/${entry}/prefs.js`)) continue;
|
|
1877
|
+
profiles.push({
|
|
1878
|
+
browser: "Firefox",
|
|
1879
|
+
profileDir: `${ffDir}/${entry}`,
|
|
1880
|
+
name: entry.replace(/^[a-z0-9]+\./, ""),
|
|
1881
|
+
isDefault: entry.includes("default")
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
} catch {
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
return { success: true, data: { profiles, total: profiles.length } };
|
|
1888
|
+
}
|
|
1838
1889
|
/* ══════════════════════════════════════════════════════════
|
|
1839
1890
|
* Productivity: Calendar
|
|
1840
1891
|
*
|
|
@@ -3443,6 +3494,18 @@ function runSwift(code, timeout = 1e4) {
|
|
|
3443
3494
|
child.stdin?.end();
|
|
3444
3495
|
});
|
|
3445
3496
|
}
|
|
3497
|
+
async function hasScreenRecordingPermission() {
|
|
3498
|
+
try {
|
|
3499
|
+
const out = await runSwift(`
|
|
3500
|
+
import Cocoa
|
|
3501
|
+
import CoreGraphics
|
|
3502
|
+
print(CGPreflightScreenCaptureAccess() ? "granted" : "denied")
|
|
3503
|
+
`, 6e3);
|
|
3504
|
+
return out.trim().toLowerCase() === "granted";
|
|
3505
|
+
} catch {
|
|
3506
|
+
return false;
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3446
3509
|
function safePath2(relative) {
|
|
3447
3510
|
const full = resolve2(HOME2, relative);
|
|
3448
3511
|
if (!full.startsWith(HOME2)) return null;
|
|
@@ -3651,6 +3714,14 @@ var MacOSAdapter = class {
|
|
|
3651
3714
|
* Screenshots & Computer Use
|
|
3652
3715
|
* ══════════════════════════════════════════════════════════ */
|
|
3653
3716
|
async screenshot() {
|
|
3717
|
+
const allowed = await hasScreenRecordingPermission();
|
|
3718
|
+
if (!allowed) {
|
|
3719
|
+
return {
|
|
3720
|
+
image: "",
|
|
3721
|
+
format: "jpeg",
|
|
3722
|
+
note: "Screen Recording permission is not granted for this Companion binary. Enable it in System Settings -> Privacy & Security -> Screen Recording, then reopen Pulso Companion."
|
|
3723
|
+
};
|
|
3724
|
+
}
|
|
3654
3725
|
const ts = Date.now();
|
|
3655
3726
|
const pngPath = `/tmp/pulso-ss-${ts}.png`;
|
|
3656
3727
|
const jpgPath = `/tmp/pulso-ss-${ts}.jpg`;
|
|
@@ -4065,6 +4136,89 @@ end tell`);
|
|
|
4065
4136
|
return { success: false, error: `JS execution failed: ${err.message}` };
|
|
4066
4137
|
}
|
|
4067
4138
|
}
|
|
4139
|
+
async browserListProfiles() {
|
|
4140
|
+
const os = homedir2();
|
|
4141
|
+
const browserPaths = [
|
|
4142
|
+
{ browser: "Google Chrome", dir: `${os}/Library/Application Support/Google/Chrome` },
|
|
4143
|
+
{ browser: "Google Chrome Beta", dir: `${os}/Library/Application Support/Google/Chrome Beta` },
|
|
4144
|
+
{ browser: "Google Chrome Dev", dir: `${os}/Library/Application Support/Google/Chrome Dev` },
|
|
4145
|
+
{ browser: "Microsoft Edge", dir: `${os}/Library/Application Support/Microsoft Edge` },
|
|
4146
|
+
{ browser: "Brave Browser", dir: `${os}/Library/Application Support/BraveSoftware/Brave-Browser` },
|
|
4147
|
+
{ browser: "Opera", dir: `${os}/Library/Application Support/com.operasoftware.Opera` },
|
|
4148
|
+
{ browser: "Vivaldi", dir: `${os}/Library/Application Support/Vivaldi` },
|
|
4149
|
+
{ browser: "Arc", dir: `${os}/Library/Application Support/Arc/User Data` },
|
|
4150
|
+
{ browser: "Chromium", dir: `${os}/Library/Application Support/Chromium` }
|
|
4151
|
+
];
|
|
4152
|
+
const profiles = [];
|
|
4153
|
+
for (const { browser, dir } of browserPaths) {
|
|
4154
|
+
if (!existsSync2(dir)) continue;
|
|
4155
|
+
let dirs = [];
|
|
4156
|
+
try {
|
|
4157
|
+
dirs = readdirSync2(dir);
|
|
4158
|
+
} catch {
|
|
4159
|
+
continue;
|
|
4160
|
+
}
|
|
4161
|
+
for (const entry of dirs) {
|
|
4162
|
+
if (entry !== "Default" && !entry.startsWith("Profile ")) continue;
|
|
4163
|
+
const prefsPath = `${dir}/${entry}/Preferences`;
|
|
4164
|
+
if (!existsSync2(prefsPath)) continue;
|
|
4165
|
+
try {
|
|
4166
|
+
const raw = readFileSync2(prefsPath, "utf-8");
|
|
4167
|
+
const prefs = JSON.parse(raw);
|
|
4168
|
+
const name = prefs.profile?.name || entry;
|
|
4169
|
+
const email = prefs.account_info?.[0]?.email;
|
|
4170
|
+
profiles.push({
|
|
4171
|
+
browser,
|
|
4172
|
+
profileDir: `${dir}/${entry}`,
|
|
4173
|
+
name,
|
|
4174
|
+
email,
|
|
4175
|
+
isDefault: entry === "Default"
|
|
4176
|
+
});
|
|
4177
|
+
} catch {
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
const firefoxPaths = [
|
|
4182
|
+
`${os}/Library/Application Support/Firefox/Profiles`,
|
|
4183
|
+
`${os}/Library/Application Support/Firefox Developer Edition/Profiles`
|
|
4184
|
+
];
|
|
4185
|
+
for (const ffDir of firefoxPaths) {
|
|
4186
|
+
if (!existsSync2(ffDir)) continue;
|
|
4187
|
+
try {
|
|
4188
|
+
const dirs = readdirSync2(ffDir);
|
|
4189
|
+
const browserName = ffDir.includes("Developer") ? "Firefox Developer Edition" : "Firefox";
|
|
4190
|
+
for (const entry of dirs) {
|
|
4191
|
+
const userJs = `${ffDir}/${entry}/user.js`;
|
|
4192
|
+
const prefsJs = `${ffDir}/${entry}/prefs.js`;
|
|
4193
|
+
if (!existsSync2(prefsJs) && !existsSync2(userJs)) continue;
|
|
4194
|
+
profiles.push({
|
|
4195
|
+
browser: browserName,
|
|
4196
|
+
profileDir: `${ffDir}/${entry}`,
|
|
4197
|
+
name: entry.replace(/^[a-z0-9]+\./, ""),
|
|
4198
|
+
// strip hash prefix
|
|
4199
|
+
isDefault: entry.includes("default")
|
|
4200
|
+
});
|
|
4201
|
+
}
|
|
4202
|
+
} catch {
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
let running = [];
|
|
4206
|
+
try {
|
|
4207
|
+
const ps = await runShell2("ps aux | grep -E '(Chrome|Edge|Brave|Firefox|Opera|Vivaldi|Arc)' | grep -v grep | awk '{print $11}'");
|
|
4208
|
+
running = ps.split("\n").filter(Boolean);
|
|
4209
|
+
} catch {
|
|
4210
|
+
}
|
|
4211
|
+
return {
|
|
4212
|
+
success: true,
|
|
4213
|
+
data: {
|
|
4214
|
+
profiles: profiles.map((p) => ({
|
|
4215
|
+
...p,
|
|
4216
|
+
isRunning: running.some((r) => r.toLowerCase().includes(p.browser.toLowerCase().replace(/ /g, "")))
|
|
4217
|
+
})),
|
|
4218
|
+
total: profiles.length
|
|
4219
|
+
}
|
|
4220
|
+
};
|
|
4221
|
+
}
|
|
4068
4222
|
/* ══════════════════════════════════════════════════════════
|
|
4069
4223
|
* Productivity: Calendar
|
|
4070
4224
|
* ══════════════════════════════════════════════════════════ */
|
|
@@ -6099,6 +6253,47 @@ foreach ($w in $windows) {
|
|
|
6099
6253
|
};
|
|
6100
6254
|
}
|
|
6101
6255
|
}
|
|
6256
|
+
async browserListProfiles() {
|
|
6257
|
+
try {
|
|
6258
|
+
const userProfile = process.env.LOCALAPPDATA || process.env.APPDATA || "";
|
|
6259
|
+
const browserPaths = [
|
|
6260
|
+
{ browser: "Google Chrome", dir: `${userProfile}\\Google\\Chrome\\User Data` },
|
|
6261
|
+
{ browser: "Microsoft Edge", dir: `${userProfile}\\Microsoft\\Edge\\User Data` },
|
|
6262
|
+
{ browser: "Brave Browser", dir: `${userProfile}\\BraveSoftware\\Brave-Browser\\User Data` },
|
|
6263
|
+
{ browser: "Vivaldi", dir: `${userProfile}\\Vivaldi\\User Data` },
|
|
6264
|
+
{ browser: "Opera", dir: `${userProfile}\\Opera Software\\Opera Stable` }
|
|
6265
|
+
];
|
|
6266
|
+
const profiles = [];
|
|
6267
|
+
for (const { browser, dir } of browserPaths) {
|
|
6268
|
+
if (!existsSync3(dir)) continue;
|
|
6269
|
+
let entries = [];
|
|
6270
|
+
try {
|
|
6271
|
+
entries = readdirSync3(dir);
|
|
6272
|
+
} catch {
|
|
6273
|
+
continue;
|
|
6274
|
+
}
|
|
6275
|
+
for (const entry of entries) {
|
|
6276
|
+
if (entry !== "Default" && !entry.startsWith("Profile ")) continue;
|
|
6277
|
+
const prefsPath = `${dir}\\${entry}\\Preferences`;
|
|
6278
|
+
if (!existsSync3(prefsPath)) continue;
|
|
6279
|
+
try {
|
|
6280
|
+
const prefs = JSON.parse(readFileSync3(prefsPath, "utf-8"));
|
|
6281
|
+
profiles.push({
|
|
6282
|
+
browser,
|
|
6283
|
+
profileDir: `${dir}\\${entry}`,
|
|
6284
|
+
name: prefs.profile?.name || entry,
|
|
6285
|
+
email: prefs.account_info?.[0]?.email,
|
|
6286
|
+
isDefault: entry === "Default"
|
|
6287
|
+
});
|
|
6288
|
+
} catch {
|
|
6289
|
+
}
|
|
6290
|
+
}
|
|
6291
|
+
}
|
|
6292
|
+
return { success: true, data: { profiles, total: profiles.length } };
|
|
6293
|
+
} catch (err) {
|
|
6294
|
+
return { success: false, error: err.message };
|
|
6295
|
+
}
|
|
6296
|
+
}
|
|
6102
6297
|
/* ══════════════════════════════════════════════════════════
|
|
6103
6298
|
* Productivity: Calendar
|
|
6104
6299
|
*
|
|
@@ -8755,6 +8950,61 @@ var WAKE_WORD_LOCAL_STT_BUDGET_MS_RAW = process.env.PULSO_WAKE_WORD_LOCAL_STT_BU
|
|
|
8755
8950
|
var WS_BASE = API_URL.replace("https://", "wss://").replace("http://", "ws://") + "/ws/companion";
|
|
8756
8951
|
var HOME4 = homedir4();
|
|
8757
8952
|
var RECONNECT_DELAY = 5e3;
|
|
8953
|
+
var COMPANION_LOCK_FILE = join5(CREDENTIALS_DIR, "companion.lock.json");
|
|
8954
|
+
function releaseCompanionLock() {
|
|
8955
|
+
try {
|
|
8956
|
+
if (!existsSync4(COMPANION_LOCK_FILE)) return;
|
|
8957
|
+
const raw = readFileSync4(COMPANION_LOCK_FILE, "utf-8");
|
|
8958
|
+
const parsed = JSON.parse(raw);
|
|
8959
|
+
if (parsed?.pid === process.pid) {
|
|
8960
|
+
unlinkSync5(COMPANION_LOCK_FILE);
|
|
8961
|
+
}
|
|
8962
|
+
} catch {
|
|
8963
|
+
}
|
|
8964
|
+
}
|
|
8965
|
+
function acquireCompanionLock() {
|
|
8966
|
+
try {
|
|
8967
|
+
if (!existsSync4(CREDENTIALS_DIR)) {
|
|
8968
|
+
mkdirSync3(CREDENTIALS_DIR, { recursive: true });
|
|
8969
|
+
}
|
|
8970
|
+
if (existsSync4(COMPANION_LOCK_FILE)) {
|
|
8971
|
+
try {
|
|
8972
|
+
const raw = readFileSync4(COMPANION_LOCK_FILE, "utf-8");
|
|
8973
|
+
const parsed = JSON.parse(raw);
|
|
8974
|
+
const existingPid = Number(parsed?.pid || 0);
|
|
8975
|
+
if (existingPid > 1 && existingPid !== process.pid) {
|
|
8976
|
+
try {
|
|
8977
|
+
process.kill(existingPid, 0);
|
|
8978
|
+
console.log("");
|
|
8979
|
+
console.log(" \u26A0\uFE0F Another Pulso Companion instance is already running.");
|
|
8980
|
+
console.log(` PID: ${existingPid}${parsed?.startedAt ? ` (${parsed.startedAt})` : ""}`);
|
|
8981
|
+
console.log(" Exiting this instance to avoid command collisions.\n");
|
|
8982
|
+
process.exit(0);
|
|
8983
|
+
} catch {
|
|
8984
|
+
}
|
|
8985
|
+
}
|
|
8986
|
+
} catch {
|
|
8987
|
+
}
|
|
8988
|
+
}
|
|
8989
|
+
writeFileSync5(
|
|
8990
|
+
COMPANION_LOCK_FILE,
|
|
8991
|
+
JSON.stringify(
|
|
8992
|
+
{
|
|
8993
|
+
pid: process.pid,
|
|
8994
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8995
|
+
argv: process.argv.slice(2)
|
|
8996
|
+
},
|
|
8997
|
+
null,
|
|
8998
|
+
2
|
|
8999
|
+
),
|
|
9000
|
+
"utf-8"
|
|
9001
|
+
);
|
|
9002
|
+
if (platform() !== "win32") {
|
|
9003
|
+
chmodSync(COMPANION_LOCK_FILE, 384);
|
|
9004
|
+
}
|
|
9005
|
+
} catch {
|
|
9006
|
+
}
|
|
9007
|
+
}
|
|
8758
9008
|
async function requestWsTicket() {
|
|
8759
9009
|
try {
|
|
8760
9010
|
const res = await fetch(`${API_URL}/ws/ticket`, {
|
|
@@ -8841,6 +9091,18 @@ function runSwift2(code, timeout = 1e4) {
|
|
|
8841
9091
|
child.stdin?.end();
|
|
8842
9092
|
});
|
|
8843
9093
|
}
|
|
9094
|
+
async function hasScreenRecordingPermission2() {
|
|
9095
|
+
try {
|
|
9096
|
+
const out = await runSwift2(`
|
|
9097
|
+
import Cocoa
|
|
9098
|
+
import CoreGraphics
|
|
9099
|
+
print(CGPreflightScreenCaptureAccess() ? "granted" : "denied")
|
|
9100
|
+
`, 6e3);
|
|
9101
|
+
return out.trim().toLowerCase() === "granted";
|
|
9102
|
+
} catch {
|
|
9103
|
+
return false;
|
|
9104
|
+
}
|
|
9105
|
+
}
|
|
8844
9106
|
var SETUP_DONE_FILE = join5(HOME4, ".pulso-companion-setup");
|
|
8845
9107
|
async function setupPermissions() {
|
|
8846
9108
|
const isFirstRun = !existsSync4(SETUP_DONE_FILE);
|
|
@@ -8944,6 +9206,39 @@ var ADAPTER_COMMANDS = {
|
|
|
8944
9206
|
sys_open_url: (a, p) => a.openUrl(p.url),
|
|
8945
9207
|
sys_speak: (a, p) => a.speak(p.text, p.voice),
|
|
8946
9208
|
sys_notification: (a, p) => a.notification(p.title, p.message),
|
|
9209
|
+
sys_dialog_action: async (_, p) => {
|
|
9210
|
+
const buttonName = p.button;
|
|
9211
|
+
const procName = p.procName ?? activeDialog?.procName;
|
|
9212
|
+
if (!procName || !buttonName) {
|
|
9213
|
+
return { success: false, error: "No active dialog or button not specified" };
|
|
9214
|
+
}
|
|
9215
|
+
const script = `
|
|
9216
|
+
tell application "System Events"
|
|
9217
|
+
tell process "${procName.replace(/"/g, '\\"')}"
|
|
9218
|
+
set targetWindow to window 1
|
|
9219
|
+
repeat with w in windows
|
|
9220
|
+
try
|
|
9221
|
+
if role of w is "AXSheet" or role of w is "AXDialog" then
|
|
9222
|
+
set targetWindow to w
|
|
9223
|
+
exit repeat
|
|
9224
|
+
end if
|
|
9225
|
+
end try
|
|
9226
|
+
end repeat
|
|
9227
|
+
tell targetWindow
|
|
9228
|
+
click button "${buttonName.replace(/"/g, '\\"')}"
|
|
9229
|
+
end tell
|
|
9230
|
+
end tell
|
|
9231
|
+
end tell`;
|
|
9232
|
+
try {
|
|
9233
|
+
await runAppleScript2(script);
|
|
9234
|
+
console.log(` \u2713 Dialog action: clicked "${buttonName}" in [${procName}]`);
|
|
9235
|
+
activeDialog = null;
|
|
9236
|
+
lastDialogSignature = null;
|
|
9237
|
+
return { success: true, clicked: buttonName };
|
|
9238
|
+
} catch (e) {
|
|
9239
|
+
return { success: false, error: e.message };
|
|
9240
|
+
}
|
|
9241
|
+
},
|
|
8947
9242
|
sys_clipboard_read: (a, _) => a.clipboardRead(),
|
|
8948
9243
|
sys_clipboard_write: (a, p) => a.clipboardWrite(p.text),
|
|
8949
9244
|
sys_screenshot: (a, _) => a.screenshot(),
|
|
@@ -8964,6 +9259,7 @@ var ADAPTER_COMMANDS = {
|
|
|
8964
9259
|
sys_browser_new_tab: (a, p) => a.browserNewTab(p.url, p.browser),
|
|
8965
9260
|
sys_browser_read_page: (a, p) => a.browserReadPage(p.browser, Number(p.maxLength) || void 0),
|
|
8966
9261
|
sys_browser_execute_js: (a, p) => a.browserExecuteJs(p.code, p.browser),
|
|
9262
|
+
sys_browser_list_profiles: (a, _) => a.browserListProfiles(),
|
|
8967
9263
|
sys_calendar_list: (a, p) => a.calendarList(Number(p.days) || void 0),
|
|
8968
9264
|
sys_calendar_create: (a, p) => a.calendarCreate(
|
|
8969
9265
|
p.title ?? p.summary,
|
|
@@ -9024,7 +9320,16 @@ var ADAPTER_COMMANDS = {
|
|
|
9024
9320
|
return level !== void 0 ? a.setBrightness(Number(level)) : a.getBrightness();
|
|
9025
9321
|
}
|
|
9026
9322
|
};
|
|
9027
|
-
|
|
9323
|
+
var claudePipeQueue = Promise.resolve();
|
|
9324
|
+
function runClaudePipeSerial(task) {
|
|
9325
|
+
const run = claudePipeQueue.then(task, task);
|
|
9326
|
+
claudePipeQueue = run.then(
|
|
9327
|
+
() => void 0,
|
|
9328
|
+
() => void 0
|
|
9329
|
+
);
|
|
9330
|
+
return run;
|
|
9331
|
+
}
|
|
9332
|
+
async function handleCommand(command, params, streamCb) {
|
|
9028
9333
|
try {
|
|
9029
9334
|
if (command === "ollama_detect") {
|
|
9030
9335
|
try {
|
|
@@ -9072,6 +9377,14 @@ async function handleCommand(command, params) {
|
|
|
9072
9377
|
const rh = params.height;
|
|
9073
9378
|
if (rx == null || ry == null || rw == null || rh == null)
|
|
9074
9379
|
return { success: false, error: "Missing x, y, width, or height" };
|
|
9380
|
+
const screenPermitted = await hasScreenRecordingPermission2();
|
|
9381
|
+
if (!screenPermitted) {
|
|
9382
|
+
return {
|
|
9383
|
+
success: false,
|
|
9384
|
+
error: "Screen Recording permission is not granted. Enable Pulso Companion in System Settings -> Privacy & Security -> Screen Recording, then reopen the app.",
|
|
9385
|
+
errorCode: "SCREEN_PERMISSION_REQUIRED"
|
|
9386
|
+
};
|
|
9387
|
+
}
|
|
9075
9388
|
const ts2 = Date.now();
|
|
9076
9389
|
const regPath = `/tmp/pulso-ss-region-${ts2}.png`;
|
|
9077
9390
|
const regJpg = `/tmp/pulso-ss-region-${ts2}.jpg`;
|
|
@@ -9722,71 +10035,198 @@ print(result.stdout[:5000])
|
|
|
9722
10035
|
const model = params.model;
|
|
9723
10036
|
const maxTurns = params.max_turns;
|
|
9724
10037
|
const systemPrompt = params.system_prompt;
|
|
9725
|
-
const
|
|
9726
|
-
const
|
|
9727
|
-
const
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
10038
|
+
const effort = params.effort || "low";
|
|
10039
|
+
const outputFormat = params.output_format || "text";
|
|
10040
|
+
const timeout = Number(params.timeout) || 18e4;
|
|
10041
|
+
const home = process.env.HOME || "";
|
|
10042
|
+
const claudePaths = [
|
|
10043
|
+
`${home}/.local/bin/claude`,
|
|
10044
|
+
`${home}/.local/share/claude/versions/latest/claude`,
|
|
10045
|
+
"/usr/local/bin/claude",
|
|
10046
|
+
"/opt/homebrew/bin/claude"
|
|
10047
|
+
];
|
|
10048
|
+
let claudeBin = "claude";
|
|
10049
|
+
for (const p of claudePaths) {
|
|
10050
|
+
try {
|
|
10051
|
+
execSync3(`test -x "${p}"`, { stdio: "ignore" });
|
|
10052
|
+
claudeBin = p;
|
|
10053
|
+
break;
|
|
10054
|
+
} catch {
|
|
10055
|
+
}
|
|
10056
|
+
}
|
|
10057
|
+
const useStreamJson = Boolean(streamCb);
|
|
10058
|
+
const effectiveOutputFormat = useStreamJson ? "stream-json" : outputFormat;
|
|
10059
|
+
const args = ["-p", "--output-format", effectiveOutputFormat];
|
|
10060
|
+
if (useStreamJson) {
|
|
10061
|
+
args.push("--verbose", "--include-partial-messages");
|
|
10062
|
+
}
|
|
10063
|
+
args.push("--tools", "");
|
|
10064
|
+
if (systemPrompt && systemPrompt.trim()) {
|
|
10065
|
+
args.push("--system-prompt", systemPrompt.trim());
|
|
10066
|
+
}
|
|
10067
|
+
if (effort) args.push("--effort", effort);
|
|
10068
|
+
if (model) args.push("--model", model);
|
|
10069
|
+
if (maxTurns) args.push("--max-turns", String(maxTurns));
|
|
10070
|
+
const promptInput = prompt;
|
|
10071
|
+
return runClaudePipeSerial(
|
|
10072
|
+
() => new Promise((resolve5) => {
|
|
10073
|
+
let stdout = "";
|
|
10074
|
+
let stderr = "";
|
|
10075
|
+
let streamBuffer = "";
|
|
10076
|
+
let streamedResponse = "";
|
|
10077
|
+
let finalResultText = "";
|
|
10078
|
+
let assistantSnapshotText = "";
|
|
10079
|
+
const processJsonLine = (line) => {
|
|
10080
|
+
const trimmed = line.trim();
|
|
10081
|
+
if (!trimmed) return;
|
|
10082
|
+
let evt = null;
|
|
9742
10083
|
try {
|
|
9743
|
-
|
|
9744
|
-
|
|
10084
|
+
evt = JSON.parse(trimmed);
|
|
10085
|
+
} catch {
|
|
10086
|
+
return;
|
|
10087
|
+
}
|
|
10088
|
+
const deltaType = evt?.event?.delta?.type;
|
|
10089
|
+
if (evt?.type === "stream_event" && deltaType === "text_delta") {
|
|
10090
|
+
const deltaText = String(evt?.event?.delta?.text ?? "");
|
|
10091
|
+
if (deltaText) {
|
|
10092
|
+
streamedResponse += deltaText;
|
|
10093
|
+
if (streamCb) streamCb(deltaText);
|
|
10094
|
+
}
|
|
10095
|
+
return;
|
|
10096
|
+
}
|
|
10097
|
+
if (evt?.type === "result" && typeof evt?.result === "string") {
|
|
10098
|
+
finalResultText = evt.result;
|
|
10099
|
+
return;
|
|
10100
|
+
}
|
|
10101
|
+
if (evt?.type === "assistant" && Array.isArray(evt?.message?.content)) {
|
|
10102
|
+
const textBlocks = evt.message.content.filter((p) => p?.type === "text" && typeof p?.text === "string").map((p) => p.text);
|
|
10103
|
+
if (textBlocks.length > 0) {
|
|
10104
|
+
assistantSnapshotText = textBlocks.join("");
|
|
10105
|
+
}
|
|
10106
|
+
}
|
|
10107
|
+
};
|
|
10108
|
+
const childEnv = {
|
|
10109
|
+
...process.env,
|
|
10110
|
+
PATH: `${home}/.local/bin:${process.env.PATH}`
|
|
10111
|
+
};
|
|
10112
|
+
delete childEnv.CLAUDECODE;
|
|
10113
|
+
delete childEnv.CLAUDE_CODE;
|
|
10114
|
+
const child = spawn(claudeBin, args, {
|
|
10115
|
+
env: childEnv,
|
|
10116
|
+
timeout,
|
|
10117
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10118
|
+
});
|
|
10119
|
+
child.stdout.on("data", (chunk) => {
|
|
10120
|
+
const text = chunk.toString();
|
|
10121
|
+
stdout += text;
|
|
10122
|
+
if (useStreamJson) {
|
|
10123
|
+
streamBuffer += text;
|
|
10124
|
+
const lines = streamBuffer.split(/\r?\n/);
|
|
10125
|
+
streamBuffer = lines.pop() || "";
|
|
10126
|
+
for (const ln of lines) {
|
|
10127
|
+
processJsonLine(ln);
|
|
10128
|
+
}
|
|
10129
|
+
} else if (streamCb) {
|
|
10130
|
+
streamCb(text);
|
|
10131
|
+
}
|
|
10132
|
+
});
|
|
10133
|
+
child.stderr.on("data", (chunk) => {
|
|
10134
|
+
stderr += chunk.toString();
|
|
10135
|
+
});
|
|
10136
|
+
child.on("close", (code) => {
|
|
10137
|
+
if (code !== 0) {
|
|
10138
|
+
const detail = (stderr.trim() || stdout.trim() || "Unknown error").slice(0, 500);
|
|
10139
|
+
const errorCode = detail.toLowerCase().includes("out of extra usage") ? "CLAUDE_USAGE_LIMIT" : "CLAUDE_PIPE_FAILED";
|
|
10140
|
+
resolve5({
|
|
10141
|
+
success: false,
|
|
10142
|
+
error: `Claude pipe error (exit ${code}): ${detail}`,
|
|
10143
|
+
errorCode
|
|
10144
|
+
});
|
|
10145
|
+
return;
|
|
10146
|
+
}
|
|
10147
|
+
try {
|
|
10148
|
+
if (effectiveOutputFormat === "stream-json") {
|
|
10149
|
+
if (streamBuffer.trim()) {
|
|
10150
|
+
processJsonLine(streamBuffer);
|
|
10151
|
+
}
|
|
10152
|
+
const response = (finalResultText || streamedResponse || assistantSnapshotText || "").trim();
|
|
9745
10153
|
resolve5({
|
|
9746
10154
|
success: true,
|
|
9747
10155
|
data: {
|
|
9748
|
-
response
|
|
9749
|
-
session_id: parsed.session_id,
|
|
9750
|
-
cost_usd: parsed.total_cost_usd ?? 0,
|
|
9751
|
-
duration_ms: parsed.duration_ms,
|
|
9752
|
-
num_turns: parsed.num_turns,
|
|
10156
|
+
response,
|
|
9753
10157
|
model: model || "default",
|
|
9754
10158
|
via: "claude-max-subscription"
|
|
9755
10159
|
}
|
|
9756
10160
|
});
|
|
9757
|
-
|
|
10161
|
+
return;
|
|
10162
|
+
}
|
|
10163
|
+
if (outputFormat === "json") {
|
|
10164
|
+
const parsed = JSON.parse(stdout);
|
|
9758
10165
|
resolve5({
|
|
9759
10166
|
success: true,
|
|
9760
10167
|
data: {
|
|
9761
|
-
response: stdout.trim(),
|
|
10168
|
+
response: parsed.result || stdout.trim(),
|
|
10169
|
+
session_id: parsed.session_id,
|
|
10170
|
+
cost_usd: 0,
|
|
10171
|
+
duration_ms: parsed.duration_ms,
|
|
10172
|
+
num_turns: parsed.num_turns,
|
|
9762
10173
|
model: model || "default",
|
|
9763
10174
|
via: "claude-max-subscription"
|
|
9764
10175
|
}
|
|
9765
10176
|
});
|
|
10177
|
+
return;
|
|
9766
10178
|
}
|
|
9767
10179
|
} catch {
|
|
9768
|
-
resolve5({
|
|
9769
|
-
success: true,
|
|
9770
|
-
data: {
|
|
9771
|
-
response: stdout.trim(),
|
|
9772
|
-
model: model || "default",
|
|
9773
|
-
via: "claude-max-subscription"
|
|
9774
|
-
}
|
|
9775
|
-
});
|
|
9776
10180
|
}
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
10181
|
+
resolve5({
|
|
10182
|
+
success: true,
|
|
10183
|
+
data: {
|
|
10184
|
+
response: stdout.trim(),
|
|
10185
|
+
model: model || "default",
|
|
10186
|
+
via: "claude-max-subscription"
|
|
10187
|
+
}
|
|
10188
|
+
});
|
|
10189
|
+
});
|
|
10190
|
+
child.on("error", (err) => {
|
|
10191
|
+
resolve5({
|
|
10192
|
+
success: false,
|
|
10193
|
+
error: `Claude pipe spawn error: ${err.message}`,
|
|
10194
|
+
errorCode: "CLAUDE_PIPE_FAILED"
|
|
10195
|
+
});
|
|
10196
|
+
});
|
|
10197
|
+
child.stdin.write(promptInput);
|
|
10198
|
+
child.stdin.end();
|
|
10199
|
+
})
|
|
10200
|
+
);
|
|
9782
10201
|
}
|
|
9783
10202
|
case "sys_claude_status": {
|
|
9784
10203
|
try {
|
|
9785
10204
|
const version = await runShell4("claude --version 2>/dev/null", 5e3);
|
|
9786
10205
|
let authStatus = "unknown";
|
|
10206
|
+
let authDetails;
|
|
9787
10207
|
try {
|
|
9788
10208
|
const status = await runShell4("claude auth status 2>&1", 1e4);
|
|
9789
|
-
|
|
10209
|
+
const trimmed = status.trim();
|
|
10210
|
+
let parsed = null;
|
|
10211
|
+
try {
|
|
10212
|
+
parsed = JSON.parse(trimmed);
|
|
10213
|
+
} catch {
|
|
10214
|
+
parsed = null;
|
|
10215
|
+
}
|
|
10216
|
+
if (parsed && typeof parsed === "object") {
|
|
10217
|
+
const loggedIn = parsed.loggedIn === true;
|
|
10218
|
+
authStatus = loggedIn ? "authenticated" : "not_authenticated";
|
|
10219
|
+
authDetails = {
|
|
10220
|
+
authMethod: typeof parsed.authMethod === "string" ? parsed.authMethod : void 0,
|
|
10221
|
+
apiProvider: typeof parsed.apiProvider === "string" ? parsed.apiProvider : void 0,
|
|
10222
|
+
email: typeof parsed.email === "string" ? parsed.email : void 0,
|
|
10223
|
+
orgId: typeof parsed.orgId === "string" ? parsed.orgId : void 0,
|
|
10224
|
+
subscriptionType: typeof parsed.subscriptionType === "string" ? parsed.subscriptionType : void 0
|
|
10225
|
+
};
|
|
10226
|
+
} else {
|
|
10227
|
+
const lower = trimmed.toLowerCase();
|
|
10228
|
+
authStatus = lower.includes("authenticated") || lower.includes("logged in") || lower.includes('loggedin":true') ? "authenticated" : "not_authenticated";
|
|
10229
|
+
}
|
|
9790
10230
|
} catch {
|
|
9791
10231
|
authStatus = "not_authenticated";
|
|
9792
10232
|
}
|
|
@@ -9796,7 +10236,8 @@ print(result.stdout[:5000])
|
|
|
9796
10236
|
installed: true,
|
|
9797
10237
|
version: version.trim(),
|
|
9798
10238
|
authenticated: authStatus === "authenticated",
|
|
9799
|
-
status: authStatus
|
|
10239
|
+
status: authStatus,
|
|
10240
|
+
...authDetails ? { details: authDetails } : {}
|
|
9800
10241
|
}
|
|
9801
10242
|
};
|
|
9802
10243
|
} catch {
|
|
@@ -9990,6 +10431,99 @@ function stopImessageMonitor() {
|
|
|
9990
10431
|
imessageTimer = null;
|
|
9991
10432
|
}
|
|
9992
10433
|
}
|
|
10434
|
+
var permissionDialogTimer = null;
|
|
10435
|
+
var lastDialogSignature = null;
|
|
10436
|
+
var DIALOG_POLL_INTERVAL = 1500;
|
|
10437
|
+
var activeDialog = null;
|
|
10438
|
+
function startPermissionDialogWatcher() {
|
|
10439
|
+
if (adapter.platform !== "macos") return;
|
|
10440
|
+
permissionDialogTimer = setInterval(async () => {
|
|
10441
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
10442
|
+
try {
|
|
10443
|
+
const script = `
|
|
10444
|
+
tell application "System Events"
|
|
10445
|
+
set procList to every process where visible is true
|
|
10446
|
+
repeat with proc in procList
|
|
10447
|
+
set procName to name of proc as string
|
|
10448
|
+
repeat with w in (windows of proc)
|
|
10449
|
+
try
|
|
10450
|
+
set wRole to role of w as string
|
|
10451
|
+
if wRole is "AXSheet" or wRole is "AXDialog" then
|
|
10452
|
+
set wTitle to ""
|
|
10453
|
+
try
|
|
10454
|
+
set wTitle to title of w as string
|
|
10455
|
+
end try
|
|
10456
|
+
set wDesc to ""
|
|
10457
|
+
try
|
|
10458
|
+
set wDesc to description of w as string
|
|
10459
|
+
end try
|
|
10460
|
+
set btnNames to {}
|
|
10461
|
+
repeat with btn in (buttons of w)
|
|
10462
|
+
try
|
|
10463
|
+
set end of btnNames to (name of btn as string)
|
|
10464
|
+
end try
|
|
10465
|
+
end repeat
|
|
10466
|
+
if (count of btnNames) > 0 then
|
|
10467
|
+
set btnStr to ""
|
|
10468
|
+
repeat with b in btnNames
|
|
10469
|
+
if btnStr is not "" then set btnStr to btnStr & "|||"
|
|
10470
|
+
set btnStr to btnStr & b
|
|
10471
|
+
end repeat
|
|
10472
|
+
return procName & ":::" & wTitle & ":::" & wDesc & ":::" & btnStr
|
|
10473
|
+
end if
|
|
10474
|
+
end if
|
|
10475
|
+
end try
|
|
10476
|
+
end repeat
|
|
10477
|
+
end repeat
|
|
10478
|
+
return ""
|
|
10479
|
+
end tell`;
|
|
10480
|
+
const raw = await runAppleScript2(script);
|
|
10481
|
+
if (!raw) {
|
|
10482
|
+
if (activeDialog) {
|
|
10483
|
+
lastDialogSignature = null;
|
|
10484
|
+
activeDialog = null;
|
|
10485
|
+
}
|
|
10486
|
+
return;
|
|
10487
|
+
}
|
|
10488
|
+
const sig = raw;
|
|
10489
|
+
if (sig === lastDialogSignature) return;
|
|
10490
|
+
lastDialogSignature = sig;
|
|
10491
|
+
const [procName = "", title = "", desc = "", btnStr = ""] = raw.split(":::");
|
|
10492
|
+
const buttons = btnStr.split("|||").map((b) => b.trim()).filter(Boolean);
|
|
10493
|
+
const dialogId = `dlg_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
10494
|
+
activeDialog = { dialogId, procName, buttons, detectedAt: Date.now() };
|
|
10495
|
+
let screenshot;
|
|
10496
|
+
try {
|
|
10497
|
+
const scrResult = await adapter.screenshot();
|
|
10498
|
+
if (scrResult.image) {
|
|
10499
|
+
screenshot = scrResult.image;
|
|
10500
|
+
}
|
|
10501
|
+
} catch {
|
|
10502
|
+
}
|
|
10503
|
+
console.log(`
|
|
10504
|
+
\u{1F514} Permission dialog: [${procName}] "${title}" \u2014 buttons: ${buttons.join(", ")}`);
|
|
10505
|
+
ws.send(JSON.stringify({
|
|
10506
|
+
type: "permission_dialog",
|
|
10507
|
+
dialogId,
|
|
10508
|
+
procName,
|
|
10509
|
+
title: title || procName,
|
|
10510
|
+
message: desc,
|
|
10511
|
+
buttons,
|
|
10512
|
+
screenshot,
|
|
10513
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10514
|
+
}));
|
|
10515
|
+
} catch {
|
|
10516
|
+
}
|
|
10517
|
+
}, DIALOG_POLL_INTERVAL);
|
|
10518
|
+
}
|
|
10519
|
+
function stopPermissionDialogWatcher() {
|
|
10520
|
+
if (permissionDialogTimer) {
|
|
10521
|
+
clearInterval(permissionDialogTimer);
|
|
10522
|
+
permissionDialogTimer = null;
|
|
10523
|
+
}
|
|
10524
|
+
lastDialogSignature = null;
|
|
10525
|
+
activeDialog = null;
|
|
10526
|
+
}
|
|
9993
10527
|
var LAZY_CAPABILITY_TOOLS = [
|
|
9994
10528
|
"sys_calendar_list",
|
|
9995
10529
|
"sys_calendar_create",
|
|
@@ -10043,7 +10577,7 @@ var CAPABILITY_PROBES = [
|
|
|
10043
10577
|
name: "claude_cli",
|
|
10044
10578
|
test: async () => {
|
|
10045
10579
|
try {
|
|
10046
|
-
await runShell4(
|
|
10580
|
+
await runShell4('(which claude >/dev/null 2>&1 || test -x "$HOME/.local/bin/claude" || test -x "$HOME/.local/share/claude/versions/"*/claude) && (claude --version >/dev/null 2>&1 || "$HOME/.local/bin/claude" --version >/dev/null 2>&1)', 5e3);
|
|
10047
10581
|
return true;
|
|
10048
10582
|
} catch {
|
|
10049
10583
|
return false;
|
|
@@ -10193,6 +10727,8 @@ async function connect() {
|
|
|
10193
10727
|
}, HEARTBEAT_INTERVAL);
|
|
10194
10728
|
stopImessageMonitor();
|
|
10195
10729
|
startImessageMonitor();
|
|
10730
|
+
stopPermissionDialogWatcher();
|
|
10731
|
+
startPermissionDialogWatcher();
|
|
10196
10732
|
});
|
|
10197
10733
|
ws.on("message", async (raw) => {
|
|
10198
10734
|
try {
|
|
@@ -10210,7 +10746,13 @@ async function connect() {
|
|
|
10210
10746
|
\u26A1 Command: ${msg.command}`,
|
|
10211
10747
|
msg.params ? JSON.stringify(msg.params).slice(0, 200) : ""
|
|
10212
10748
|
);
|
|
10213
|
-
const
|
|
10749
|
+
const streamCb = (chunk) => {
|
|
10750
|
+
try {
|
|
10751
|
+
ws.send(JSON.stringify({ id: msg.id, type: "stream", chunk }));
|
|
10752
|
+
} catch {
|
|
10753
|
+
}
|
|
10754
|
+
};
|
|
10755
|
+
const result = await handleCommand(msg.command, msg.params ?? {}, streamCb);
|
|
10214
10756
|
console.log(
|
|
10215
10757
|
` \u2192 ${result.success ? "\u2705" : "\u274C"}`,
|
|
10216
10758
|
result.success ? JSON.stringify(result.data).slice(0, 200) : result.error
|
|
@@ -10228,6 +10770,7 @@ async function connect() {
|
|
|
10228
10770
|
console.log(`
|
|
10229
10771
|
\u{1F50C} Disconnected (${code}: ${reasonStr})`);
|
|
10230
10772
|
stopImessageMonitor();
|
|
10773
|
+
stopPermissionDialogWatcher();
|
|
10231
10774
|
if (heartbeatTimer) {
|
|
10232
10775
|
clearInterval(heartbeatTimer);
|
|
10233
10776
|
heartbeatTimer = null;
|
|
@@ -10933,6 +11476,10 @@ function writeString(view, offset, str) {
|
|
|
10933
11476
|
}
|
|
10934
11477
|
var currentPlatform = detectPlatform();
|
|
10935
11478
|
var platformName = { macos: "macOS", windows: "Windows", linux: "Linux" }[currentPlatform] || "Unknown";
|
|
11479
|
+
acquireCompanionLock();
|
|
11480
|
+
process.on("exit", () => {
|
|
11481
|
+
releaseCompanionLock();
|
|
11482
|
+
});
|
|
10936
11483
|
console.log("");
|
|
10937
11484
|
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");
|
|
10938
11485
|
console.log(` \u2551 Pulso ${platformName} Companion v0.4.3 \u2551`);
|
|
@@ -10958,10 +11505,12 @@ process.on("SIGINT", () => {
|
|
|
10958
11505
|
console.log("\n\u{1F44B} Shutting down Pulso Companion...");
|
|
10959
11506
|
wakeWordActive = false;
|
|
10960
11507
|
ws?.close(1e3, "User shutdown");
|
|
11508
|
+
releaseCompanionLock();
|
|
10961
11509
|
process.exit(0);
|
|
10962
11510
|
});
|
|
10963
11511
|
process.on("SIGTERM", () => {
|
|
10964
11512
|
wakeWordActive = false;
|
|
10965
11513
|
ws?.close(1e3, "Process terminated");
|
|
11514
|
+
releaseCompanionLock();
|
|
10966
11515
|
process.exit(0);
|
|
10967
11516
|
});
|