@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.
Files changed (2) hide show
  1. package/dist/index.js +68 -52
  2. 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 -e '${script.replace(/'/g, "'\\''")}'`,
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 Terminal (or your terminal app)."
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 may be needed. Go to System Settings \u2192 Privacy & Security \u2192 Screen Recording."
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
- exec(`rm -f ${pngPath}`);
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
- exec(`rm -f ${pngPath} ${jpgPath}`);
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
- `tell application "Safari" to do JavaScript ${JSON.stringify(js)} in front document`
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
- `tell application "${browser.replace(/"/g, '\\"')}" to execute javascript ${JSON.stringify(js)} in active tab of front window`
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
- return { success: true, data: { result: result.slice(0, 5e3) } };
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
- set year of ${varName} to ${p.y}
1326
- set month of ${varName} to ${p.mo}
1327
- set day of ${varName} to ${p.d}
1328
- set hours of ${varName} to ${p.h}
1329
- set minutes of ${varName} to ${p.mi}
1330
- set seconds of ${varName} to 0`;
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 aux --sort=-%mem | head -25 | awk '{printf "%s|%s|%s|%s|", $1, $2, $3, $4; for(i=11;i<=NF;i++) printf "%s ", $i; print ""}'`
2299
+ `ps -Ae -o user,pid,%cpu,%mem,command | sort -nr ${sortCol} | head -${limit}`
2270
2300
  );
2271
- const lines = result.trim().split("\n").slice(1);
2272
- const processes = lines.map((l) => {
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: user?.trim(),
2276
- pid: pid?.trim(),
2277
- cpu: cpu?.trim() + "%",
2278
- mem: mem?.trim() + "%",
2279
- command: cmdParts.join("|").trim()
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 result = await runAppleScript(`
2385
- set output to ""
2386
- tell application "System Events"
2387
- set audioOut to name of every audio output device
2388
- repeat with d in audioOut
2389
- set output to output & d & linefeed
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: result.trim().split("\n").filter((d) => d)
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.0",
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@pulso.dev>",
25
+ "author": "Pulso <hello@runpulso.com>",
26
26
  "license": "MIT",
27
27
  "repository": {
28
28
  "type": "git",