@pulso/companion 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +409 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ if (!TOKEN) {
|
|
|
27
27
|
var ACCESS_LEVEL = process.env.PULSO_ACCESS ?? process.argv.find((_, i, a) => a[i - 1] === "--access") ?? "sandboxed";
|
|
28
28
|
var WAKE_WORD_ENABLED = process.argv.includes("--wake-word");
|
|
29
29
|
var PICOVOICE_ACCESS_KEY = process.env.PICOVOICE_ACCESS_KEY ?? process.argv.find((_, i, a) => a[i - 1] === "--picovoice-key") ?? "";
|
|
30
|
-
var WS_URL = API_URL.replace("https://", "wss://").replace("http://", "ws://") + "/ws/
|
|
30
|
+
var WS_URL = API_URL.replace("https://", "wss://").replace("http://", "ws://") + "/ws/companion?token=" + TOKEN;
|
|
31
31
|
var HOME = homedir();
|
|
32
32
|
var RECONNECT_DELAY = 5e3;
|
|
33
33
|
var SAFE_DIRS = ["Documents", "Desktop", "Downloads", "Projects", "Projetos"];
|
|
@@ -478,8 +478,54 @@ async function handleCommand(command, params) {
|
|
|
478
478
|
case "sys_open_app": {
|
|
479
479
|
const app = params.app;
|
|
480
480
|
if (!app) return { success: false, error: "Missing app name" };
|
|
481
|
-
|
|
482
|
-
|
|
481
|
+
const sanitizedApp = app.replace(/"/g, "");
|
|
482
|
+
try {
|
|
483
|
+
await runShell(`open -a "${sanitizedApp}"`);
|
|
484
|
+
} catch (e) {
|
|
485
|
+
return { success: false, error: `Failed to open "${sanitizedApp}": ${e.message}`, errorCode: "APP_NOT_FOUND" };
|
|
486
|
+
}
|
|
487
|
+
let launched = false;
|
|
488
|
+
for (let i = 0; i < 10; i++) {
|
|
489
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
490
|
+
try {
|
|
491
|
+
const running = await runAppleScript(
|
|
492
|
+
`tell application "System Events" to (name of processes) contains "${sanitizedApp}"`
|
|
493
|
+
);
|
|
494
|
+
if (running.trim() === "true") {
|
|
495
|
+
launched = true;
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
} catch {
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
let windowInfo;
|
|
502
|
+
if (launched) {
|
|
503
|
+
try {
|
|
504
|
+
const winCheck = await runAppleScript(`
|
|
505
|
+
tell application "System Events"
|
|
506
|
+
tell process "${sanitizedApp}"
|
|
507
|
+
if (count of windows) > 0 then
|
|
508
|
+
set w to window 1
|
|
509
|
+
return (name of w) & "|" & (position of w as string) & "|" & (size of w as string)
|
|
510
|
+
else
|
|
511
|
+
return "no-window"
|
|
512
|
+
end if
|
|
513
|
+
end tell
|
|
514
|
+
end tell`);
|
|
515
|
+
windowInfo = winCheck.trim();
|
|
516
|
+
} catch {
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
success: true,
|
|
521
|
+
data: {
|
|
522
|
+
opened: sanitizedApp,
|
|
523
|
+
launched,
|
|
524
|
+
hasWindow: windowInfo ? windowInfo !== "no-window" : false,
|
|
525
|
+
windowInfo: windowInfo && windowInfo !== "no-window" ? windowInfo : void 0,
|
|
526
|
+
note: launched ? `"${sanitizedApp}" is running${windowInfo && windowInfo !== "no-window" ? " with a visible window" : " (may still be loading)"}.` : `"${sanitizedApp}" was requested to open but process not detected yet. It may still be launching \u2014 take a screenshot to verify.`
|
|
527
|
+
}
|
|
528
|
+
};
|
|
483
529
|
}
|
|
484
530
|
case "sys_open_url": {
|
|
485
531
|
const url = params.url;
|
|
@@ -685,29 +731,33 @@ async function handleCommand(command, params) {
|
|
|
685
731
|
return { success: true, data: { path, written: content.length } };
|
|
686
732
|
}
|
|
687
733
|
case "sys_screenshot": {
|
|
734
|
+
const display = params.display || 0;
|
|
688
735
|
const ts = Date.now();
|
|
689
736
|
const pngPath = `/tmp/pulso-ss-${ts}.png`;
|
|
690
737
|
const jpgPath = `/tmp/pulso-ss-${ts}.jpg`;
|
|
691
738
|
try {
|
|
692
|
-
|
|
739
|
+
const displayFlag = display > 0 ? `-D${display}` : "";
|
|
740
|
+
await runShell(`screencapture -C -x ${displayFlag} ${pngPath}`, 15e3);
|
|
693
741
|
} catch (ssErr) {
|
|
694
742
|
const msg = ssErr.message || "";
|
|
695
743
|
if (msg.includes("could not create image") || msg.includes("display")) {
|
|
696
744
|
return {
|
|
697
745
|
success: false,
|
|
698
|
-
error: "Screen Recording permission required. Go to System Settings \u2192 Privacy & Security \u2192 Screen Recording \u2192 enable your terminal app (Terminal, iTerm, etc). Then restart the companion."
|
|
746
|
+
error: "Screen Recording permission required. Go to System Settings \u2192 Privacy & Security \u2192 Screen Recording \u2192 enable your terminal app (Terminal, iTerm, etc). Then restart the companion.",
|
|
747
|
+
errorCode: "PERMISSION_DENIED"
|
|
699
748
|
};
|
|
700
749
|
}
|
|
701
|
-
return { success: false, error: `Screenshot failed: ${msg}
|
|
750
|
+
return { success: false, error: `Screenshot failed: ${msg}`, errorCode: "SCREENSHOT_FAILED" };
|
|
702
751
|
}
|
|
703
752
|
if (!existsSync(pngPath))
|
|
704
753
|
return {
|
|
705
754
|
success: false,
|
|
706
|
-
error: "Screenshot failed \u2014 Screen Recording permission needed.
|
|
755
|
+
error: "Screenshot failed \u2014 Screen Recording permission needed.",
|
|
756
|
+
errorCode: "PERMISSION_DENIED"
|
|
707
757
|
};
|
|
708
758
|
try {
|
|
709
759
|
await runShell(
|
|
710
|
-
`sips --resampleWidth
|
|
760
|
+
`sips --resampleWidth 1600 --setProperty format jpeg --setProperty formatOptions 75 ${pngPath} --out ${jpgPath}`,
|
|
711
761
|
1e4
|
|
712
762
|
);
|
|
713
763
|
} catch {
|
|
@@ -721,7 +771,8 @@ async function handleCommand(command, params) {
|
|
|
721
771
|
data: {
|
|
722
772
|
image: `data:image/png;base64,${buf2.toString("base64")}`,
|
|
723
773
|
format: "png",
|
|
724
|
-
|
|
774
|
+
display: display || "all",
|
|
775
|
+
note: "Full screen screenshot (PNG fallback)"
|
|
725
776
|
}
|
|
726
777
|
};
|
|
727
778
|
}
|
|
@@ -735,11 +786,18 @@ async function handleCommand(command, params) {
|
|
|
735
786
|
unlinkSync(jpgPath);
|
|
736
787
|
} catch {
|
|
737
788
|
}
|
|
738
|
-
let
|
|
789
|
+
let displayInfo = { width: 0, height: 0, displays: 1 };
|
|
739
790
|
try {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
791
|
+
const diSwift = `
|
|
792
|
+
import Cocoa
|
|
793
|
+
let screens = NSScreen.screens
|
|
794
|
+
let main = screens.first!
|
|
795
|
+
let w = Int(main.frame.width)
|
|
796
|
+
let h = Int(main.frame.height)
|
|
797
|
+
print("\\(w),\\(h),\\(screens.count)")`;
|
|
798
|
+
const diResult = await runSwift(diSwift);
|
|
799
|
+
const [dw, dh, dc] = diResult.trim().split(",").map(Number);
|
|
800
|
+
displayInfo = { width: dw || 0, height: dh || 0, displays: dc || 1 };
|
|
743
801
|
} catch {
|
|
744
802
|
}
|
|
745
803
|
return {
|
|
@@ -747,12 +805,110 @@ async function handleCommand(command, params) {
|
|
|
747
805
|
data: {
|
|
748
806
|
image: `data:image/jpeg;base64,${base64}`,
|
|
749
807
|
format: "jpeg",
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
808
|
+
display: display || "all",
|
|
809
|
+
screenWidth: displayInfo.width,
|
|
810
|
+
screenHeight: displayInfo.height,
|
|
811
|
+
totalDisplays: displayInfo.displays,
|
|
812
|
+
imageWidth: 1600,
|
|
813
|
+
note: display > 0 ? `Screenshot of display ${display}. Coordinates: multiply x by (${displayInfo.width}/1600) and y by (${displayInfo.height}/(1600*${displayInfo.height}/${displayInfo.width})) for actual clicks.` : `Screenshot of all ${displayInfo.displays} display(s) stitched horizontally. Use sys_list_displays to get individual display bounds for coordinate mapping.`
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
case "sys_screenshot_region": {
|
|
818
|
+
const rx = params.x;
|
|
819
|
+
const ry = params.y;
|
|
820
|
+
const rw = params.width;
|
|
821
|
+
const rh = params.height;
|
|
822
|
+
if (rx == null || ry == null || rw == null || rh == null)
|
|
823
|
+
return { success: false, error: "Missing x, y, width, or height" };
|
|
824
|
+
const ts2 = Date.now();
|
|
825
|
+
const regPath = `/tmp/pulso-ss-region-${ts2}.png`;
|
|
826
|
+
const regJpg = `/tmp/pulso-ss-region-${ts2}.jpg`;
|
|
827
|
+
try {
|
|
828
|
+
await runShell(`screencapture -x -R${rx},${ry},${rw},${rh} ${regPath}`, 15e3);
|
|
829
|
+
} catch (e) {
|
|
830
|
+
return { success: false, error: `Region screenshot failed: ${e.message}`, errorCode: "SCREENSHOT_FAILED" };
|
|
831
|
+
}
|
|
832
|
+
if (!existsSync(regPath))
|
|
833
|
+
return { success: false, error: "Region screenshot failed", errorCode: "SCREENSHOT_FAILED" };
|
|
834
|
+
try {
|
|
835
|
+
await runShell(`sips --setProperty format jpeg --setProperty formatOptions 85 ${regPath} --out ${regJpg}`, 1e4);
|
|
836
|
+
} catch {
|
|
837
|
+
const rb = readFileSync(regPath);
|
|
838
|
+
try {
|
|
839
|
+
unlinkSync(regPath);
|
|
840
|
+
} catch {
|
|
841
|
+
}
|
|
842
|
+
return { success: true, data: { image: `data:image/png;base64,${rb.toString("base64")}`, format: "png", region: { x: rx, y: ry, width: rw, height: rh } } };
|
|
843
|
+
}
|
|
844
|
+
const rb2 = readFileSync(regJpg);
|
|
845
|
+
try {
|
|
846
|
+
unlinkSync(regPath);
|
|
847
|
+
} catch {
|
|
848
|
+
}
|
|
849
|
+
try {
|
|
850
|
+
unlinkSync(regJpg);
|
|
851
|
+
} catch {
|
|
852
|
+
}
|
|
853
|
+
return {
|
|
854
|
+
success: true,
|
|
855
|
+
data: {
|
|
856
|
+
image: `data:image/jpeg;base64,${rb2.toString("base64")}`,
|
|
857
|
+
format: "jpeg",
|
|
858
|
+
region: { x: rx, y: ry, width: rw, height: rh },
|
|
859
|
+
note: "Region screenshot at actual resolution (no scaling). Coordinates are absolute screen coordinates."
|
|
753
860
|
}
|
|
754
861
|
};
|
|
755
862
|
}
|
|
863
|
+
case "sys_list_displays": {
|
|
864
|
+
try {
|
|
865
|
+
const swift = `
|
|
866
|
+
import Cocoa
|
|
867
|
+
let screens = NSScreen.screens
|
|
868
|
+
var result = ""
|
|
869
|
+
for (i, screen) in screens.enumerated() {
|
|
870
|
+
let f = screen.frame
|
|
871
|
+
let vf = screen.visibleFrame
|
|
872
|
+
let isMain = (screen == NSScreen.main)
|
|
873
|
+
let scale = screen.backingScaleFactor
|
|
874
|
+
let name = screen.localizedName
|
|
875
|
+
result += "\\(i+1)|\\(name)|\\(Int(f.origin.x)),\\(Int(f.origin.y))|\\(Int(f.width)),\\(Int(f.height))|\\(Int(vf.origin.x)),\\(Int(vf.origin.y))|\\(Int(vf.width)),\\(Int(vf.height))|\\(scale)|\\(isMain)\\n"
|
|
876
|
+
}
|
|
877
|
+
print(result)`;
|
|
878
|
+
const raw = await runSwift(swift, 15e3);
|
|
879
|
+
const displays = raw.trim().split("\n").filter(Boolean).map((line) => {
|
|
880
|
+
const [index, name, origin, size, visOrigin, visSize, scale, isMain] = line.split("|");
|
|
881
|
+
const [ox, oy] = (origin || "0,0").split(",").map(Number);
|
|
882
|
+
const [sw2, sh2] = (size || "0,0").split(",").map(Number);
|
|
883
|
+
const [vox, voy] = (visOrigin || "0,0").split(",").map(Number);
|
|
884
|
+
const [vsw, vsh] = (visSize || "0,0").split(",").map(Number);
|
|
885
|
+
return {
|
|
886
|
+
display: parseInt(index) || 0,
|
|
887
|
+
name: name?.trim() || "Unknown",
|
|
888
|
+
x: ox,
|
|
889
|
+
y: oy,
|
|
890
|
+
width: sw2,
|
|
891
|
+
height: sh2,
|
|
892
|
+
visibleX: vox,
|
|
893
|
+
visibleY: voy,
|
|
894
|
+
visibleWidth: vsw,
|
|
895
|
+
visibleHeight: vsh,
|
|
896
|
+
scale: parseFloat(scale) || 1,
|
|
897
|
+
isMain: isMain?.trim() === "true"
|
|
898
|
+
};
|
|
899
|
+
});
|
|
900
|
+
return {
|
|
901
|
+
success: true,
|
|
902
|
+
data: {
|
|
903
|
+
displays,
|
|
904
|
+
count: displays.length,
|
|
905
|
+
note: "Display coordinates use macOS coordinate system (origin bottom-left). For screencapture: use display number (1-based). For mouse clicks: windows on display 2 have x >= display1.width."
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
} catch (e) {
|
|
909
|
+
return { success: false, error: `Failed to list displays: ${e.message}` };
|
|
910
|
+
}
|
|
911
|
+
}
|
|
756
912
|
// ── Computer-Use: Mouse & Keyboard ────────────────────
|
|
757
913
|
case "sys_mouse_click": {
|
|
758
914
|
const x = Number(params.x);
|
|
@@ -1732,6 +1888,19 @@ end tell`);
|
|
|
1732
1888
|
}
|
|
1733
1889
|
// ── Window Management ───────────────────────────────────
|
|
1734
1890
|
case "sys_window_list": {
|
|
1891
|
+
let displayBounds = [];
|
|
1892
|
+
try {
|
|
1893
|
+
const dbSwift = `
|
|
1894
|
+
import Cocoa
|
|
1895
|
+
let screens = NSScreen.screens
|
|
1896
|
+
for s in screens { print("\\(Int(s.frame.origin.x)),\\(Int(s.frame.width))") }`;
|
|
1897
|
+
const dbRaw = await runSwift(dbSwift, 5e3);
|
|
1898
|
+
displayBounds = dbRaw.trim().split("\n").filter(Boolean).map((l) => {
|
|
1899
|
+
const [bx, bw] = l.split(",").map(Number);
|
|
1900
|
+
return { x: bx || 0, width: bw || 0 };
|
|
1901
|
+
});
|
|
1902
|
+
} catch {
|
|
1903
|
+
}
|
|
1735
1904
|
const raw4 = await runAppleScript(`
|
|
1736
1905
|
tell application "System Events"
|
|
1737
1906
|
set output to ""
|
|
@@ -1744,15 +1913,42 @@ end tell`);
|
|
|
1744
1913
|
return output
|
|
1745
1914
|
end tell`);
|
|
1746
1915
|
const windows = raw4.split("\n").filter(Boolean).map((line) => {
|
|
1747
|
-
const [
|
|
1916
|
+
const [appW, title, pos, sz] = line.split(" | ");
|
|
1917
|
+
const posMatch = (pos || "").match(/(\d+),\s*(\d+)/);
|
|
1918
|
+
const wx = posMatch ? parseInt(posMatch[1]) : 0;
|
|
1919
|
+
const wy = posMatch ? parseInt(posMatch[2]) : 0;
|
|
1920
|
+
const szMatch = (sz || "").match(/(\d+),\s*(\d+)/);
|
|
1921
|
+
const ww = szMatch ? parseInt(szMatch[1]) : 0;
|
|
1922
|
+
const wh = szMatch ? parseInt(szMatch[2]) : 0;
|
|
1923
|
+
let displayIndex = 1;
|
|
1924
|
+
if (displayBounds.length > 1) {
|
|
1925
|
+
for (let di = 0; di < displayBounds.length; di++) {
|
|
1926
|
+
const db = displayBounds[di];
|
|
1927
|
+
if (wx >= db.x && wx < db.x + db.width) {
|
|
1928
|
+
displayIndex = di + 1;
|
|
1929
|
+
break;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1748
1933
|
return {
|
|
1749
|
-
app:
|
|
1934
|
+
app: appW?.trim(),
|
|
1750
1935
|
title: title?.trim(),
|
|
1751
|
-
|
|
1752
|
-
|
|
1936
|
+
x: wx,
|
|
1937
|
+
y: wy,
|
|
1938
|
+
width: ww,
|
|
1939
|
+
height: wh,
|
|
1940
|
+
display: displayIndex
|
|
1753
1941
|
};
|
|
1754
1942
|
});
|
|
1755
|
-
return {
|
|
1943
|
+
return {
|
|
1944
|
+
success: true,
|
|
1945
|
+
data: {
|
|
1946
|
+
windows,
|
|
1947
|
+
count: windows.length,
|
|
1948
|
+
displays: displayBounds.length || 1,
|
|
1949
|
+
note: "Window positions are in global coordinates. 'display' indicates which monitor the window is on (1=primary, 2=secondary, etc)."
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1756
1952
|
}
|
|
1757
1953
|
case "sys_window_focus": {
|
|
1758
1954
|
const appName = params.app;
|
|
@@ -2885,6 +3081,184 @@ var reconnectTimer = null;
|
|
|
2885
3081
|
var heartbeatTimer = null;
|
|
2886
3082
|
var HEARTBEAT_INTERVAL = 3e4;
|
|
2887
3083
|
var reconnectAttempts = 0;
|
|
3084
|
+
var CAPABILITY_PROBES = [
|
|
3085
|
+
{
|
|
3086
|
+
name: "calendar",
|
|
3087
|
+
test: async () => {
|
|
3088
|
+
try {
|
|
3089
|
+
await runAppleScript('tell application "Calendar" to name of calendars');
|
|
3090
|
+
return true;
|
|
3091
|
+
} catch {
|
|
3092
|
+
return false;
|
|
3093
|
+
}
|
|
3094
|
+
},
|
|
3095
|
+
tools: ["sys_calendar_list", "sys_calendar_create"]
|
|
3096
|
+
},
|
|
3097
|
+
{
|
|
3098
|
+
name: "contacts",
|
|
3099
|
+
test: async () => {
|
|
3100
|
+
try {
|
|
3101
|
+
await runAppleScript('tell application "Contacts" to count of people');
|
|
3102
|
+
return true;
|
|
3103
|
+
} catch {
|
|
3104
|
+
return false;
|
|
3105
|
+
}
|
|
3106
|
+
},
|
|
3107
|
+
tools: ["sys_contacts_search"]
|
|
3108
|
+
},
|
|
3109
|
+
{
|
|
3110
|
+
name: "reminders",
|
|
3111
|
+
test: async () => {
|
|
3112
|
+
try {
|
|
3113
|
+
await runAppleScript('tell application "Reminders" to name of lists');
|
|
3114
|
+
return true;
|
|
3115
|
+
} catch {
|
|
3116
|
+
return false;
|
|
3117
|
+
}
|
|
3118
|
+
},
|
|
3119
|
+
tools: ["sys_reminder_create", "sys_reminder_list"]
|
|
3120
|
+
},
|
|
3121
|
+
{
|
|
3122
|
+
name: "screenshot",
|
|
3123
|
+
test: async () => {
|
|
3124
|
+
try {
|
|
3125
|
+
await runShell("screencapture -x /tmp/pulso-probe-ss.png", 5e3);
|
|
3126
|
+
unlinkSync("/tmp/pulso-probe-ss.png");
|
|
3127
|
+
return true;
|
|
3128
|
+
} catch {
|
|
3129
|
+
return false;
|
|
3130
|
+
}
|
|
3131
|
+
},
|
|
3132
|
+
tools: ["sys_screenshot", "sys_screenshot_region", "sys_list_displays"]
|
|
3133
|
+
},
|
|
3134
|
+
{
|
|
3135
|
+
name: "chrome_js",
|
|
3136
|
+
test: async () => {
|
|
3137
|
+
try {
|
|
3138
|
+
await runShell("pgrep -x 'Google Chrome' >/dev/null 2>&1");
|
|
3139
|
+
return true;
|
|
3140
|
+
} catch {
|
|
3141
|
+
return false;
|
|
3142
|
+
}
|
|
3143
|
+
},
|
|
3144
|
+
tools: ["sys_browser_execute_js"]
|
|
3145
|
+
},
|
|
3146
|
+
{
|
|
3147
|
+
name: "safari_js",
|
|
3148
|
+
test: async () => {
|
|
3149
|
+
try {
|
|
3150
|
+
await runAppleScript('tell application "Safari" to name of front window');
|
|
3151
|
+
return true;
|
|
3152
|
+
} catch {
|
|
3153
|
+
return false;
|
|
3154
|
+
}
|
|
3155
|
+
},
|
|
3156
|
+
tools: ["sys_browser_execute_js"]
|
|
3157
|
+
},
|
|
3158
|
+
{
|
|
3159
|
+
name: "spotify",
|
|
3160
|
+
test: async () => {
|
|
3161
|
+
try {
|
|
3162
|
+
await runShell("pgrep -x Spotify >/dev/null 2>&1 || ls /Applications/Spotify.app >/dev/null 2>&1");
|
|
3163
|
+
return true;
|
|
3164
|
+
} catch {
|
|
3165
|
+
return false;
|
|
3166
|
+
}
|
|
3167
|
+
},
|
|
3168
|
+
tools: ["sys_spotify_play", "sys_spotify_pause", "sys_spotify_current", "sys_spotify_next", "sys_spotify_previous", "sys_spotify_search"]
|
|
3169
|
+
},
|
|
3170
|
+
{
|
|
3171
|
+
name: "tts",
|
|
3172
|
+
test: async () => {
|
|
3173
|
+
try {
|
|
3174
|
+
await runShell("which say >/dev/null 2>&1");
|
|
3175
|
+
return true;
|
|
3176
|
+
} catch {
|
|
3177
|
+
return false;
|
|
3178
|
+
}
|
|
3179
|
+
},
|
|
3180
|
+
tools: ["sys_tts"]
|
|
3181
|
+
},
|
|
3182
|
+
{
|
|
3183
|
+
name: "shortcuts",
|
|
3184
|
+
test: async () => {
|
|
3185
|
+
try {
|
|
3186
|
+
await runShell("which shortcuts >/dev/null 2>&1");
|
|
3187
|
+
return true;
|
|
3188
|
+
} catch {
|
|
3189
|
+
return false;
|
|
3190
|
+
}
|
|
3191
|
+
},
|
|
3192
|
+
tools: ["sys_shortcuts_run", "sys_shortcuts_list"]
|
|
3193
|
+
}
|
|
3194
|
+
];
|
|
3195
|
+
var verifiedCapabilities = {
|
|
3196
|
+
available: [],
|
|
3197
|
+
unavailable: [],
|
|
3198
|
+
tools: []
|
|
3199
|
+
};
|
|
3200
|
+
async function probeCapabilities() {
|
|
3201
|
+
console.log("\u{1F50D} Probing system capabilities...");
|
|
3202
|
+
const available = [];
|
|
3203
|
+
const unavailable = [];
|
|
3204
|
+
const tools = /* @__PURE__ */ new Set();
|
|
3205
|
+
const alwaysAvailable = [
|
|
3206
|
+
"sys_open_app",
|
|
3207
|
+
"sys_open_url",
|
|
3208
|
+
"sys_clipboard_read",
|
|
3209
|
+
"sys_clipboard_write",
|
|
3210
|
+
"sys_notification",
|
|
3211
|
+
"sys_shell",
|
|
3212
|
+
"sys_file_read",
|
|
3213
|
+
"sys_file_write",
|
|
3214
|
+
"sys_file_list",
|
|
3215
|
+
"sys_file_search",
|
|
3216
|
+
"sys_volume",
|
|
3217
|
+
"sys_window_list",
|
|
3218
|
+
"sys_window_focus",
|
|
3219
|
+
"sys_system_info",
|
|
3220
|
+
"sys_wifi_info",
|
|
3221
|
+
"sys_disk_info",
|
|
3222
|
+
"sys_process_list",
|
|
3223
|
+
"sys_process_kill",
|
|
3224
|
+
"sys_dark_mode",
|
|
3225
|
+
"sys_spotlight_search",
|
|
3226
|
+
"sys_settings",
|
|
3227
|
+
"sys_screen_lock",
|
|
3228
|
+
"sys_trash",
|
|
3229
|
+
"sys_power",
|
|
3230
|
+
"sys_focus_mode",
|
|
3231
|
+
"sys_audio_devices",
|
|
3232
|
+
"sys_bluetooth",
|
|
3233
|
+
"sys_browser_open_tab",
|
|
3234
|
+
"sys_browser_close_tab",
|
|
3235
|
+
"sys_browser_tabs",
|
|
3236
|
+
"sys_browser_read_page",
|
|
3237
|
+
"sys_browser_navigate"
|
|
3238
|
+
];
|
|
3239
|
+
for (const t of alwaysAvailable) tools.add(t);
|
|
3240
|
+
const results = await Promise.allSettled(
|
|
3241
|
+
CAPABILITY_PROBES.map(async (probe) => {
|
|
3242
|
+
const ok = await probe.test();
|
|
3243
|
+
return { name: probe.name, ok, tools: probe.tools };
|
|
3244
|
+
})
|
|
3245
|
+
);
|
|
3246
|
+
for (const r of results) {
|
|
3247
|
+
if (r.status === "fulfilled") {
|
|
3248
|
+
if (r.value.ok) {
|
|
3249
|
+
available.push(r.value.name);
|
|
3250
|
+
for (const t of r.value.tools) tools.add(t);
|
|
3251
|
+
} else {
|
|
3252
|
+
unavailable.push(r.value.name);
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
const cap = { available, unavailable, tools: Array.from(tools) };
|
|
3257
|
+
console.log(` \u2705 Available: ${available.join(", ") || "none"}`);
|
|
3258
|
+
if (unavailable.length) console.log(` \u26A0\uFE0F Unavailable: ${unavailable.join(", ")}`);
|
|
3259
|
+
console.log(` \u{1F4E6} ${cap.tools.length} tools verified`);
|
|
3260
|
+
return cap;
|
|
3261
|
+
}
|
|
2888
3262
|
function connect() {
|
|
2889
3263
|
console.log("\u{1F50C} Connecting to Pulso...");
|
|
2890
3264
|
console.log(` ${WS_URL.replace(/token=.*/, "token=***")}`);
|
|
@@ -2917,7 +3291,19 @@ function connect() {
|
|
|
2917
3291
|
` Access: ${ACCESS_LEVEL === "full" ? "\u{1F513} Full (unrestricted)" : "\u{1F512} Sandboxed (safe dirs only)"}`
|
|
2918
3292
|
);
|
|
2919
3293
|
console.log(" Waiting for commands from Pulso agent...");
|
|
2920
|
-
|
|
3294
|
+
probeCapabilities().then((cap) => {
|
|
3295
|
+
verifiedCapabilities = cap;
|
|
3296
|
+
ws.send(JSON.stringify({
|
|
3297
|
+
type: "extension_ready",
|
|
3298
|
+
platform: "macos",
|
|
3299
|
+
version: "0.4.0",
|
|
3300
|
+
accessLevel: ACCESS_LEVEL,
|
|
3301
|
+
capabilities: cap.available,
|
|
3302
|
+
unavailable: cap.unavailable,
|
|
3303
|
+
tools: cap.tools,
|
|
3304
|
+
totalTools: cap.tools.length
|
|
3305
|
+
}));
|
|
3306
|
+
});
|
|
2921
3307
|
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
2922
3308
|
heartbeatTimer = setInterval(() => {
|
|
2923
3309
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
@@ -3168,7 +3554,7 @@ function writeString(view, offset, str) {
|
|
|
3168
3554
|
}
|
|
3169
3555
|
console.log("");
|
|
3170
3556
|
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");
|
|
3171
|
-
console.log(" \u2551 \u{1FAC0} Pulso Mac Companion v0.
|
|
3557
|
+
console.log(" \u2551 \u{1FAC0} Pulso Mac Companion v0.4.0 \u2551");
|
|
3172
3558
|
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");
|
|
3173
3559
|
console.log("");
|
|
3174
3560
|
setupPermissions().then(() => {
|