@pulso/companion 0.3.0 → 0.3.2
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 +68 -52
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
readdirSync,
|
|
11
11
|
statSync,
|
|
12
12
|
copyFileSync,
|
|
13
|
-
renameSync
|
|
13
|
+
renameSync,
|
|
14
|
+
unlinkSync
|
|
14
15
|
} from "fs";
|
|
15
16
|
import { homedir } from "os";
|
|
16
17
|
import { join, resolve, basename, extname } from "path";
|
|
@@ -43,10 +44,16 @@ function safePath(relative) {
|
|
|
43
44
|
}
|
|
44
45
|
function runAppleScript(script) {
|
|
45
46
|
return new Promise((resolve2, reject) => {
|
|
47
|
+
const tmpPath = `/tmp/pulso-as-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.scpt`;
|
|
48
|
+
writeFileSync(tmpPath, script, "utf-8");
|
|
46
49
|
exec(
|
|
47
|
-
`osascript
|
|
50
|
+
`osascript ${tmpPath}`,
|
|
48
51
|
{ timeout: 15e3 },
|
|
49
52
|
(err, stdout, stderr) => {
|
|
53
|
+
try {
|
|
54
|
+
unlinkSync(tmpPath);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
50
57
|
if (err) reject(new Error(stderr || err.message));
|
|
51
58
|
else resolve2(stdout.trim());
|
|
52
59
|
}
|
|
@@ -682,13 +689,13 @@ async function handleCommand(command, params) {
|
|
|
682
689
|
const pngPath = `/tmp/pulso-ss-${ts}.png`;
|
|
683
690
|
const jpgPath = `/tmp/pulso-ss-${ts}.jpg`;
|
|
684
691
|
try {
|
|
685
|
-
await runShell(`screencapture -x ${pngPath}`, 15e3);
|
|
692
|
+
await runShell(`screencapture -C -x -D1 ${pngPath}`, 15e3);
|
|
686
693
|
} catch (ssErr) {
|
|
687
694
|
const msg = ssErr.message || "";
|
|
688
695
|
if (msg.includes("could not create image") || msg.includes("display")) {
|
|
689
696
|
return {
|
|
690
697
|
success: false,
|
|
691
|
-
error: "Screen Recording permission required. Go to System Settings \u2192 Privacy & Security \u2192 Screen Recording \u2192 enable
|
|
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."
|
|
692
699
|
};
|
|
693
700
|
}
|
|
694
701
|
return { success: false, error: `Screenshot failed: ${msg}` };
|
|
@@ -696,7 +703,7 @@ async function handleCommand(command, params) {
|
|
|
696
703
|
if (!existsSync(pngPath))
|
|
697
704
|
return {
|
|
698
705
|
success: false,
|
|
699
|
-
error: "Screenshot failed \u2014 Screen Recording permission
|
|
706
|
+
error: "Screenshot failed \u2014 Screen Recording permission needed. System Settings \u2192 Privacy & Security \u2192 Screen Recording \u2192 enable your terminal app, then restart companion."
|
|
700
707
|
};
|
|
701
708
|
try {
|
|
702
709
|
await runShell(
|
|
@@ -705,7 +712,10 @@ async function handleCommand(command, params) {
|
|
|
705
712
|
);
|
|
706
713
|
} catch {
|
|
707
714
|
const buf2 = readFileSync(pngPath);
|
|
708
|
-
|
|
715
|
+
try {
|
|
716
|
+
unlinkSync(pngPath);
|
|
717
|
+
} catch {
|
|
718
|
+
}
|
|
709
719
|
return {
|
|
710
720
|
success: true,
|
|
711
721
|
data: {
|
|
@@ -717,7 +727,14 @@ async function handleCommand(command, params) {
|
|
|
717
727
|
}
|
|
718
728
|
const buf = readFileSync(jpgPath);
|
|
719
729
|
const base64 = buf.toString("base64");
|
|
720
|
-
|
|
730
|
+
try {
|
|
731
|
+
unlinkSync(pngPath);
|
|
732
|
+
} catch {
|
|
733
|
+
}
|
|
734
|
+
try {
|
|
735
|
+
unlinkSync(jpgPath);
|
|
736
|
+
} catch {
|
|
737
|
+
}
|
|
721
738
|
let screenSize = "unknown";
|
|
722
739
|
try {
|
|
723
740
|
screenSize = await runShell(
|
|
@@ -1159,21 +1176,33 @@ print("\\(x),\\(y)")`;
|
|
|
1159
1176
|
const browser = params.browser || "Google Chrome";
|
|
1160
1177
|
if (!js) return { success: false, error: "Missing JavaScript code" };
|
|
1161
1178
|
try {
|
|
1179
|
+
const tmpJs = `/tmp/pulso-js-${Date.now()}.js`;
|
|
1180
|
+
writeFileSync(tmpJs, js, "utf-8");
|
|
1162
1181
|
let result;
|
|
1163
1182
|
if (browser === "Safari") {
|
|
1164
1183
|
result = await runAppleScript(
|
|
1165
|
-
`
|
|
1184
|
+
`set jsCode to read POSIX file "${tmpJs}" as \xABclass utf8\xBB
|
|
1185
|
+
tell application "Safari"
|
|
1186
|
+
do JavaScript jsCode in document 1
|
|
1187
|
+
end tell`
|
|
1166
1188
|
);
|
|
1167
1189
|
} else {
|
|
1168
1190
|
result = await runAppleScript(
|
|
1169
|
-
`
|
|
1191
|
+
`set jsCode to read POSIX file "${tmpJs}" as \xABclass utf8\xBB
|
|
1192
|
+
tell application "${browser.replace(/"/g, '\\"')}"
|
|
1193
|
+
execute javascript jsCode in active tab of front window
|
|
1194
|
+
end tell`
|
|
1170
1195
|
);
|
|
1171
1196
|
}
|
|
1172
|
-
|
|
1197
|
+
try {
|
|
1198
|
+
unlinkSync(tmpJs);
|
|
1199
|
+
} catch {
|
|
1200
|
+
}
|
|
1201
|
+
return { success: true, data: { result: (result || "").slice(0, 5e3) } };
|
|
1173
1202
|
} catch (err) {
|
|
1174
1203
|
return {
|
|
1175
1204
|
success: false,
|
|
1176
|
-
error: `JS execution failed (enable 'Allow JavaScript from Apple Events' in browser): ${err.message}`
|
|
1205
|
+
error: `JS execution failed (enable 'Allow JavaScript from Apple Events' in browser Dev menu): ${err.message}`
|
|
1177
1206
|
};
|
|
1178
1207
|
}
|
|
1179
1208
|
}
|
|
@@ -1322,12 +1351,14 @@ print("\\(x),\\(y)")`;
|
|
|
1322
1351
|
const p = parseDate(iso);
|
|
1323
1352
|
if (!p) return "";
|
|
1324
1353
|
return `set ${varName} to current date
|
|
1325
|
-
|
|
1326
|
-
set
|
|
1327
|
-
set
|
|
1328
|
-
set
|
|
1329
|
-
set
|
|
1330
|
-
set
|
|
1354
|
+
tell ${varName}
|
|
1355
|
+
set its year to ${p.y}
|
|
1356
|
+
set its month to ${p.mo}
|
|
1357
|
+
set its day to ${p.d}
|
|
1358
|
+
set its hours to ${p.h}
|
|
1359
|
+
set its minutes to ${p.mi}
|
|
1360
|
+
set its seconds to 0
|
|
1361
|
+
end tell`;
|
|
1331
1362
|
};
|
|
1332
1363
|
const startDateScript = buildDateScript("startD", startStr);
|
|
1333
1364
|
if (!startDateScript)
|
|
@@ -1726,11 +1757,7 @@ end tell`);
|
|
|
1726
1757
|
case "sys_window_focus": {
|
|
1727
1758
|
const appName = params.app;
|
|
1728
1759
|
if (!appName) return { success: false, error: "Missing app name" };
|
|
1729
|
-
await runAppleScript(`
|
|
1730
|
-
tell application "${appName.replace(/"/g, '\\"')}"
|
|
1731
|
-
activate
|
|
1732
|
-
set frontmost to true
|
|
1733
|
-
end tell`);
|
|
1760
|
+
await runAppleScript(`tell application "${appName.replace(/"/g, '\\"')}" to activate`);
|
|
1734
1761
|
return { success: true, data: { focused: appName } };
|
|
1735
1762
|
}
|
|
1736
1763
|
case "sys_window_resize": {
|
|
@@ -2265,18 +2292,20 @@ print(text)`;
|
|
|
2265
2292
|
}
|
|
2266
2293
|
// ── NEW: Process Management ─────────────────────────────
|
|
2267
2294
|
case "sys_process_list": {
|
|
2295
|
+
const sortBy = params.sort_by || "cpu";
|
|
2296
|
+
const limit = Math.min(Number(params.limit) || 15, 30);
|
|
2297
|
+
const sortCol = sortBy === "mem" ? "-k4" : "-k3";
|
|
2268
2298
|
const result = await runShell(
|
|
2269
|
-
`ps
|
|
2299
|
+
`ps -Ae -o user,pid,%cpu,%mem,command | sort -nr ${sortCol} | head -${limit}`
|
|
2270
2300
|
);
|
|
2271
|
-
const
|
|
2272
|
-
|
|
2273
|
-
const [user, pid, cpu, mem, ...cmdParts] = l.split("|");
|
|
2301
|
+
const processes = result.trim().split("\n").filter((l) => l.trim()).map((l) => {
|
|
2302
|
+
const parts = l.trim().split(/\s+/);
|
|
2274
2303
|
return {
|
|
2275
|
-
user:
|
|
2276
|
-
pid:
|
|
2277
|
-
cpu:
|
|
2278
|
-
mem:
|
|
2279
|
-
command:
|
|
2304
|
+
user: parts[0],
|
|
2305
|
+
pid: parts[1],
|
|
2306
|
+
cpu: parts[2] + "%",
|
|
2307
|
+
mem: parts[3] + "%",
|
|
2308
|
+
command: parts.slice(4).join(" ").slice(0, 80)
|
|
2280
2309
|
};
|
|
2281
2310
|
});
|
|
2282
2311
|
return { success: true, data: { processes } };
|
|
@@ -2381,30 +2410,17 @@ print(text)`;
|
|
|
2381
2410
|
case "sys_audio_devices": {
|
|
2382
2411
|
const audioAction = params.action || "list";
|
|
2383
2412
|
if (audioAction === "list") {
|
|
2384
|
-
const
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
end repeat
|
|
2391
|
-
end tell
|
|
2392
|
-
return output`);
|
|
2393
|
-
if (!result.trim()) {
|
|
2394
|
-
const spResult = await runShell(
|
|
2395
|
-
`system_profiler SPAudioDataType 2>/dev/null | grep "Device Name" | sed 's/.*: //'`
|
|
2396
|
-
);
|
|
2397
|
-
return {
|
|
2398
|
-
success: true,
|
|
2399
|
-
data: {
|
|
2400
|
-
devices: spResult.trim().split("\n").filter((d) => d)
|
|
2401
|
-
}
|
|
2402
|
-
};
|
|
2403
|
-
}
|
|
2413
|
+
const spResult = await runShell(
|
|
2414
|
+
`system_profiler SPAudioDataType 2>/dev/null | grep -E "Device Name|Output Source" | sed 's/.*: //'`
|
|
2415
|
+
);
|
|
2416
|
+
const currentOut = await runShell(
|
|
2417
|
+
`osascript -e 'output volume of (get volume settings)'`
|
|
2418
|
+
).catch(() => "");
|
|
2404
2419
|
return {
|
|
2405
2420
|
success: true,
|
|
2406
2421
|
data: {
|
|
2407
|
-
devices:
|
|
2422
|
+
devices: spResult.trim().split("\n").filter((d) => d),
|
|
2423
|
+
currentVolume: currentOut.trim()
|
|
2408
2424
|
}
|
|
2409
2425
|
};
|
|
2410
2426
|
} else if (audioAction === "switch") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pulso/companion",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pulso Companion — gives your AI agent real control over your computer",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"mac",
|
|
23
23
|
"automation"
|
|
24
24
|
],
|
|
25
|
-
"author": "Pulso <hello@
|
|
25
|
+
"author": "Pulso <hello@runpulso.com>",
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|