@pulso/companion 0.4.4 → 0.4.6

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 +1494 -525
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -381,13 +381,7 @@ import { homedir, hostname, cpus, totalmem, freemem, uptime } from "os";
381
381
  import { join, resolve, basename, extname } from "path";
382
382
  var HOME = homedir();
383
383
  var NOTES_DIR = join(HOME, "Documents", "PulsoNotes");
384
- var SAFE_DIRS = [
385
- "Documents",
386
- "Desktop",
387
- "Downloads",
388
- "Projects",
389
- "Projetos"
390
- ];
384
+ var SAFE_DIRS = ["Documents", "Desktop", "Downloads", "Projects", "Projetos"];
391
385
  var ACCESS_LEVEL = process.env.PULSO_ACCESS ?? "sandboxed";
392
386
  function detectDisplayServer() {
393
387
  const sessionType = process.env.XDG_SESSION_TYPE;
@@ -674,9 +668,7 @@ var LinuxAdapter = class {
674
668
  async getVolume() {
675
669
  try {
676
670
  if (await commandExists("wpctl")) {
677
- const output2 = await runShell(
678
- "wpctl get-volume @DEFAULT_AUDIO_SINK@"
679
- );
671
+ const output2 = await runShell("wpctl get-volume @DEFAULT_AUDIO_SINK@");
680
672
  const match = output2.match(/Volume:\s+([\d.]+)/);
681
673
  if (match) {
682
674
  const volume = Math.round(parseFloat(match[1]) * 100);
@@ -703,9 +695,7 @@ var LinuxAdapter = class {
703
695
  }
704
696
  }
705
697
  if (await commandExists("amixer")) {
706
- const output2 = await runShell(
707
- "amixer sget Master | tail -1"
708
- );
698
+ const output2 = await runShell("amixer sget Master | tail -1");
709
699
  const match = output2.match(/\[(\d+)%\]/);
710
700
  const muteMatch = output2.match(/\[(on|off)\]/);
711
701
  if (match) {
@@ -763,7 +753,10 @@ var LinuxAdapter = class {
763
753
  if (backlightDirs.length > 0) {
764
754
  const bl = backlightDirs[0];
765
755
  const current = parseInt(
766
- readFileSync(`/sys/class/backlight/${bl}/brightness`, "utf-8").trim(),
756
+ readFileSync(
757
+ `/sys/class/backlight/${bl}/brightness`,
758
+ "utf-8"
759
+ ).trim(),
767
760
  10
768
761
  );
769
762
  const max = parseInt(
@@ -845,9 +838,7 @@ var LinuxAdapter = class {
845
838
  "xrandr --query | grep ' connected' | head -1 | cut -d' ' -f1"
846
839
  );
847
840
  if (displays) {
848
- await runShell(
849
- `xrandr --output ${displays} --brightness ${scalar}`
850
- );
841
+ await runShell(`xrandr --output ${displays} --brightness ${scalar}`);
851
842
  return {
852
843
  success: true,
853
844
  data: {
@@ -931,20 +922,14 @@ var LinuxAdapter = class {
931
922
  if (wifiOut) wifi = wifiOut;
932
923
  } catch {
933
924
  try {
934
- const wifiOut = await runShell(
935
- "iwgetid -r 2>/dev/null",
936
- 3e3
937
- );
925
+ const wifiOut = await runShell("iwgetid -r 2>/dev/null", 3e3);
938
926
  if (wifiOut) wifi = wifiOut;
939
927
  } catch {
940
928
  }
941
929
  }
942
930
  let ip;
943
931
  try {
944
- const ipOut = await runShell(
945
- "hostname -I | awk '{print $1}'",
946
- 3e3
947
- );
932
+ const ipOut = await runShell("hostname -I | awk '{print $1}'", 3e3);
948
933
  if (ipOut) ip = ipOut;
949
934
  } catch {
950
935
  }
@@ -1233,7 +1218,10 @@ var LinuxAdapter = class {
1233
1218
  let finalBuffer = imgBuffer;
1234
1219
  if (await commandExists("convert")) {
1235
1220
  try {
1236
- const resizedFile = join("/tmp", `pulso-screenshot-${ts}-resized.png`);
1221
+ const resizedFile = join(
1222
+ "/tmp",
1223
+ `pulso-screenshot-${ts}-resized.png`
1224
+ );
1237
1225
  await runShell(
1238
1226
  `convert '${tmpFile}' -resize '1280x>' '${resizedFile}'`,
1239
1227
  1e4
@@ -1447,9 +1435,7 @@ var LinuxAdapter = class {
1447
1435
  for (let i = 1; i <= steps; i++) {
1448
1436
  const cx = Math.round(fromX + (toX - fromX) * i / steps);
1449
1437
  const cy = Math.round(fromY + (toY - fromY) * i / steps);
1450
- await runShell(
1451
- `ydotool mousemove --absolute -x ${cx} -y ${cy}`
1452
- );
1438
+ await runShell(`ydotool mousemove --absolute -x ${cx} -y ${cy}`);
1453
1439
  await new Promise((r) => setTimeout(r, 20));
1454
1440
  }
1455
1441
  await runShell(`ydotool mouseup 0xC0`);
@@ -1464,9 +1450,7 @@ var LinuxAdapter = class {
1464
1450
  };
1465
1451
  }
1466
1452
  if (await commandExists("xdotool")) {
1467
- await runShell(
1468
- `xdotool mousemove ${fromX} ${fromY} mousedown 1`
1469
- );
1453
+ await runShell(`xdotool mousemove ${fromX} ${fromY} mousedown 1`);
1470
1454
  await new Promise((r) => setTimeout(r, 50));
1471
1455
  const steps = 10;
1472
1456
  for (let i = 1; i <= steps; i++) {
@@ -1538,10 +1522,7 @@ var LinuxAdapter = class {
1538
1522
  }
1539
1523
  if (await commandExists("xdotool")) {
1540
1524
  const safeText = text.replace(/'/g, "'\\''");
1541
- await runShell(
1542
- `xdotool type --clearmodifiers -- '${safeText}'`,
1543
- 1e4
1544
- );
1525
+ await runShell(`xdotool type --clearmodifiers -- '${safeText}'`, 1e4);
1545
1526
  return { success: true, data: { text, length: text.length } };
1546
1527
  }
1547
1528
  return {
@@ -1626,7 +1607,12 @@ var LinuxAdapter = class {
1626
1607
  await runShell(`ydotool key ${keyName}`);
1627
1608
  return {
1628
1609
  success: true,
1629
- data: { key, modifiers: modifiers || [], combo, note: "ydotool has limited modifier support" }
1610
+ data: {
1611
+ key,
1612
+ modifiers: modifiers || [],
1613
+ combo,
1614
+ note: "ydotool has limited modifier support"
1615
+ }
1630
1616
  };
1631
1617
  }
1632
1618
  return {
@@ -1724,9 +1710,7 @@ var LinuxAdapter = class {
1724
1710
  const page = tabs.find((t) => t.type === "page");
1725
1711
  if (page) {
1726
1712
  await this.cdpRequest(`/json/activate/${page.id}`, "GET");
1727
- await runShell(
1728
- `xdg-open '${url.replace(/'/g, "'\\''")}'`
1729
- );
1713
+ await runShell(`xdg-open '${url.replace(/'/g, "'\\''")}'`);
1730
1714
  return {
1731
1715
  success: true,
1732
1716
  data: { url, browser: browser || "default" }
@@ -1735,9 +1719,7 @@ var LinuxAdapter = class {
1735
1719
  throw new Error("No CDP page found");
1736
1720
  } catch {
1737
1721
  try {
1738
- await runShell(
1739
- `xdg-open '${url.replace(/'/g, "'\\''")}'`
1740
- );
1722
+ await runShell(`xdg-open '${url.replace(/'/g, "'\\''")}'`);
1741
1723
  return {
1742
1724
  success: true,
1743
1725
  data: { url, browser: browser || "default", method: "xdg-open" }
@@ -1773,9 +1755,7 @@ var LinuxAdapter = class {
1773
1755
  };
1774
1756
  } catch {
1775
1757
  try {
1776
- await runShell(
1777
- `xdg-open '${url.replace(/'/g, "'\\''")}'`
1778
- );
1758
+ await runShell(`xdg-open '${url.replace(/'/g, "'\\''")}'`);
1779
1759
  return {
1780
1760
  success: true,
1781
1761
  data: { url, browser: "default", method: "xdg-open" }
@@ -1841,7 +1821,10 @@ var LinuxAdapter = class {
1841
1821
  { browser: "Google Chrome", dir: `${home}/.config/google-chrome` },
1842
1822
  { browser: "Chromium", dir: `${home}/.config/chromium` },
1843
1823
  { browser: "Microsoft Edge", dir: `${home}/.config/microsoft-edge` },
1844
- { browser: "Brave Browser", dir: `${home}/.config/BraveSoftware/Brave-Browser` }
1824
+ {
1825
+ browser: "Brave Browser",
1826
+ dir: `${home}/.config/BraveSoftware/Brave-Browser`
1827
+ }
1845
1828
  ];
1846
1829
  const profiles = [];
1847
1830
  for (const { browser, dir } of browserPaths) {
@@ -2045,9 +2028,7 @@ ${body}`;
2045
2028
  if (method === "gmail") {
2046
2029
  try {
2047
2030
  const gmailUrl = `https://mail.google.com/mail/?view=cm&to=${encodeURIComponent(to)}&su=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
2048
- await runShell(
2049
- `xdg-open '${gmailUrl.replace(/'/g, "'\\''")}'`
2050
- );
2031
+ await runShell(`xdg-open '${gmailUrl.replace(/'/g, "'\\''")}'`);
2051
2032
  return {
2052
2033
  success: true,
2053
2034
  data: {
@@ -2066,9 +2047,7 @@ ${body}`;
2066
2047
  }
2067
2048
  try {
2068
2049
  const mailtoUrl = `mailto:${encodeURIComponent(to)}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
2069
- await runShell(
2070
- `xdg-open '${mailtoUrl.replace(/'/g, "'\\''")}'`
2071
- );
2050
+ await runShell(`xdg-open '${mailtoUrl.replace(/'/g, "'\\''")}'`);
2072
2051
  return {
2073
2052
  success: true,
2074
2053
  data: {
@@ -2241,9 +2220,7 @@ ${body}`;
2241
2220
  }
2242
2221
  if (await commandExists("gio")) {
2243
2222
  try {
2244
- await runShell(
2245
- `gio trash '${fullPath.replace(/'/g, "'\\''")}'`
2246
- );
2223
+ await runShell(`gio trash '${fullPath.replace(/'/g, "'\\''")}'`);
2247
2224
  return {
2248
2225
  success: true,
2249
2226
  data: { path, action: "moved_to_trash" }
@@ -2265,9 +2242,7 @@ ${body}`;
2265
2242
  }
2266
2243
  if (await commandExists("trash-put")) {
2267
2244
  try {
2268
- await runShell(
2269
- `trash-put '${fullPath.replace(/'/g, "'\\''")}'`
2270
- );
2245
+ await runShell(`trash-put '${fullPath.replace(/'/g, "'\\''")}'`);
2271
2246
  return {
2272
2247
  success: true,
2273
2248
  data: { path, action: "moved_to_trash" }
@@ -2500,9 +2475,7 @@ ${body}`;
2500
2475
  const gy = y !== void 0 ? y : -1;
2501
2476
  const gw = width !== void 0 ? width : -1;
2502
2477
  const gh = height !== void 0 ? height : -1;
2503
- await runShell(
2504
- `wmctrl -r '${safeApp}' -e 0,${gx},${gy},${gw},${gh}`
2505
- );
2478
+ await runShell(`wmctrl -r '${safeApp}' -e 0,${gx},${gy},${gw},${gh}`);
2506
2479
  return {
2507
2480
  success: true,
2508
2481
  data: { app, x, y, width, height }
@@ -2670,9 +2643,7 @@ ${body}`;
2670
2643
  `dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:'org.mpris.MediaPlayer2.Player' string:'Metadata'`,
2671
2644
  5e3
2672
2645
  );
2673
- const titleMatch = output2.match(
2674
- /xesam:title.*?string "([^"]+)"/
2675
- );
2646
+ const titleMatch = output2.match(/xesam:title.*?string "([^"]+)"/);
2676
2647
  const artistMatch = output2.match(
2677
2648
  /xesam:artist.*?string "([^"]+)"/
2678
2649
  );
@@ -2702,9 +2673,7 @@ ${body}`;
2702
2673
  return { success: false, error: "Missing search query" };
2703
2674
  }
2704
2675
  const uri = `spotify:search:${encodeURIComponent(query)}`;
2705
- await runShell(
2706
- `xdg-open '${uri}' 2>/dev/null &`
2707
- );
2676
+ await runShell(`xdg-open '${uri}' 2>/dev/null &`);
2708
2677
  return {
2709
2678
  success: true,
2710
2679
  data: {
@@ -2828,12 +2797,9 @@ ${body}`;
2828
2797
  if (!lightId) {
2829
2798
  return { success: false, error: `Light '${light}' not found` };
2830
2799
  }
2831
- const res = await hueRequest(
2832
- config,
2833
- `lights/${lightId}/state`,
2834
- "PUT",
2835
- { on: false }
2836
- );
2800
+ const res = await hueRequest(config, `lights/${lightId}/state`, "PUT", {
2801
+ on: false
2802
+ });
2837
2803
  return {
2838
2804
  success: true,
2839
2805
  data: { light: lightId, action: "off", response: res }
@@ -2860,15 +2826,10 @@ ${body}`;
2860
2826
  return { success: false, error: `Unrecognized color: ${color}` };
2861
2827
  }
2862
2828
  const [x, y] = rgbToXy(...rgb);
2863
- const res = await hueRequest(
2864
- config,
2865
- `lights/${lightId}/state`,
2866
- "PUT",
2867
- {
2868
- on: true,
2869
- xy: [x, y]
2870
- }
2871
- );
2829
+ const res = await hueRequest(config, `lights/${lightId}/state`, "PUT", {
2830
+ on: true,
2831
+ xy: [x, y]
2832
+ });
2872
2833
  return {
2873
2834
  success: true,
2874
2835
  data: { light: lightId, color, xy: [x, y], response: res }
@@ -2891,15 +2852,10 @@ ${body}`;
2891
2852
  return { success: false, error: `Light '${light}' not found` };
2892
2853
  }
2893
2854
  const bri = Math.max(1, Math.min(254, brightness));
2894
- const res = await hueRequest(
2895
- config,
2896
- `lights/${lightId}/state`,
2897
- "PUT",
2898
- {
2899
- on: true,
2900
- bri
2901
- }
2902
- );
2855
+ const res = await hueRequest(config, `lights/${lightId}/state`, "PUT", {
2856
+ on: true,
2857
+ bri
2858
+ });
2903
2859
  return {
2904
2860
  success: true,
2905
2861
  data: { light: lightId, brightness: bri, response: res }
@@ -2932,14 +2888,9 @@ ${body}`;
2932
2888
  error: `Scene '${scene}' not found. Available: ${Object.values(scenes).map((s) => s.name).join(", ")}`
2933
2889
  };
2934
2890
  }
2935
- const res = await hueRequest(
2936
- config,
2937
- `groups/${groupId}/action`,
2938
- "PUT",
2939
- {
2940
- scene: sceneId
2941
- }
2942
- );
2891
+ const res = await hueRequest(config, `groups/${groupId}/action`, "PUT", {
2892
+ scene: sceneId
2893
+ });
2943
2894
  return {
2944
2895
  success: true,
2945
2896
  data: { scene, sceneId, group: groupId, response: res }
@@ -3004,10 +2955,7 @@ ${body}`;
3004
2955
  error: "Sonos not configured. Set SONOS_API_URL environment variable (e.g., http://localhost:5005)."
3005
2956
  };
3006
2957
  }
3007
- const res = await sonosRequest(
3008
- url,
3009
- `${encodeURIComponent(room)}/play`
3010
- );
2958
+ const res = await sonosRequest(url, `${encodeURIComponent(room)}/play`);
3011
2959
  return {
3012
2960
  success: true,
3013
2961
  data: { room, action: "play", response: res }
@@ -3022,12 +2970,8 @@ ${body}`;
3022
2970
  async sonosPause(room) {
3023
2971
  try {
3024
2972
  const url = getSonosApiUrl();
3025
- if (!url)
3026
- return { success: false, error: "Sonos not configured." };
3027
- const res = await sonosRequest(
3028
- url,
3029
- `${encodeURIComponent(room)}/pause`
3030
- );
2973
+ if (!url) return { success: false, error: "Sonos not configured." };
2974
+ const res = await sonosRequest(url, `${encodeURIComponent(room)}/pause`);
3031
2975
  return {
3032
2976
  success: true,
3033
2977
  data: { room, action: "pause", response: res }
@@ -3042,8 +2986,7 @@ ${body}`;
3042
2986
  async sonosVolume(room, level) {
3043
2987
  try {
3044
2988
  const url = getSonosApiUrl();
3045
- if (!url)
3046
- return { success: false, error: "Sonos not configured." };
2989
+ if (!url) return { success: false, error: "Sonos not configured." };
3047
2990
  const vol = Math.max(0, Math.min(100, level));
3048
2991
  const res = await sonosRequest(
3049
2992
  url,
@@ -3063,8 +3006,7 @@ ${body}`;
3063
3006
  async sonosPlayUri(room, uri, title) {
3064
3007
  try {
3065
3008
  const url = getSonosApiUrl();
3066
- if (!url)
3067
- return { success: false, error: "Sonos not configured." };
3009
+ if (!url) return { success: false, error: "Sonos not configured." };
3068
3010
  if (uri.startsWith("spotify:")) {
3069
3011
  const res2 = await sonosRequest(
3070
3012
  url,
@@ -3090,8 +3032,7 @@ ${body}`;
3090
3032
  async sonosRooms() {
3091
3033
  try {
3092
3034
  const url = getSonosApiUrl();
3093
- if (!url)
3094
- return { success: false, error: "Sonos not configured." };
3035
+ if (!url) return { success: false, error: "Sonos not configured." };
3095
3036
  const res = await sonosRequest(url, "zones");
3096
3037
  return { success: true, data: res };
3097
3038
  } catch (err) {
@@ -3104,12 +3045,8 @@ ${body}`;
3104
3045
  async sonosNext(room) {
3105
3046
  try {
3106
3047
  const url = getSonosApiUrl();
3107
- if (!url)
3108
- return { success: false, error: "Sonos not configured." };
3109
- const res = await sonosRequest(
3110
- url,
3111
- `${encodeURIComponent(room)}/next`
3112
- );
3048
+ if (!url) return { success: false, error: "Sonos not configured." };
3049
+ const res = await sonosRequest(url, `${encodeURIComponent(room)}/next`);
3113
3050
  return {
3114
3051
  success: true,
3115
3052
  data: { room, action: "next", response: res }
@@ -3124,8 +3061,7 @@ ${body}`;
3124
3061
  async sonosPrevious(room) {
3125
3062
  try {
3126
3063
  const url = getSonosApiUrl();
3127
- if (!url)
3128
- return { success: false, error: "Sonos not configured." };
3064
+ if (!url) return { success: false, error: "Sonos not configured." };
3129
3065
  const res = await sonosRequest(
3130
3066
  url,
3131
3067
  `${encodeURIComponent(room)}/previous`
@@ -3144,12 +3080,8 @@ ${body}`;
3144
3080
  async sonosNowPlaying(room) {
3145
3081
  try {
3146
3082
  const url = getSonosApiUrl();
3147
- if (!url)
3148
- return { success: false, error: "Sonos not configured." };
3149
- const res = await sonosRequest(
3150
- url,
3151
- `${encodeURIComponent(room)}/state`
3152
- );
3083
+ if (!url) return { success: false, error: "Sonos not configured." };
3084
+ const res = await sonosRequest(url, `${encodeURIComponent(room)}/state`);
3153
3085
  return {
3154
3086
  success: true,
3155
3087
  data: { room, ...res }
@@ -3199,7 +3131,8 @@ async function loadKokoro() {
3199
3131
  console.log(" \u{1F399}\uFE0F Loading Kokoro TTS (~83MB, first use only)...");
3200
3132
  const mod = await import("./kokoro-UIHMLMG3.js");
3201
3133
  const KokoroTTS = mod.KokoroTTS ?? mod.default?.KokoroTTS;
3202
- if (!KokoroTTS) throw new Error("KokoroTTS class not found in kokoro-js export");
3134
+ if (!KokoroTTS)
3135
+ throw new Error("KokoroTTS class not found in kokoro-js export");
3203
3136
  kokoroTts = await KokoroTTS.from_pretrained(KOKORO_MODEL, {
3204
3137
  dtype: KOKORO_DTYPE,
3205
3138
  device: "cpu"
@@ -3209,7 +3142,10 @@ async function loadKokoro() {
3209
3142
  } catch (err) {
3210
3143
  kokoroState = "failed";
3211
3144
  const msg = err.message ?? String(err);
3212
- console.warn(" \u2139\uFE0F Kokoro TTS unavailable (optional):", msg.slice(0, 120));
3145
+ console.warn(
3146
+ " \u2139\uFE0F Kokoro TTS unavailable (optional):",
3147
+ msg.slice(0, 120)
3148
+ );
3213
3149
  }
3214
3150
  })();
3215
3151
  return kokoroLoadPromise;
@@ -3342,12 +3278,18 @@ async function speakKokoro(text, voice) {
3342
3278
  const tmpFile = join2(tmpdir(), `pulso-tts-${Date.now()}.wav`);
3343
3279
  try {
3344
3280
  const result = await kokoroTts.generate(text.slice(0, 500), { voice });
3345
- const wav = float32ToWav(result.audio, result.sampling_rate);
3281
+ const wav = float32ToWav(
3282
+ result.audio,
3283
+ result.sampling_rate
3284
+ );
3346
3285
  writeFileSync2(tmpFile, wav);
3347
3286
  await playWavFile(tmpFile);
3348
3287
  return true;
3349
3288
  } catch (err) {
3350
- console.warn(" \u26A0\uFE0F Kokoro speak failed:", err.message.slice(0, 100));
3289
+ console.warn(
3290
+ " \u26A0\uFE0F Kokoro speak failed:",
3291
+ err.message.slice(0, 100)
3292
+ );
3351
3293
  return false;
3352
3294
  } finally {
3353
3295
  try {
@@ -3368,7 +3310,8 @@ function playWavFile(filePath) {
3368
3310
  cmd = `aplay "${filePath}" 2>/dev/null || paplay "${filePath}" 2>/dev/null || ffplay -nodisp -autoexit "${filePath}" 2>/dev/null || true`;
3369
3311
  }
3370
3312
  exec2(cmd, (err) => {
3371
- if (err) console.warn(" \u26A0\uFE0F Audio playback error:", err.message.slice(0, 80));
3313
+ if (err)
3314
+ console.warn(" \u26A0\uFE0F Audio playback error:", err.message.slice(0, 80));
3372
3315
  resolve5();
3373
3316
  });
3374
3317
  });
@@ -3394,7 +3337,11 @@ async function speak(text, opts = {}) {
3394
3337
  }
3395
3338
  function getTTSInfo() {
3396
3339
  if (isKokoroReady()) {
3397
- return { engine: "kokoro", voice: DEFAULT_KOKORO_VOICE, model: KOKORO_MODEL };
3340
+ return {
3341
+ engine: "kokoro",
3342
+ voice: DEFAULT_KOKORO_VOICE,
3343
+ model: KOKORO_MODEL
3344
+ };
3398
3345
  }
3399
3346
  if (process.platform === "darwin") {
3400
3347
  return { engine: "native", voice: getBestMacVoice() };
@@ -3461,27 +3408,27 @@ function runAppleScript(script) {
3461
3408
  return new Promise((resolve5, reject) => {
3462
3409
  const tmpPath = `/tmp/pulso-as-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.scpt`;
3463
3410
  writeFileSync3(tmpPath, script, "utf-8");
3464
- exec3(
3465
- `osascript ${tmpPath}`,
3466
- { timeout: 15e3 },
3467
- (err, stdout, stderr) => {
3468
- try {
3469
- unlinkSync3(tmpPath);
3470
- } catch {
3471
- }
3472
- if (err) reject(new Error(stderr || err.message));
3473
- else resolve5(stdout.trim());
3411
+ exec3(`osascript ${tmpPath}`, { timeout: 15e3 }, (err, stdout, stderr) => {
3412
+ try {
3413
+ unlinkSync3(tmpPath);
3414
+ } catch {
3474
3415
  }
3475
- );
3416
+ if (err) reject(new Error(stderr || err.message));
3417
+ else resolve5(stdout.trim());
3418
+ });
3476
3419
  });
3477
3420
  }
3478
3421
  function runShell2(cmd, timeout = 1e4) {
3479
3422
  return new Promise((resolve5, reject) => {
3480
3423
  const shell = process.env.SHELL || "/bin/zsh";
3481
- exec3(cmd, { timeout, shell, env: { ...process.env, PATH: augmentedPath() } }, (err, stdout, stderr) => {
3482
- if (err) reject(new Error(stderr || err.message));
3483
- else resolve5(stdout.trim());
3484
- });
3424
+ exec3(
3425
+ cmd,
3426
+ { timeout, shell, env: { ...process.env, PATH: augmentedPath() } },
3427
+ (err, stdout, stderr) => {
3428
+ if (err) reject(new Error(stderr || err.message));
3429
+ else resolve5(stdout.trim());
3430
+ }
3431
+ );
3485
3432
  });
3486
3433
  }
3487
3434
  function runSwift(code, timeout = 1e4) {
@@ -3496,11 +3443,14 @@ function runSwift(code, timeout = 1e4) {
3496
3443
  }
3497
3444
  async function hasScreenRecordingPermission() {
3498
3445
  try {
3499
- const out = await runSwift(`
3446
+ const out = await runSwift(
3447
+ `
3500
3448
  import Cocoa
3501
3449
  import CoreGraphics
3502
3450
  print(CGPreflightScreenCaptureAccess() ? "granted" : "denied")
3503
- `, 6e3);
3451
+ `,
3452
+ 6e3
3453
+ );
3504
3454
  return out.trim().toLowerCase() === "granted";
3505
3455
  } catch {
3506
3456
  return false;
@@ -3513,7 +3463,8 @@ function safePath2(relative) {
3513
3463
  if (accessLevel === "full") return full;
3514
3464
  const relFromHome = full.slice(HOME2.length + 1);
3515
3465
  const topDir = relFromHome.split("/")[0];
3516
- if (!topDir || !SAFE_DIRS2.some((d) => topDir.toLowerCase() === d.toLowerCase())) return null;
3466
+ if (!topDir || !SAFE_DIRS2.some((d) => topDir.toLowerCase() === d.toLowerCase()))
3467
+ return null;
3517
3468
  return full;
3518
3469
  }
3519
3470
  var MacOSAdapter = class {
@@ -3534,7 +3485,10 @@ var MacOSAdapter = class {
3534
3485
  try {
3535
3486
  await runShell2(`open -a "${app}"`);
3536
3487
  } catch (e) {
3537
- return { success: false, error: `Failed to open "${app}": ${e.message}` };
3488
+ return {
3489
+ success: false,
3490
+ error: `Failed to open "${app}": ${e.message}`
3491
+ };
3538
3492
  }
3539
3493
  let launched = false;
3540
3494
  for (let i = 0; i < 10; i++) {
@@ -3604,7 +3558,8 @@ var MacOSAdapter = class {
3604
3558
  }
3605
3559
  }
3606
3560
  async notification(title, message) {
3607
- if (!title || !message) return { success: false, error: "Missing title or message" };
3561
+ if (!title || !message)
3562
+ return { success: false, error: "Missing title or message" };
3608
3563
  await runAppleScript(
3609
3564
  `display notification "${message.replace(/"/g, '\\"')}" with title "${title.replace(/"/g, '\\"')}"`
3610
3565
  );
@@ -3621,10 +3576,18 @@ var MacOSAdapter = class {
3621
3576
  }
3622
3577
  async getBrightness() {
3623
3578
  try {
3624
- const raw = await runShell2("brightness -l 2>/dev/null | grep brightness | head -1 | awk '{print $NF}'");
3579
+ const raw = await runShell2(
3580
+ "brightness -l 2>/dev/null | grep brightness | head -1 | awk '{print $NF}'"
3581
+ );
3625
3582
  return { success: true, data: { brightness: parseFloat(raw) || 0.5 } };
3626
3583
  } catch {
3627
- return { success: true, data: { brightness: "unknown", note: "Install 'brightness' via brew for control" } };
3584
+ return {
3585
+ success: true,
3586
+ data: {
3587
+ brightness: "unknown",
3588
+ note: "Install 'brightness' via brew for control"
3589
+ }
3590
+ };
3628
3591
  }
3629
3592
  }
3630
3593
  async setBrightness(level) {
@@ -3641,7 +3604,10 @@ var MacOSAdapter = class {
3641
3604
  ["os", "sw_vers -productVersion"],
3642
3605
  ["cpu", "sysctl -n machdep.cpu.brand_string"],
3643
3606
  ["memory", "vm_stat | head -5"],
3644
- ["disk", `df -h / | tail -1 | awk '{print $3 " used / " $2 " total (" $5 " used)"}'`],
3607
+ [
3608
+ "disk",
3609
+ `df -h / | tail -1 | awk '{print $3 " used / " $2 " total (" $5 " used)"}'`
3610
+ ],
3645
3611
  ["uptime", "uptime | sed 's/.*up /up /' | sed 's/,.*//'"],
3646
3612
  ["battery", "pmset -g batt | grep -Eo '\\d+%'"],
3647
3613
  ["wifi", "networksetup -getairportnetwork en0 2>/dev/null | cut -d: -f2"],
@@ -3676,27 +3642,51 @@ var MacOSAdapter = class {
3676
3642
  const t = Number(timeout) || 15e3;
3677
3643
  try {
3678
3644
  const output2 = await runShell2(command, t);
3679
- return { success: true, data: { command, output: output2.slice(0, 1e4), truncated: output2.length > 1e4 } };
3645
+ return {
3646
+ success: true,
3647
+ data: {
3648
+ command,
3649
+ output: output2.slice(0, 1e4),
3650
+ truncated: output2.length > 1e4
3651
+ }
3652
+ };
3680
3653
  } catch (err) {
3681
- return { success: false, error: `Shell error: ${err.message.slice(0, 2e3)}` };
3654
+ return {
3655
+ success: false,
3656
+ error: `Shell error: ${err.message.slice(0, 2e3)}`
3657
+ };
3682
3658
  }
3683
3659
  }
3684
3660
  async runShortcut(name, input2) {
3685
3661
  if (!name) return { success: false, error: "Missing shortcut name" };
3686
3662
  const inputFlag = input2 ? `--input-type text --input "${input2.replace(/"/g, '\\"')}"` : "";
3687
- const result = await runShell2(`shortcuts run "${name.replace(/"/g, '\\"')}" ${inputFlag}`, 3e4);
3688
- return { success: true, data: { shortcut: name, output: result || "Shortcut executed" } };
3663
+ const result = await runShell2(
3664
+ `shortcuts run "${name.replace(/"/g, '\\"')}" ${inputFlag}`,
3665
+ 3e4
3666
+ );
3667
+ return {
3668
+ success: true,
3669
+ data: { shortcut: name, output: result || "Shortcut executed" }
3670
+ };
3689
3671
  }
3690
3672
  async dnd(enabled) {
3691
3673
  if (enabled !== void 0) {
3692
3674
  try {
3693
- await runShell2(`shortcuts run "Toggle Do Not Disturb" 2>/dev/null || osascript -e 'do shell script "defaults write com.apple.ncprefs dnd_prefs -data 0"'`);
3675
+ await runShell2(
3676
+ `shortcuts run "Toggle Do Not Disturb" 2>/dev/null || osascript -e 'do shell script "defaults write com.apple.ncprefs dnd_prefs -data 0"'`
3677
+ );
3694
3678
  return { success: true, data: { dnd: enabled, note: "DND toggled" } };
3695
3679
  } catch {
3696
- return { success: true, data: { dnd: enabled, note: "Set DND manually in Control Center" } };
3680
+ return {
3681
+ success: true,
3682
+ data: { dnd: enabled, note: "Set DND manually in Control Center" }
3683
+ };
3697
3684
  }
3698
3685
  }
3699
- return { success: true, data: { note: "Pass enabled: true/false to toggle DND" } };
3686
+ return {
3687
+ success: true,
3688
+ data: { note: "Pass enabled: true/false to toggle DND" }
3689
+ };
3700
3690
  }
3701
3691
  /* ══════════════════════════════════════════════════════════
3702
3692
  * Clipboard
@@ -3730,22 +3720,37 @@ var MacOSAdapter = class {
3730
3720
  } catch (ssErr) {
3731
3721
  const msg = ssErr.message || "";
3732
3722
  if (msg.includes("could not create image") || msg.includes("display")) {
3733
- return { image: "", format: "jpeg", note: "Screen Recording permission required." };
3723
+ return {
3724
+ image: "",
3725
+ format: "jpeg",
3726
+ note: "Screen Recording permission required."
3727
+ };
3734
3728
  }
3735
3729
  return { image: "", format: "jpeg", note: `Screenshot failed: ${msg}` };
3736
3730
  }
3737
3731
  if (!existsSync2(pngPath)) {
3738
- return { image: "", format: "jpeg", note: "Screenshot failed \u2014 Screen Recording permission needed." };
3732
+ return {
3733
+ image: "",
3734
+ format: "jpeg",
3735
+ note: "Screenshot failed \u2014 Screen Recording permission needed."
3736
+ };
3739
3737
  }
3740
3738
  try {
3741
- await runShell2(`sips --resampleWidth 1600 --setProperty format jpeg --setProperty formatOptions 75 ${pngPath} --out ${jpgPath}`, 1e4);
3739
+ await runShell2(
3740
+ `sips --resampleWidth 1600 --setProperty format jpeg --setProperty formatOptions 75 ${pngPath} --out ${jpgPath}`,
3741
+ 1e4
3742
+ );
3742
3743
  } catch {
3743
3744
  const buf2 = readFileSync2(pngPath);
3744
3745
  try {
3745
3746
  unlinkSync3(pngPath);
3746
3747
  } catch {
3747
3748
  }
3748
- return { image: `data:image/png;base64,${buf2.toString("base64")}`, format: "png", note: "Full screen screenshot (PNG fallback)" };
3749
+ return {
3750
+ image: `data:image/png;base64,${buf2.toString("base64")}`,
3751
+ format: "png",
3752
+ note: "Full screen screenshot (PNG fallback)"
3753
+ };
3749
3754
  }
3750
3755
  const buf = readFileSync2(jpgPath);
3751
3756
  const base64 = buf.toString("base64");
@@ -3780,7 +3785,8 @@ print("\\(Int(main.frame.width)),\\(Int(main.frame.height))")`);
3780
3785
  }
3781
3786
  async mouseClick(x, y, button) {
3782
3787
  const btn = button || "left";
3783
- if (isNaN(x) || isNaN(y)) return { success: false, error: "Missing x, y coordinates" };
3788
+ if (isNaN(x) || isNaN(y))
3789
+ return { success: false, error: "Missing x, y coordinates" };
3784
3790
  const mouseType = btn === "right" ? "rightMouseDown" : "leftMouseDown";
3785
3791
  const mouseTypeUp = btn === "right" ? "rightMouseUp" : "leftMouseUp";
3786
3792
  const mouseButton = btn === "right" ? ".right" : ".left";
@@ -3797,7 +3803,8 @@ print("clicked")`;
3797
3803
  return { success: true, data: { clicked: { x, y }, button: btn } };
3798
3804
  }
3799
3805
  async mouseDoubleClick(x, y) {
3800
- if (isNaN(x) || isNaN(y)) return { success: false, error: "Missing x, y coordinates" };
3806
+ if (isNaN(x) || isNaN(y))
3807
+ return { success: false, error: "Missing x, y coordinates" };
3801
3808
  const swift = `
3802
3809
  import Cocoa
3803
3810
  let p = CGPoint(x: ${x}, y: ${y})
@@ -3823,7 +3830,8 @@ print("double-clicked")`;
3823
3830
  async mouseScroll(scrollY, scrollX, x, y) {
3824
3831
  const sx = scrollX || 0;
3825
3832
  const sy = scrollY || 0;
3826
- if (!sy && !sx) return { success: false, error: "Missing scrollY or scrollX" };
3833
+ if (!sy && !sx)
3834
+ return { success: false, error: "Missing scrollY or scrollX" };
3827
3835
  const swift = `
3828
3836
  import Cocoa
3829
3837
  let p = CGPoint(x: ${x || 0}, y: ${y || 0})
@@ -3834,10 +3842,14 @@ let scroll = CGEvent(scrollWheelEvent2Source: nil, units: .pixel, wheelCount: 2,
3834
3842
  scroll.post(tap: .cghidEventTap)
3835
3843
  print("scrolled")`;
3836
3844
  await runSwift(swift);
3837
- return { success: true, data: { scrolled: { x: x || 0, y: y || 0, scrollY: sy, scrollX: sx } } };
3845
+ return {
3846
+ success: true,
3847
+ data: { scrolled: { x: x || 0, y: y || 0, scrollY: sy, scrollX: sx } }
3848
+ };
3838
3849
  }
3839
3850
  async mouseMove(x, y) {
3840
- if (isNaN(x) || isNaN(y)) return { success: false, error: "Missing x, y coordinates" };
3851
+ if (isNaN(x) || isNaN(y))
3852
+ return { success: false, error: "Missing x, y coordinates" };
3841
3853
  const swift = `
3842
3854
  import Cocoa
3843
3855
  let p = CGPoint(x: ${x}, y: ${y})
@@ -3869,7 +3881,12 @@ let u = CGEvent(mouseEventSource: nil, mouseType: .leftMouseUp, mouseCursorPosit
3869
3881
  u.post(tap: .cghidEventTap)
3870
3882
  print("dragged")`;
3871
3883
  await runSwift(swift);
3872
- return { success: true, data: { dragged: { from: { x: fromX, y: fromY }, to: { x: toX, y: toY } } } };
3884
+ return {
3885
+ success: true,
3886
+ data: {
3887
+ dragged: { from: { x: fromX, y: fromY }, to: { x: toX, y: toY } }
3888
+ }
3889
+ };
3873
3890
  }
3874
3891
  async getCursorPosition() {
3875
3892
  const swift = `
@@ -3937,12 +3954,19 @@ print("\\(x),\\(y)")`;
3937
3954
  const keyCode = keyCodeMap[key.toLowerCase()];
3938
3955
  if (keyCode !== void 0) {
3939
3956
  const using = modStr.length > 0 ? ` using {${modStr.join(", ")}}` : "";
3940
- await runAppleScript(`tell application "System Events" to key code ${keyCode}${using}`);
3957
+ await runAppleScript(
3958
+ `tell application "System Events" to key code ${keyCode}${using}`
3959
+ );
3941
3960
  } else if (key.length === 1) {
3942
3961
  const using = modStr.length > 0 ? ` using {${modStr.join(", ")}}` : "";
3943
- await runAppleScript(`tell application "System Events" to keystroke "${key}"${using}`);
3962
+ await runAppleScript(
3963
+ `tell application "System Events" to keystroke "${key}"${using}`
3964
+ );
3944
3965
  } else {
3945
- return { success: false, error: `Unknown key: ${key}. Use single characters or: enter, tab, escape, delete, space, up, down, left, right, f1-f12, home, end, pageup, pagedown` };
3966
+ return {
3967
+ success: false,
3968
+ error: `Unknown key: ${key}. Use single characters or: enter, tab, escape, delete, space, up, down, left, right, f1-f12, home, end, pageup, pagedown`
3969
+ };
3946
3970
  }
3947
3971
  return { success: true, data: { pressed: key, modifiers: mods } };
3948
3972
  }
@@ -3950,7 +3974,13 @@ print("\\(x),\\(y)")`;
3950
3974
  * Browser Automation
3951
3975
  * ══════════════════════════════════════════════════════════ */
3952
3976
  async browserListTabs() {
3953
- const browsers = ["Google Chrome", "Safari", "Arc", "Firefox", "Microsoft Edge"];
3977
+ const browsers = [
3978
+ "Google Chrome",
3979
+ "Safari",
3980
+ "Arc",
3981
+ "Firefox",
3982
+ "Microsoft Edge"
3983
+ ];
3954
3984
  const allTabs = [];
3955
3985
  for (const browser of browsers) {
3956
3986
  try {
@@ -3974,7 +4004,12 @@ print("\\(x),\\(y)")`;
3974
4004
  const tabStr = rest.join("~~~");
3975
4005
  const pairs = tabStr.split("|||").filter(Boolean);
3976
4006
  for (let i = 0; i < pairs.length - 1; i += 2) {
3977
- allTabs.push({ browser: "Safari", title: pairs[i], url: pairs[i + 1], active: pairs[i + 1] === activeURL.trim() });
4007
+ allTabs.push({
4008
+ browser: "Safari",
4009
+ title: pairs[i],
4010
+ url: pairs[i + 1],
4011
+ active: pairs[i + 1] === activeURL.trim()
4012
+ });
3978
4013
  }
3979
4014
  } else {
3980
4015
  const tabData = await runAppleScript(`
@@ -3992,7 +4027,12 @@ print("\\(x),\\(y)")`;
3992
4027
  const tabStr = rest.join("~~~");
3993
4028
  const pairs = tabStr.split("|||").filter(Boolean);
3994
4029
  for (let i = 0; i < pairs.length - 1; i += 2) {
3995
- allTabs.push({ browser, title: pairs[i], url: pairs[i + 1], active: pairs[i + 1] === activeURL.trim() });
4030
+ allTabs.push({
4031
+ browser,
4032
+ title: pairs[i],
4033
+ url: pairs[i + 1],
4034
+ active: pairs[i + 1] === activeURL.trim()
4035
+ });
3996
4036
  }
3997
4037
  }
3998
4038
  } catch {
@@ -4020,7 +4060,10 @@ print("\\(x),\\(y)")`;
4020
4060
  }
4021
4061
  return { success: true, data: { navigated: url, browser: b } };
4022
4062
  } catch (err) {
4023
- return { success: false, error: `Failed to navigate: ${err.message}` };
4063
+ return {
4064
+ success: false,
4065
+ error: `Failed to navigate: ${err.message}`
4066
+ };
4024
4067
  }
4025
4068
  }
4026
4069
  async browserNewTab(url, browser) {
@@ -4043,7 +4086,10 @@ print("\\(x),\\(y)")`;
4043
4086
  }
4044
4087
  return { success: true, data: { opened: url, browser: b } };
4045
4088
  } catch (err) {
4046
- return { success: false, error: `Failed to open window: ${err.message}` };
4089
+ return {
4090
+ success: false,
4091
+ error: `Failed to open window: ${err.message}`
4092
+ };
4047
4093
  }
4048
4094
  }
4049
4095
  async browserReadPage(browser, maxLength) {
@@ -4069,11 +4115,17 @@ print("\\(x),\\(y)")`;
4069
4115
  } catch {
4070
4116
  try {
4071
4117
  const savedClipboard = await runShell2("pbpaste 2>/dev/null || true");
4072
- await runAppleScript(`tell application "${b.replace(/"/g, '\\"')}" to activate`);
4118
+ await runAppleScript(
4119
+ `tell application "${b.replace(/"/g, '\\"')}" to activate`
4120
+ );
4073
4121
  await new Promise((r) => setTimeout(r, 300));
4074
- await runAppleScript('tell application "System Events" to keystroke "a" using command down');
4122
+ await runAppleScript(
4123
+ 'tell application "System Events" to keystroke "a" using command down'
4124
+ );
4075
4125
  await new Promise((r) => setTimeout(r, 200));
4076
- await runAppleScript('tell application "System Events" to keystroke "c" using command down');
4126
+ await runAppleScript(
4127
+ 'tell application "System Events" to keystroke "c" using command down'
4128
+ );
4077
4129
  await new Promise((r) => setTimeout(r, 300));
4078
4130
  content = await runShell2("pbpaste");
4079
4131
  method = "clipboard";
@@ -4082,18 +4134,29 @@ print("\\(x),\\(y)")`;
4082
4134
  execSync2(`echo ${JSON.stringify(savedClipboard)} | pbcopy`);
4083
4135
  }
4084
4136
  } catch (clipErr) {
4085
- return { success: false, error: `Could not read page: ${clipErr.message}` };
4137
+ return {
4138
+ success: false,
4139
+ error: `Could not read page: ${clipErr.message}`
4140
+ };
4086
4141
  }
4087
4142
  }
4088
4143
  let pageUrl = "";
4089
4144
  let pageTitle = "";
4090
4145
  try {
4091
4146
  if (b === "Safari") {
4092
- pageUrl = await runAppleScript('tell application "Safari" to return URL of front document');
4093
- pageTitle = await runAppleScript('tell application "Safari" to return name of front document');
4147
+ pageUrl = await runAppleScript(
4148
+ 'tell application "Safari" to return URL of front document'
4149
+ );
4150
+ pageTitle = await runAppleScript(
4151
+ 'tell application "Safari" to return name of front document'
4152
+ );
4094
4153
  } else {
4095
- pageUrl = await runAppleScript(`tell application "${b.replace(/"/g, '\\"')}" to return URL of active tab of front window`);
4096
- pageTitle = await runAppleScript(`tell application "${b.replace(/"/g, '\\"')}" to return title of active tab of front window`);
4154
+ pageUrl = await runAppleScript(
4155
+ `tell application "${b.replace(/"/g, '\\"')}" to return URL of active tab of front window`
4156
+ );
4157
+ pageTitle = await runAppleScript(
4158
+ `tell application "${b.replace(/"/g, '\\"')}" to return title of active tab of front window`
4159
+ );
4097
4160
  }
4098
4161
  } catch {
4099
4162
  }
@@ -4133,21 +4196,48 @@ end tell`);
4133
4196
  }
4134
4197
  return { success: true, data: { result: (result || "").slice(0, 5e3) } };
4135
4198
  } catch (err) {
4136
- return { success: false, error: `JS execution failed: ${err.message}` };
4199
+ return {
4200
+ success: false,
4201
+ error: `JS execution failed: ${err.message}`
4202
+ };
4137
4203
  }
4138
4204
  }
4139
4205
  async browserListProfiles() {
4140
4206
  const os = homedir2();
4141
4207
  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` },
4208
+ {
4209
+ browser: "Google Chrome",
4210
+ dir: `${os}/Library/Application Support/Google/Chrome`
4211
+ },
4212
+ {
4213
+ browser: "Google Chrome Beta",
4214
+ dir: `${os}/Library/Application Support/Google/Chrome Beta`
4215
+ },
4216
+ {
4217
+ browser: "Google Chrome Dev",
4218
+ dir: `${os}/Library/Application Support/Google/Chrome Dev`
4219
+ },
4220
+ {
4221
+ browser: "Microsoft Edge",
4222
+ dir: `${os}/Library/Application Support/Microsoft Edge`
4223
+ },
4224
+ {
4225
+ browser: "Brave Browser",
4226
+ dir: `${os}/Library/Application Support/BraveSoftware/Brave-Browser`
4227
+ },
4228
+ {
4229
+ browser: "Opera",
4230
+ dir: `${os}/Library/Application Support/com.operasoftware.Opera`
4231
+ },
4148
4232
  { 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` }
4233
+ {
4234
+ browser: "Arc",
4235
+ dir: `${os}/Library/Application Support/Arc/User Data`
4236
+ },
4237
+ {
4238
+ browser: "Chromium",
4239
+ dir: `${os}/Library/Application Support/Chromium`
4240
+ }
4151
4241
  ];
4152
4242
  const profiles = [];
4153
4243
  for (const { browser, dir } of browserPaths) {
@@ -4204,7 +4294,9 @@ end tell`);
4204
4294
  }
4205
4295
  let running = [];
4206
4296
  try {
4207
- const ps = await runShell2("ps aux | grep -E '(Chrome|Edge|Brave|Firefox|Opera|Vivaldi|Arc)' | grep -v grep | awk '{print $11}'");
4297
+ const ps = await runShell2(
4298
+ "ps aux | grep -E '(Chrome|Edge|Brave|Firefox|Opera|Vivaldi|Arc)' | grep -v grep | awk '{print $11}'"
4299
+ );
4208
4300
  running = ps.split("\n").filter(Boolean);
4209
4301
  } catch {
4210
4302
  }
@@ -4213,7 +4305,9 @@ end tell`);
4213
4305
  data: {
4214
4306
  profiles: profiles.map((p) => ({
4215
4307
  ...p,
4216
- isRunning: running.some((r) => r.toLowerCase().includes(p.browser.toLowerCase().replace(/ /g, "")))
4308
+ isRunning: running.some(
4309
+ (r) => r.toLowerCase().includes(p.browser.toLowerCase().replace(/ /g, ""))
4310
+ )
4217
4311
  })),
4218
4312
  total: profiles.length
4219
4313
  }
@@ -4244,15 +4338,27 @@ end tell`);
4244
4338
  const raw = await runAppleScript(script);
4245
4339
  return raw.split("\n").filter(Boolean).map((line) => {
4246
4340
  const [cal, summary, start, end] = line.split(" | ");
4247
- return { calendar: cal?.trim(), title: summary?.trim() || "", startDate: start?.trim() || "", endDate: end?.trim() };
4341
+ return {
4342
+ calendar: cal?.trim(),
4343
+ title: summary?.trim() || "",
4344
+ startDate: start?.trim() || "",
4345
+ endDate: end?.trim()
4346
+ };
4248
4347
  });
4249
4348
  }
4250
4349
  async calendarCreate(title, startDate, endDate, calendar, notes) {
4251
- if (!title || !startDate) return { success: false, error: "Missing title or start date" };
4350
+ if (!title || !startDate)
4351
+ return { success: false, error: "Missing title or start date" };
4252
4352
  const parseDate = (iso) => {
4253
4353
  const d = new Date(iso);
4254
4354
  if (isNaN(d.getTime())) return null;
4255
- return { y: d.getFullYear(), mo: d.getMonth() + 1, d: d.getDate(), h: d.getHours(), mi: d.getMinutes() };
4355
+ return {
4356
+ y: d.getFullYear(),
4357
+ mo: d.getMonth() + 1,
4358
+ d: d.getDate(),
4359
+ h: d.getHours(),
4360
+ mi: d.getMinutes()
4361
+ };
4256
4362
  };
4257
4363
  const buildDateScript = (varName, iso) => {
4258
4364
  const p = parseDate(iso);
@@ -4268,7 +4374,8 @@ tell ${varName}
4268
4374
  end tell`;
4269
4375
  };
4270
4376
  const startDateScript = buildDateScript("startD", startDate);
4271
- if (!startDateScript) return { success: false, error: `Invalid start date: ${startDate}` };
4377
+ if (!startDateScript)
4378
+ return { success: false, error: `Invalid start date: ${startDate}` };
4272
4379
  const endDateScript = endDate ? buildDateScript("endD", endDate) : "";
4273
4380
  const calTarget = calendar ? `calendar "${calendar.replace(/"/g, '\\"')}"` : "default calendar";
4274
4381
  const notesPart = notes ? `
@@ -4287,7 +4394,10 @@ tell application "Calendar"
4287
4394
  set newEvent to make new event with properties {summary:"${title.replace(/"/g, '\\"')}", start date:startD}${endPart}${notesPart}
4288
4395
  end tell
4289
4396
  end tell`);
4290
- return { success: true, data: { created: title, start: startDate, end: endDate || "1 hour" } };
4397
+ return {
4398
+ success: true,
4399
+ data: { created: title, start: startDate, end: endDate || "1 hour" }
4400
+ };
4291
4401
  }
4292
4402
  /* ══════════════════════════════════════════════════════════
4293
4403
  * Productivity: Reminders
@@ -4312,11 +4422,22 @@ end tell`);
4312
4422
  const raw = await runAppleScript(script);
4313
4423
  const reminders = raw.split("\n").filter(Boolean).map((line) => {
4314
4424
  const parts = line.split(" | ");
4315
- return parts.length === 3 ? { list: parts[0]?.trim(), name: parts[1]?.trim(), due: parts[2]?.trim() } : { name: parts[0]?.trim(), due: parts[1]?.trim() };
4425
+ return parts.length === 3 ? {
4426
+ list: parts[0]?.trim(),
4427
+ name: parts[1]?.trim(),
4428
+ due: parts[2]?.trim()
4429
+ } : { name: parts[0]?.trim(), due: parts[1]?.trim() };
4316
4430
  });
4317
4431
  return { success: true, data: { reminders, count: reminders.length } };
4318
4432
  } catch {
4319
- return { success: true, data: { reminders: [], count: 0, note: "No reminders or Reminders app not accessible" } };
4433
+ return {
4434
+ success: true,
4435
+ data: {
4436
+ reminders: [],
4437
+ count: 0,
4438
+ note: "No reminders or Reminders app not accessible"
4439
+ }
4440
+ };
4320
4441
  }
4321
4442
  }
4322
4443
  async reminderCreate(title, list, dueDate) {
@@ -4324,7 +4445,9 @@ end tell`);
4324
4445
  let listName = list || "";
4325
4446
  if (!listName) {
4326
4447
  try {
4327
- listName = (await runAppleScript('tell application "Reminders" to return name of default list')).trim();
4448
+ listName = (await runAppleScript(
4449
+ 'tell application "Reminders" to return name of default list'
4450
+ )).trim();
4328
4451
  } catch {
4329
4452
  listName = "Reminders";
4330
4453
  }
@@ -4350,20 +4473,27 @@ tell application "Reminders"
4350
4473
  make new reminder with properties {name:"${title.replace(/"/g, '\\"')}"${dueProperty}}
4351
4474
  end tell
4352
4475
  end tell`);
4353
- return { success: true, data: { created: title, due: dueDate || "none", list: listName } };
4476
+ return {
4477
+ success: true,
4478
+ data: { created: title, due: dueDate || "none", list: listName }
4479
+ };
4354
4480
  }
4355
4481
  /* ══════════════════════════════════════════════════════════
4356
4482
  * Productivity: Messages
4357
4483
  * ══════════════════════════════════════════════════════════ */
4358
4484
  async sendMessage(to, message) {
4359
- if (!to || !message) return { success: false, error: "Missing 'to' or 'message'" };
4485
+ if (!to || !message)
4486
+ return { success: false, error: "Missing 'to' or 'message'" };
4360
4487
  await runAppleScript(`
4361
4488
  tell application "Messages"
4362
4489
  set targetService to 1st account whose service type = iMessage
4363
4490
  set targetBuddy to participant "${to.replace(/"/g, '\\"')}" of targetService
4364
4491
  send "${message.replace(/"/g, '\\"')}" to targetBuddy
4365
4492
  end tell`);
4366
- return { success: true, data: { sent: true, to, message: message.slice(0, 100) } };
4493
+ return {
4494
+ success: true,
4495
+ data: { sent: true, to, message: message.slice(0, 100) }
4496
+ };
4367
4497
  }
4368
4498
  /* ══════════════════════════════════════════════════════════
4369
4499
  * Productivity: Contacts
@@ -4390,7 +4520,11 @@ end tell`);
4390
4520
  end tell`);
4391
4521
  return raw.split("\n").filter(Boolean).map((line) => {
4392
4522
  const [name, email, phone] = line.split(" | ");
4393
- return { name: name?.trim() || "", email: email?.trim(), phone: phone?.trim() };
4523
+ return {
4524
+ name: name?.trim() || "",
4525
+ email: email?.trim(),
4526
+ phone: phone?.trim()
4527
+ };
4394
4528
  });
4395
4529
  }
4396
4530
  /* ══════════════════════════════════════════════════════════
@@ -4427,11 +4561,15 @@ end tell`);
4427
4561
  * Email
4428
4562
  * ══════════════════════════════════════════════════════════ */
4429
4563
  async emailSend(to, subject, body, method) {
4430
- if (!to || !subject || !body) return { success: false, error: "Missing to, subject, or body" };
4564
+ if (!to || !subject || !body)
4565
+ return { success: false, error: "Missing to, subject, or body" };
4431
4566
  if (method === "gmail") {
4432
4567
  const gmailUrl = `https://mail.google.com/mail/u/0/?view=cm&fs=1&to=${encodeURIComponent(to)}&su=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
4433
4568
  await runShell2(`open "${gmailUrl}"`);
4434
- return { success: true, data: { method: "gmail", to, subject, note: "Gmail compose opened." } };
4569
+ return {
4570
+ success: true,
4571
+ data: { method: "gmail", to, subject, note: "Gmail compose opened." }
4572
+ };
4435
4573
  }
4436
4574
  try {
4437
4575
  await runAppleScript(`
@@ -4442,11 +4580,22 @@ end tell`);
4442
4580
  end tell
4443
4581
  send newMessage
4444
4582
  end tell`);
4445
- return { success: true, data: { method: "mail", to, subject, sent: true } };
4583
+ return {
4584
+ success: true,
4585
+ data: { method: "mail", to, subject, sent: true }
4586
+ };
4446
4587
  } catch (err) {
4447
4588
  const gmailUrl = `https://mail.google.com/mail/u/0/?view=cm&fs=1&to=${encodeURIComponent(to)}&su=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
4448
4589
  await runShell2(`open "${gmailUrl}"`);
4449
- return { success: true, data: { method: "gmail_fallback", to, subject, note: `Mail.app failed (${err.message}). Gmail opened instead.` } };
4590
+ return {
4591
+ success: true,
4592
+ data: {
4593
+ method: "gmail_fallback",
4594
+ to,
4595
+ subject,
4596
+ note: `Mail.app failed (${err.message}). Gmail opened instead.`
4597
+ }
4598
+ };
4450
4599
  }
4451
4600
  }
4452
4601
  /* ══════════════════════════════════════════════════════════
@@ -4455,15 +4604,33 @@ end tell`);
4455
4604
  async fileRead(path) {
4456
4605
  if (!path) return { success: false, error: "Missing file path" };
4457
4606
  const fullPath = safePath2(path);
4458
- if (!fullPath) return { success: false, error: `Access denied. Only files in ${SAFE_DIRS2.join(", ")} are allowed.` };
4459
- if (!existsSync2(fullPath)) return { success: false, error: `File not found: ${path}` };
4607
+ if (!fullPath)
4608
+ return {
4609
+ success: false,
4610
+ error: `Access denied. Only files in ${SAFE_DIRS2.join(", ")} are allowed.`
4611
+ };
4612
+ if (!existsSync2(fullPath))
4613
+ return { success: false, error: `File not found: ${path}` };
4460
4614
  const content = readFileSync2(fullPath, "utf-8");
4461
- return { success: true, data: { path, content: content.slice(0, 1e4), size: content.length, truncated: content.length > 1e4 } };
4462
- }
4463
- async fileWrite(path, content) {
4464
- if (!path || !content) return { success: false, error: "Missing path or content" };
4465
- const fullPath = safePath2(path);
4466
- if (!fullPath) return { success: false, error: `Access denied. Only files in ${SAFE_DIRS2.join(", ")} are allowed.` };
4615
+ return {
4616
+ success: true,
4617
+ data: {
4618
+ path,
4619
+ content: content.slice(0, 1e4),
4620
+ size: content.length,
4621
+ truncated: content.length > 1e4
4622
+ }
4623
+ };
4624
+ }
4625
+ async fileWrite(path, content) {
4626
+ if (!path || !content)
4627
+ return { success: false, error: "Missing path or content" };
4628
+ const fullPath = safePath2(path);
4629
+ if (!fullPath)
4630
+ return {
4631
+ success: false,
4632
+ error: `Access denied. Only files in ${SAFE_DIRS2.join(", ")} are allowed.`
4633
+ };
4467
4634
  writeFileSync3(fullPath, content, "utf-8");
4468
4635
  return { success: true, data: { path, written: content.length } };
4469
4636
  }
@@ -4471,32 +4638,45 @@ end tell`);
4471
4638
  const dirPath = path || "Desktop";
4472
4639
  const fullDir = safePath2(dirPath);
4473
4640
  if (!fullDir) return { success: false, error: `Access denied: ${dirPath}` };
4474
- if (!existsSync2(fullDir)) return { success: false, error: `Directory not found: ${dirPath}` };
4641
+ if (!existsSync2(fullDir))
4642
+ return { success: false, error: `Directory not found: ${dirPath}` };
4475
4643
  const entries = readdirSync2(fullDir).map((name) => {
4476
4644
  try {
4477
4645
  const st = statSync2(join3(fullDir, name));
4478
- return { name, type: st.isDirectory() ? "dir" : "file", size: st.size, modified: st.mtime.toISOString() };
4646
+ return {
4647
+ name,
4648
+ type: st.isDirectory() ? "dir" : "file",
4649
+ size: st.size,
4650
+ modified: st.mtime.toISOString()
4651
+ };
4479
4652
  } catch {
4480
4653
  return { name, type: "unknown", size: 0, modified: "" };
4481
4654
  }
4482
4655
  });
4483
- return { success: true, data: { path: dirPath, entries, count: entries.length } };
4656
+ return {
4657
+ success: true,
4658
+ data: { path: dirPath, entries, count: entries.length }
4659
+ };
4484
4660
  }
4485
4661
  async fileMove(source, destination) {
4486
- if (!source || !destination) return { success: false, error: "Missing from/to paths" };
4662
+ if (!source || !destination)
4663
+ return { success: false, error: "Missing from/to paths" };
4487
4664
  const fullSrc = safePath2(source);
4488
4665
  const fullDst = safePath2(destination);
4489
4666
  if (!fullSrc || !fullDst) return { success: false, error: "Access denied" };
4490
- if (!existsSync2(fullSrc)) return { success: false, error: `Source not found: ${source}` };
4667
+ if (!existsSync2(fullSrc))
4668
+ return { success: false, error: `Source not found: ${source}` };
4491
4669
  renameSync2(fullSrc, fullDst);
4492
4670
  return { success: true, data: { moved: source, to: destination } };
4493
4671
  }
4494
4672
  async fileCopy(source, destination) {
4495
- if (!source || !destination) return { success: false, error: "Missing from/to paths" };
4673
+ if (!source || !destination)
4674
+ return { success: false, error: "Missing from/to paths" };
4496
4675
  const fullSrc = safePath2(source);
4497
4676
  const fullDst = safePath2(destination);
4498
4677
  if (!fullSrc || !fullDst) return { success: false, error: "Access denied" };
4499
- if (!existsSync2(fullSrc)) return { success: false, error: `Source not found: ${source}` };
4678
+ if (!existsSync2(fullSrc))
4679
+ return { success: false, error: `Source not found: ${source}` };
4500
4680
  copyFileSync2(fullSrc, fullDst);
4501
4681
  return { success: true, data: { copied: source, to: destination } };
4502
4682
  }
@@ -4504,15 +4684,19 @@ end tell`);
4504
4684
  if (!path) return { success: false, error: "Missing path" };
4505
4685
  const fullTarget = safePath2(path);
4506
4686
  if (!fullTarget) return { success: false, error: "Access denied" };
4507
- if (!existsSync2(fullTarget)) return { success: false, error: `Not found: ${path}` };
4508
- await runShell2(`osascript -e 'tell application "Finder" to delete POSIX file "${fullTarget}"'`);
4687
+ if (!existsSync2(fullTarget))
4688
+ return { success: false, error: `Not found: ${path}` };
4689
+ await runShell2(
4690
+ `osascript -e 'tell application "Finder" to delete POSIX file "${fullTarget}"'`
4691
+ );
4509
4692
  return { success: true, data: { deleted: path, method: "moved_to_trash" } };
4510
4693
  }
4511
4694
  async fileInfo(path) {
4512
4695
  if (!path) return { success: false, error: "Missing path" };
4513
4696
  const fullF = safePath2(path);
4514
4697
  if (!fullF) return { success: false, error: "Access denied" };
4515
- if (!existsSync2(fullF)) return { success: false, error: `Not found: ${path}` };
4698
+ if (!existsSync2(fullF))
4699
+ return { success: false, error: `Not found: ${path}` };
4516
4700
  const st = statSync2(fullF);
4517
4701
  return {
4518
4702
  success: true,
@@ -4533,7 +4717,10 @@ end tell`);
4533
4717
  const dlDest = destination || `Downloads/${basename2(new URL(url).pathname) || "download"}`;
4534
4718
  const fullDl = safePath2(dlDest);
4535
4719
  if (!fullDl) return { success: false, error: "Access denied" };
4536
- await runShell2(`curl -sL -o "${fullDl}" "${url.replace(/"/g, '\\"')}"`, 6e4);
4720
+ await runShell2(
4721
+ `curl -sL -o "${fullDl}" "${url.replace(/"/g, '\\"')}"`,
4722
+ 6e4
4723
+ );
4537
4724
  const size = existsSync2(fullDl) ? statSync2(fullDl).size : 0;
4538
4725
  return { success: true, data: { downloaded: url, saved: dlDest, size } };
4539
4726
  }
@@ -4570,14 +4757,20 @@ end tell`);
4570
4757
  }
4571
4758
  async windowFocus(app) {
4572
4759
  if (!app) return { success: false, error: "Missing app name" };
4573
- await runAppleScript(`tell application "${app.replace(/"/g, '\\"')}" to activate`);
4760
+ await runAppleScript(
4761
+ `tell application "${app.replace(/"/g, '\\"')}" to activate`
4762
+ );
4574
4763
  return { success: true, data: { focused: app } };
4575
4764
  }
4576
4765
  async windowResize(app, x, y, width, height) {
4577
4766
  if (!app) return { success: false, error: "Missing app name" };
4578
4767
  const posPart = x !== void 0 && y !== void 0 ? `set position of window 1 to {${x}, ${y}}` : "";
4579
4768
  const sizePart = width !== void 0 && height !== void 0 ? `set size of window 1 to {${width}, ${height}}` : "";
4580
- if (!posPart && !sizePart) return { success: false, error: "Provide x,y for position and/or width,height for size" };
4769
+ if (!posPart && !sizePart)
4770
+ return {
4771
+ success: false,
4772
+ error: "Provide x,y for position and/or width,height for size"
4773
+ };
4581
4774
  await runAppleScript(`
4582
4775
  tell application "System Events"
4583
4776
  tell process "${app.replace(/"/g, '\\"')}"
@@ -4585,7 +4778,14 @@ end tell`);
4585
4778
  ${sizePart}
4586
4779
  end tell
4587
4780
  end tell`);
4588
- return { success: true, data: { app, position: posPart ? { x, y } : "unchanged", size: sizePart ? { width, height } : "unchanged" } };
4781
+ return {
4782
+ success: true,
4783
+ data: {
4784
+ app,
4785
+ position: posPart ? { x, y } : "unchanged",
4786
+ size: sizePart ? { width, height } : "unchanged"
4787
+ }
4788
+ };
4589
4789
  }
4590
4790
  /* ══════════════════════════════════════════════════════════
4591
4791
  * OCR
@@ -4594,7 +4794,8 @@ end tell`);
4594
4794
  if (!imagePath) return { success: false, error: "Missing image path" };
4595
4795
  const fullImg = imagePath.startsWith("/tmp/") ? imagePath : safePath2(imagePath);
4596
4796
  if (!fullImg) return { success: false, error: "Access denied" };
4597
- if (!existsSync2(fullImg)) return { success: false, error: `Image not found: ${imagePath}` };
4797
+ if (!existsSync2(fullImg))
4798
+ return { success: false, error: `Image not found: ${imagePath}` };
4598
4799
  const swiftOcr = `
4599
4800
  import Foundation
4600
4801
  import Vision
@@ -4616,7 +4817,14 @@ let text = results.compactMap { $0.topCandidates(1).first?.string }.joined(separ
4616
4817
  print(text)`;
4617
4818
  try {
4618
4819
  const ocrText = await runSwift(swiftOcr, 3e4);
4619
- return { success: true, data: { text: ocrText.slice(0, 1e4), length: ocrText.length, path: imagePath } };
4820
+ return {
4821
+ success: true,
4822
+ data: {
4823
+ text: ocrText.slice(0, 1e4),
4824
+ length: ocrText.length,
4825
+ path: imagePath
4826
+ }
4827
+ };
4620
4828
  } catch (err) {
4621
4829
  return { success: false, error: `OCR failed: ${err.message}` };
4622
4830
  }
@@ -4640,10 +4848,18 @@ print(text)`;
4640
4848
  await runAppleScript('tell application "Spotify" to previous track');
4641
4849
  return { success: true, data: { action: "previous" } };
4642
4850
  case "now_playing": {
4643
- const name = await runAppleScript('tell application "Spotify" to name of current track');
4644
- const artist = await runAppleScript('tell application "Spotify" to artist of current track');
4645
- const album = await runAppleScript('tell application "Spotify" to album of current track');
4646
- const state = await runAppleScript('tell application "Spotify" to player state as string');
4851
+ const name = await runAppleScript(
4852
+ 'tell application "Spotify" to name of current track'
4853
+ );
4854
+ const artist = await runAppleScript(
4855
+ 'tell application "Spotify" to artist of current track'
4856
+ );
4857
+ const album = await runAppleScript(
4858
+ 'tell application "Spotify" to album of current track'
4859
+ );
4860
+ const state = await runAppleScript(
4861
+ 'tell application "Spotify" to player state as string'
4862
+ );
4647
4863
  return { success: true, data: { track: name, artist, album, state } };
4648
4864
  }
4649
4865
  case "search_play": {
@@ -4651,34 +4867,68 @@ print(text)`;
4651
4867
  if (!query) return { success: false, error: "Missing search query" };
4652
4868
  const result = await this.spotifySearch(query);
4653
4869
  if (result) {
4654
- await runAppleScript(`tell application "Spotify" to play track "${result.uri}"`);
4870
+ await runAppleScript(
4871
+ `tell application "Spotify" to play track "${result.uri}"`
4872
+ );
4655
4873
  await new Promise((r) => setTimeout(r, 1500));
4656
4874
  try {
4657
- const track = await runAppleScript('tell application "Spotify" to name of current track');
4658
- const artist = await runAppleScript('tell application "Spotify" to artist of current track');
4659
- return { success: true, data: { searched: query, resolved: `${result.name} - ${result.artist}`, nowPlaying: `${track} - ${artist}` } };
4875
+ const track = await runAppleScript(
4876
+ 'tell application "Spotify" to name of current track'
4877
+ );
4878
+ const artist = await runAppleScript(
4879
+ 'tell application "Spotify" to artist of current track'
4880
+ );
4881
+ return {
4882
+ success: true,
4883
+ data: {
4884
+ searched: query,
4885
+ resolved: `${result.name} - ${result.artist}`,
4886
+ nowPlaying: `${track} - ${artist}`
4887
+ }
4888
+ };
4660
4889
  } catch {
4661
- return { success: true, data: { searched: query, resolved: `${result.name} - ${result.artist}`, note: "Playing track" } };
4890
+ return {
4891
+ success: true,
4892
+ data: {
4893
+ searched: query,
4894
+ resolved: `${result.name} - ${result.artist}`,
4895
+ note: "Playing track"
4896
+ }
4897
+ };
4662
4898
  }
4663
4899
  }
4664
4900
  await runShell2(`open "spotify:search:${encodeURIComponent(query)}"`);
4665
- return { success: true, data: { searched: query, note: "Opened Spotify search." } };
4901
+ return {
4902
+ success: true,
4903
+ data: { searched: query, note: "Opened Spotify search." }
4904
+ };
4666
4905
  }
4667
4906
  case "volume": {
4668
4907
  const level = p.level;
4669
- if (level === void 0 || level < 0 || level > 100) return { success: false, error: "Volume must be 0-100" };
4670
- await runAppleScript(`tell application "Spotify" to set sound volume to ${level}`);
4908
+ if (level === void 0 || level < 0 || level > 100)
4909
+ return { success: false, error: "Volume must be 0-100" };
4910
+ await runAppleScript(
4911
+ `tell application "Spotify" to set sound volume to ${level}`
4912
+ );
4671
4913
  return { success: true, data: { volume: level } };
4672
4914
  }
4673
4915
  case "shuffle": {
4674
4916
  const enabled = p.enabled;
4675
- await runAppleScript(`tell application "Spotify" to set shuffling to ${enabled ? "true" : "false"}`);
4917
+ await runAppleScript(
4918
+ `tell application "Spotify" to set shuffling to ${enabled ? "true" : "false"}`
4919
+ );
4676
4920
  return { success: true, data: { shuffling: enabled } };
4677
4921
  }
4678
4922
  case "repeat": {
4679
4923
  const mode = p.mode;
4680
- if (!mode) return { success: false, error: "Missing mode (off, context, track)" };
4681
- await runAppleScript(`tell application "Spotify" to set repeating to ${mode !== "off"}`);
4924
+ if (!mode)
4925
+ return {
4926
+ success: false,
4927
+ error: "Missing mode (off, context, track)"
4928
+ };
4929
+ await runAppleScript(
4930
+ `tell application "Spotify" to set repeating to ${mode !== "off"}`
4931
+ );
4682
4932
  return { success: true, data: { repeating: mode } };
4683
4933
  }
4684
4934
  default:
@@ -4691,11 +4941,17 @@ print(text)`;
4691
4941
  async hueLightsOn(light, brightness, color) {
4692
4942
  if (!light) return { success: false, error: "Missing light ID or name" };
4693
4943
  const hueConfig = this.getHueConfig();
4694
- if (!hueConfig) return { success: false, error: "Philips Hue not configured. Set HUE_BRIDGE_IP and HUE_USERNAME environment variables." };
4944
+ if (!hueConfig)
4945
+ return {
4946
+ success: false,
4947
+ error: "Philips Hue not configured. Set HUE_BRIDGE_IP and HUE_USERNAME environment variables."
4948
+ };
4695
4949
  const lightId = await this.resolveHueLight(hueConfig, light);
4696
- if (!lightId) return { success: false, error: `Light '${light}' not found` };
4950
+ if (!lightId)
4951
+ return { success: false, error: `Light '${light}' not found` };
4697
4952
  const state = { on: true };
4698
- if (brightness !== void 0) state.bri = Math.max(1, Math.min(254, Number(brightness)));
4953
+ if (brightness !== void 0)
4954
+ state.bri = Math.max(1, Math.min(254, Number(brightness)));
4699
4955
  if (color) {
4700
4956
  const rgb = this.parseColor(color);
4701
4957
  if (rgb) {
@@ -4703,46 +4959,91 @@ print(text)`;
4703
4959
  state.xy = [x, y];
4704
4960
  }
4705
4961
  }
4706
- const res = await this.hueRequest(hueConfig, `lights/${lightId}/state`, "PUT", state);
4707
- return { success: true, data: { light: lightId, action: "on", ...state, response: res } };
4962
+ const res = await this.hueRequest(
4963
+ hueConfig,
4964
+ `lights/${lightId}/state`,
4965
+ "PUT",
4966
+ state
4967
+ );
4968
+ return {
4969
+ success: true,
4970
+ data: { light: lightId, action: "on", ...state, response: res }
4971
+ };
4708
4972
  }
4709
4973
  async hueLightsOff(light) {
4710
4974
  if (!light) return { success: false, error: "Missing light ID or name" };
4711
4975
  const hueConfig = this.getHueConfig();
4712
- if (!hueConfig) return { success: false, error: "Philips Hue not configured." };
4976
+ if (!hueConfig)
4977
+ return { success: false, error: "Philips Hue not configured." };
4713
4978
  const lightId = await this.resolveHueLight(hueConfig, light);
4714
- if (!lightId) return { success: false, error: `Light '${light}' not found` };
4715
- const res = await this.hueRequest(hueConfig, `lights/${lightId}/state`, "PUT", { on: false });
4716
- return { success: true, data: { light: lightId, action: "off", response: res } };
4979
+ if (!lightId)
4980
+ return { success: false, error: `Light '${light}' not found` };
4981
+ const res = await this.hueRequest(
4982
+ hueConfig,
4983
+ `lights/${lightId}/state`,
4984
+ "PUT",
4985
+ { on: false }
4986
+ );
4987
+ return {
4988
+ success: true,
4989
+ data: { light: lightId, action: "off", response: res }
4990
+ };
4717
4991
  }
4718
4992
  async hueLightsColor(light, color) {
4719
- if (!light || !color) return { success: false, error: "Missing light or color" };
4993
+ if (!light || !color)
4994
+ return { success: false, error: "Missing light or color" };
4720
4995
  const hueConfig = this.getHueConfig();
4721
- if (!hueConfig) return { success: false, error: "Philips Hue not configured." };
4996
+ if (!hueConfig)
4997
+ return { success: false, error: "Philips Hue not configured." };
4722
4998
  const lightId = await this.resolveHueLight(hueConfig, light);
4723
- if (!lightId) return { success: false, error: `Light '${light}' not found` };
4999
+ if (!lightId)
5000
+ return { success: false, error: `Light '${light}' not found` };
4724
5001
  const rgb = this.parseColor(color);
4725
5002
  if (!rgb) return { success: false, error: `Unrecognized color: ${color}` };
4726
5003
  const [x, y] = this.rgbToXy(...rgb);
4727
- const res = await this.hueRequest(hueConfig, `lights/${lightId}/state`, "PUT", { on: true, xy: [x, y] });
4728
- return { success: true, data: { light: lightId, color, xy: [x, y], response: res } };
5004
+ const res = await this.hueRequest(
5005
+ hueConfig,
5006
+ `lights/${lightId}/state`,
5007
+ "PUT",
5008
+ { on: true, xy: [x, y] }
5009
+ );
5010
+ return {
5011
+ success: true,
5012
+ data: { light: lightId, color, xy: [x, y], response: res }
5013
+ };
4729
5014
  }
4730
5015
  async hueLightsBrightness(light, brightness) {
4731
- if (!light || isNaN(brightness)) return { success: false, error: "Missing light or brightness" };
5016
+ if (!light || isNaN(brightness))
5017
+ return { success: false, error: "Missing light or brightness" };
4732
5018
  const hueConfig = this.getHueConfig();
4733
- if (!hueConfig) return { success: false, error: "Philips Hue not configured." };
5019
+ if (!hueConfig)
5020
+ return { success: false, error: "Philips Hue not configured." };
4734
5021
  const lightId = await this.resolveHueLight(hueConfig, light);
4735
- if (!lightId) return { success: false, error: `Light '${light}' not found` };
5022
+ if (!lightId)
5023
+ return { success: false, error: `Light '${light}' not found` };
4736
5024
  const bri = Math.max(1, Math.min(254, brightness));
4737
- const res = await this.hueRequest(hueConfig, `lights/${lightId}/state`, "PUT", { on: true, bri });
4738
- return { success: true, data: { light: lightId, brightness: bri, response: res } };
5025
+ const res = await this.hueRequest(
5026
+ hueConfig,
5027
+ `lights/${lightId}/state`,
5028
+ "PUT",
5029
+ { on: true, bri }
5030
+ );
5031
+ return {
5032
+ success: true,
5033
+ data: { light: lightId, brightness: bri, response: res }
5034
+ };
4739
5035
  }
4740
5036
  async hueLightsScene(scene, group) {
4741
5037
  if (!scene) return { success: false, error: "Missing scene name" };
4742
5038
  const hueConfig = this.getHueConfig();
4743
- if (!hueConfig) return { success: false, error: "Philips Hue not configured." };
5039
+ if (!hueConfig)
5040
+ return { success: false, error: "Philips Hue not configured." };
4744
5041
  const g = group || "0";
4745
- const scenes = await this.hueRequest(hueConfig, "scenes", "GET");
5042
+ const scenes = await this.hueRequest(
5043
+ hueConfig,
5044
+ "scenes",
5045
+ "GET"
5046
+ );
4746
5047
  let sceneId = null;
4747
5048
  for (const [id, s] of Object.entries(scenes)) {
4748
5049
  if (s.name?.toLowerCase() === scene.toLowerCase()) {
@@ -4750,13 +5051,20 @@ print(text)`;
4750
5051
  break;
4751
5052
  }
4752
5053
  }
4753
- if (!sceneId) return { success: false, error: `Scene '${scene}' not found. Available: ${Object.values(scenes).map((s) => s.name).join(", ")}` };
4754
- const res = await this.hueRequest(hueConfig, `groups/${g}/action`, "PUT", { scene: sceneId });
5054
+ if (!sceneId)
5055
+ return {
5056
+ success: false,
5057
+ error: `Scene '${scene}' not found. Available: ${Object.values(scenes).map((s) => s.name).join(", ")}`
5058
+ };
5059
+ const res = await this.hueRequest(hueConfig, `groups/${g}/action`, "PUT", {
5060
+ scene: sceneId
5061
+ });
4755
5062
  return { success: true, data: { scene, sceneId, group: g, response: res } };
4756
5063
  }
4757
5064
  async hueLightsList() {
4758
5065
  const hueConfig = this.getHueConfig();
4759
- if (!hueConfig) return { success: false, error: "Philips Hue not configured." };
5066
+ if (!hueConfig)
5067
+ return { success: false, error: "Philips Hue not configured." };
4760
5068
  const [lights, groups, scenes] = await Promise.all([
4761
5069
  this.hueRequest(hueConfig, "lights", "GET"),
4762
5070
  this.hueRequest(hueConfig, "groups", "GET"),
@@ -4765,9 +5073,24 @@ print(text)`;
4765
5073
  return {
4766
5074
  success: true,
4767
5075
  data: {
4768
- lights: Object.entries(lights).map(([id, l]) => ({ id, name: l.name, on: l.state?.on, brightness: l.state?.bri, type: l.type })),
4769
- groups: Object.entries(groups).map(([id, g]) => ({ id, name: g.name, type: g.type, lightCount: g.lights?.length })),
4770
- scenes: Object.entries(scenes).map(([id, s]) => ({ id, name: s.name, group: s.group }))
5076
+ lights: Object.entries(lights).map(([id, l]) => ({
5077
+ id,
5078
+ name: l.name,
5079
+ on: l.state?.on,
5080
+ brightness: l.state?.bri,
5081
+ type: l.type
5082
+ })),
5083
+ groups: Object.entries(groups).map(([id, g]) => ({
5084
+ id,
5085
+ name: g.name,
5086
+ type: g.type,
5087
+ lightCount: g.lights?.length
5088
+ })),
5089
+ scenes: Object.entries(scenes).map(([id, s]) => ({
5090
+ id,
5091
+ name: s.name,
5092
+ group: s.group
5093
+ }))
4771
5094
  }
4772
5095
  };
4773
5096
  }
@@ -4777,23 +5100,37 @@ print(text)`;
4777
5100
  async sonosPlay(room) {
4778
5101
  if (!room) return { success: false, error: "Missing room name" };
4779
5102
  const url = this.getSonosApiUrl();
4780
- if (!url) return { success: false, error: "Sonos not configured. Set SONOS_API_URL env var." };
4781
- const res = await this.sonosRequest(url, `${encodeURIComponent(room)}/play`);
5103
+ if (!url)
5104
+ return {
5105
+ success: false,
5106
+ error: "Sonos not configured. Set SONOS_API_URL env var."
5107
+ };
5108
+ const res = await this.sonosRequest(
5109
+ url,
5110
+ `${encodeURIComponent(room)}/play`
5111
+ );
4782
5112
  return { success: true, data: { room, action: "play", response: res } };
4783
5113
  }
4784
5114
  async sonosPause(room) {
4785
5115
  if (!room) return { success: false, error: "Missing room name" };
4786
5116
  const url = this.getSonosApiUrl();
4787
5117
  if (!url) return { success: false, error: "Sonos not configured." };
4788
- const res = await this.sonosRequest(url, `${encodeURIComponent(room)}/pause`);
5118
+ const res = await this.sonosRequest(
5119
+ url,
5120
+ `${encodeURIComponent(room)}/pause`
5121
+ );
4789
5122
  return { success: true, data: { room, action: "pause", response: res } };
4790
5123
  }
4791
5124
  async sonosVolume(room, level) {
4792
- if (!room || isNaN(level)) return { success: false, error: "Missing room or level" };
5125
+ if (!room || isNaN(level))
5126
+ return { success: false, error: "Missing room or level" };
4793
5127
  const url = this.getSonosApiUrl();
4794
5128
  if (!url) return { success: false, error: "Sonos not configured." };
4795
5129
  const vol = Math.max(0, Math.min(100, level));
4796
- const res = await this.sonosRequest(url, `${encodeURIComponent(room)}/volume/${vol}`);
5130
+ const res = await this.sonosRequest(
5131
+ url,
5132
+ `${encodeURIComponent(room)}/volume/${vol}`
5133
+ );
4797
5134
  return { success: true, data: { room, volume: vol, response: res } };
4798
5135
  }
4799
5136
  async sonosPlayUri(room, uri, _title) {
@@ -4801,10 +5138,19 @@ print(text)`;
4801
5138
  const url = this.getSonosApiUrl();
4802
5139
  if (!url) return { success: false, error: "Sonos not configured." };
4803
5140
  if (uri.startsWith("spotify:")) {
4804
- const res2 = await this.sonosRequest(url, `${encodeURIComponent(room)}/spotify/now/${encodeURIComponent(uri)}`);
4805
- return { success: true, data: { room, uri, type: "spotify", response: res2 } };
5141
+ const res2 = await this.sonosRequest(
5142
+ url,
5143
+ `${encodeURIComponent(room)}/spotify/now/${encodeURIComponent(uri)}`
5144
+ );
5145
+ return {
5146
+ success: true,
5147
+ data: { room, uri, type: "spotify", response: res2 }
5148
+ };
4806
5149
  }
4807
- const res = await this.sonosRequest(url, `${encodeURIComponent(room)}/setavtransporturi/${encodeURIComponent(uri)}`);
5150
+ const res = await this.sonosRequest(
5151
+ url,
5152
+ `${encodeURIComponent(room)}/setavtransporturi/${encodeURIComponent(uri)}`
5153
+ );
4808
5154
  return { success: true, data: { room, uri, response: res } };
4809
5155
  }
4810
5156
  async sonosRooms() {
@@ -4817,21 +5163,30 @@ print(text)`;
4817
5163
  if (!room) return { success: false, error: "Missing room name" };
4818
5164
  const url = this.getSonosApiUrl();
4819
5165
  if (!url) return { success: false, error: "Sonos not configured." };
4820
- const res = await this.sonosRequest(url, `${encodeURIComponent(room)}/next`);
5166
+ const res = await this.sonosRequest(
5167
+ url,
5168
+ `${encodeURIComponent(room)}/next`
5169
+ );
4821
5170
  return { success: true, data: { room, action: "next", response: res } };
4822
5171
  }
4823
5172
  async sonosPrevious(room) {
4824
5173
  if (!room) return { success: false, error: "Missing room name" };
4825
5174
  const url = this.getSonosApiUrl();
4826
5175
  if (!url) return { success: false, error: "Sonos not configured." };
4827
- const res = await this.sonosRequest(url, `${encodeURIComponent(room)}/previous`);
5176
+ const res = await this.sonosRequest(
5177
+ url,
5178
+ `${encodeURIComponent(room)}/previous`
5179
+ );
4828
5180
  return { success: true, data: { room, action: "previous", response: res } };
4829
5181
  }
4830
5182
  async sonosNowPlaying(room) {
4831
5183
  if (!room) return { success: false, error: "Missing room name" };
4832
5184
  const url = this.getSonosApiUrl();
4833
5185
  if (!url) return { success: false, error: "Sonos not configured." };
4834
- const res = await this.sonosRequest(url, `${encodeURIComponent(room)}/state`);
5186
+ const res = await this.sonosRequest(
5187
+ url,
5188
+ `${encodeURIComponent(room)}/state`
5189
+ );
4835
5190
  return { success: true, data: { room, ...res } };
4836
5191
  }
4837
5192
  /* ══════════════════════════════════════════════════════════
@@ -4843,9 +5198,12 @@ print(text)`;
4843
5198
  const cached = this.searchCache.get(key);
4844
5199
  if (cached && Date.now() - cached.ts < CACHE_TTL) return cached;
4845
5200
  try {
4846
- const res = await fetch(`${this.apiUrl}/tools/spotify/search?q=${encodeURIComponent(query)}`, {
4847
- headers: { Authorization: `Bearer ${this.token}` }
4848
- });
5201
+ const res = await fetch(
5202
+ `${this.apiUrl}/tools/spotify/search?q=${encodeURIComponent(query)}`,
5203
+ {
5204
+ headers: { Authorization: `Bearer ${this.token}` }
5205
+ }
5206
+ );
4849
5207
  if (res.ok) {
4850
5208
  const data = await res.json();
4851
5209
  if (data.uri) {
@@ -4860,7 +5218,11 @@ print(text)`;
4860
5218
  if (looksLikeArtist) {
4861
5219
  const artistId = await this.searchWebSpotifyIds(query, "artist");
4862
5220
  if (artistId) {
4863
- const result2 = { uri: `spotify:artist:${artistId}`, name: "Top Songs", artist: query };
5221
+ const result2 = {
5222
+ uri: `spotify:artist:${artistId}`,
5223
+ name: "Top Songs",
5224
+ artist: query
5225
+ };
4864
5226
  this.searchCache.set(key, { ...result2, ts: Date.now() });
4865
5227
  this.pushToServerCache(query, result2).catch(() => {
4866
5228
  });
@@ -4870,7 +5232,11 @@ print(text)`;
4870
5232
  const trackIds = await this.searchWebSpotifyIds(query, "track");
4871
5233
  if (!trackIds) return null;
4872
5234
  const meta = await this.getTrackMetadata(trackIds);
4873
- const result = { uri: `spotify:track:${trackIds}`, name: meta?.name ?? query, artist: meta?.artist ?? "Unknown" };
5235
+ const result = {
5236
+ uri: `spotify:track:${trackIds}`,
5237
+ name: meta?.name ?? query,
5238
+ artist: meta?.artist ?? "Unknown"
5239
+ };
4874
5240
  this.searchCache.set(key, { ...result, ts: Date.now() });
4875
5241
  this.pushToServerCache(query, result).catch(() => {
4876
5242
  });
@@ -4881,12 +5247,18 @@ print(text)`;
4881
5247
  `https://search.brave.com/search?q=${encodeURIComponent(`${query} ${type} site:open.spotify.com`)}&source=web`,
4882
5248
  `https://html.duckduckgo.com/html/?q=${encodeURIComponent(`${query} ${type} site:open.spotify.com`)}`
4883
5249
  ];
4884
- const pattern = new RegExp(`open\\.spotify\\.com(?:/intl-[a-z]+)?/${type}/([a-zA-Z0-9]{22})`, "g");
5250
+ const pattern = new RegExp(
5251
+ `open\\.spotify\\.com(?:/intl-[a-z]+)?/${type}/([a-zA-Z0-9]{22})`,
5252
+ "g"
5253
+ );
4885
5254
  for (const url of engines) {
4886
5255
  try {
4887
5256
  const controller = new AbortController();
4888
5257
  const timeout = setTimeout(() => controller.abort(), 8e3);
4889
- const res = await fetch(url, { headers: { "User-Agent": UA, Accept: "text/html" }, signal: controller.signal });
5258
+ const res = await fetch(url, {
5259
+ headers: { "User-Agent": UA, Accept: "text/html" },
5260
+ signal: controller.signal
5261
+ });
4890
5262
  clearTimeout(timeout);
4891
5263
  if (!res.ok) continue;
4892
5264
  const html = await res.text();
@@ -4900,19 +5272,27 @@ print(text)`;
4900
5272
  try {
4901
5273
  const controller = new AbortController();
4902
5274
  const timeout = setTimeout(() => controller.abort(), 5e3);
4903
- const res = await fetch(`https://open.spotify.com/embed/track/${trackId}`, {
4904
- headers: { "User-Agent": UA, Accept: "text/html" },
4905
- signal: controller.signal
4906
- });
5275
+ const res = await fetch(
5276
+ `https://open.spotify.com/embed/track/${trackId}`,
5277
+ {
5278
+ headers: { "User-Agent": UA, Accept: "text/html" },
5279
+ signal: controller.signal
5280
+ }
5281
+ );
4907
5282
  clearTimeout(timeout);
4908
5283
  if (!res.ok) return null;
4909
5284
  const html = await res.text();
4910
- const match = html.match(/<script[^>]*>(\{"props":\{"pageProps".*?\})<\/script>/s);
5285
+ const match = html.match(
5286
+ /<script[^>]*>(\{"props":\{"pageProps".*?\})<\/script>/s
5287
+ );
4911
5288
  if (!match) return null;
4912
5289
  const data = JSON.parse(match[1]);
4913
5290
  const entity = data?.props?.pageProps?.state?.data?.entity;
4914
5291
  if (!entity?.name) return null;
4915
- return { name: entity.name, artist: entity.artists?.map((a) => a.name).join(", ") ?? "Unknown" };
5292
+ return {
5293
+ name: entity.name,
5294
+ artist: entity.artists?.map((a) => a.name).join(", ") ?? "Unknown"
5295
+ };
4916
5296
  } catch {
4917
5297
  return null;
4918
5298
  }
@@ -4921,7 +5301,10 @@ print(text)`;
4921
5301
  try {
4922
5302
  await fetch(`${this.apiUrl}/tools/spotify/cache`, {
4923
5303
  method: "POST",
4924
- headers: { Authorization: `Bearer ${this.token}`, "Content-Type": "application/json" },
5304
+ headers: {
5305
+ Authorization: `Bearer ${this.token}`,
5306
+ "Content-Type": "application/json"
5307
+ },
4925
5308
  body: JSON.stringify({ query, ...result })
4926
5309
  });
4927
5310
  } catch {
@@ -4947,21 +5330,31 @@ print(text)`;
4947
5330
  const controller = new AbortController();
4948
5331
  const timeout = setTimeout(() => controller.abort(), 1e4);
4949
5332
  try {
4950
- const opts = { method, signal: controller.signal, headers: { "Content-Type": "application/json" } };
5333
+ const opts = {
5334
+ method,
5335
+ signal: controller.signal,
5336
+ headers: { "Content-Type": "application/json" }
5337
+ };
4951
5338
  if (body && method !== "GET") opts.body = JSON.stringify(body);
4952
5339
  const res = await fetch(url, opts);
4953
5340
  clearTimeout(timeout);
4954
5341
  return await res.json();
4955
5342
  } catch (err) {
4956
5343
  clearTimeout(timeout);
4957
- throw new Error(`Hue bridge unreachable at ${config.bridgeIp}: ${err.message}`);
5344
+ throw new Error(
5345
+ `Hue bridge unreachable at ${config.bridgeIp}: ${err.message}`
5346
+ );
4958
5347
  }
4959
5348
  }
4960
5349
  async resolveHueLight(config, lightRef) {
4961
5350
  if (/^\d+$/.test(lightRef)) return lightRef;
4962
5351
  if (!this.hueLightCache || Date.now() - this.hueLightCacheTs > 3e5) {
4963
5352
  try {
4964
- const lights = await this.hueRequest(config, "lights", "GET");
5353
+ const lights = await this.hueRequest(
5354
+ config,
5355
+ "lights",
5356
+ "GET"
5357
+ );
4965
5358
  this.hueLightCache = /* @__PURE__ */ new Map();
4966
5359
  for (const [id, light] of Object.entries(lights)) {
4967
5360
  this.hueLightCache.set(light.name.toLowerCase(), id);
@@ -4975,7 +5368,11 @@ print(text)`;
4975
5368
  }
4976
5369
  parseColor(color) {
4977
5370
  if (color.startsWith("#") && color.length === 7) {
4978
- return [parseInt(color.slice(1, 3), 16), parseInt(color.slice(3, 5), 16), parseInt(color.slice(5, 7), 16)];
5371
+ return [
5372
+ parseInt(color.slice(1, 3), 16),
5373
+ parseInt(color.slice(3, 5), 16),
5374
+ parseInt(color.slice(5, 7), 16)
5375
+ ];
4979
5376
  }
4980
5377
  return CSS_COLORS2[color.toLowerCase()] ?? null;
4981
5378
  }
@@ -5010,7 +5407,9 @@ print(text)`;
5010
5407
  }
5011
5408
  } catch (err) {
5012
5409
  clearTimeout(timeout);
5013
- throw new Error(`Sonos API unreachable at ${baseUrl}: ${err.message}`);
5410
+ throw new Error(
5411
+ `Sonos API unreachable at ${baseUrl}: ${err.message}`
5412
+ );
5014
5413
  }
5015
5414
  }
5016
5415
  };
@@ -5032,13 +5431,7 @@ import { homedir as homedir3, hostname as hostname2, cpus as cpus2, totalmem as
5032
5431
  import { join as join4, resolve as resolve3, basename as basename3, extname as extname3 } from "path";
5033
5432
  var HOME3 = homedir3();
5034
5433
  var NOTES_DIR2 = join4(HOME3, "Documents", "PulsoNotes");
5035
- var SAFE_DIRS3 = [
5036
- "Documents",
5037
- "Desktop",
5038
- "Downloads",
5039
- "Projects",
5040
- "Projetos"
5041
- ];
5434
+ var SAFE_DIRS3 = ["Documents", "Desktop", "Downloads", "Projects", "Projetos"];
5042
5435
  var ACCESS_LEVEL2 = process.env.PULSO_ACCESS ?? "sandboxed";
5043
5436
  function safePath3(relative) {
5044
5437
  const full = resolve3(HOME3, relative);
@@ -5065,10 +5458,14 @@ function runPowerShell(script, timeout = 15e3) {
5065
5458
  return new Promise((resolve5, reject) => {
5066
5459
  const encoded = Buffer.from(script, "utf16le").toString("base64");
5067
5460
  const cmd = `powershell -NoProfile -NonInteractive -EncodedCommand ${encoded}`;
5068
- exec4(cmd, { timeout, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
5069
- if (err) return reject(new Error(stderr?.trim() || err.message));
5070
- resolve5(stdout.trim());
5071
- });
5461
+ exec4(
5462
+ cmd,
5463
+ { timeout, maxBuffer: 10 * 1024 * 1024 },
5464
+ (err, stdout, stderr) => {
5465
+ if (err) return reject(new Error(stderr?.trim() || err.message));
5466
+ resolve5(stdout.trim());
5467
+ }
5468
+ );
5072
5469
  });
5073
5470
  }
5074
5471
  function runPowerShellScript(script, timeout = 15e3) {
@@ -5361,7 +5758,10 @@ var WindowsAdapter = class {
5361
5758
  try {
5362
5759
  const parsed = new URL(url);
5363
5760
  if (!["http:", "https:"].includes(parsed.protocol)) {
5364
- return { success: false, error: "Only http and https URLs are allowed" };
5761
+ return {
5762
+ success: false,
5763
+ error: "Only http and https URLs are allowed"
5764
+ };
5365
5765
  }
5366
5766
  await runPowerShell(`Start-Process "${url}"`);
5367
5767
  return { success: true, data: { url, action: "opened" } };
@@ -5478,7 +5878,10 @@ public class AudioHelper {
5478
5878
  `;
5479
5879
  const output2 = await runPowerShellScript(script, 1e4);
5480
5880
  const volume = parseInt(output2, 10);
5481
- return { success: true, data: { volume: isNaN(volume) ? output2 : volume } };
5881
+ return {
5882
+ success: true,
5883
+ data: { volume: isNaN(volume) ? output2 : volume }
5884
+ };
5482
5885
  } catch (err) {
5483
5886
  try {
5484
5887
  const output2 = await runPowerShell(
@@ -5689,7 +6092,12 @@ Write-Output "$uptimeStr"
5689
6092
  data: { shortcut: name, method: "power-automate" }
5690
6093
  };
5691
6094
  } catch {
5692
- const scriptPath = join4(HOME3, "Documents", "PulsoScripts", `${name}.ps1`);
6095
+ const scriptPath = join4(
6096
+ HOME3,
6097
+ "Documents",
6098
+ "PulsoScripts",
6099
+ `${name}.ps1`
6100
+ );
5693
6101
  if (existsSync3(scriptPath)) {
5694
6102
  const inputArg = input2 ? `-InputData '${input2.replace(/'/g, "''")}'` : "";
5695
6103
  const output2 = await runPowerShell(
@@ -5716,9 +6124,7 @@ Write-Output "$uptimeStr"
5716
6124
  async dnd(enabled) {
5717
6125
  try {
5718
6126
  if (enabled === void 0 || enabled) {
5719
- await runPowerShell(
5720
- `Start-Process "ms-settings:quiethours"`
5721
- );
6127
+ await runPowerShell(`Start-Process "ms-settings:quiethours"`);
5722
6128
  return {
5723
6129
  success: true,
5724
6130
  data: {
@@ -6142,7 +6548,11 @@ $chromeProcs | ForEach-Object { Write-Output "Chrome|$($_.MainWindowTitle)|" }
6142
6548
  if (!output2) return [];
6143
6549
  return output2.split("\n").filter(Boolean).map((line) => {
6144
6550
  const [browser, title] = line.split("|");
6145
- return { browser: browser || "Unknown", title: title || "", url: "" };
6551
+ return {
6552
+ browser: browser || "Unknown",
6553
+ title: title || "",
6554
+ url: ""
6555
+ };
6146
6556
  });
6147
6557
  } catch {
6148
6558
  return [];
@@ -6239,7 +6649,11 @@ foreach ($w in $windows) {
6239
6649
  if (output2.includes("executed")) {
6240
6650
  return {
6241
6651
  success: true,
6242
- data: { code, method: "ie-com", note: "Executed via IE COM object. For modern browser JS, use Chrome CDP." }
6652
+ data: {
6653
+ code,
6654
+ method: "ie-com",
6655
+ note: "Executed via IE COM object. For modern browser JS, use Chrome CDP."
6656
+ }
6243
6657
  };
6244
6658
  }
6245
6659
  return {
@@ -6257,11 +6671,23 @@ foreach ($w in $windows) {
6257
6671
  try {
6258
6672
  const userProfile = process.env.LOCALAPPDATA || process.env.APPDATA || "";
6259
6673
  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` },
6674
+ {
6675
+ browser: "Google Chrome",
6676
+ dir: `${userProfile}\\Google\\Chrome\\User Data`
6677
+ },
6678
+ {
6679
+ browser: "Microsoft Edge",
6680
+ dir: `${userProfile}\\Microsoft\\Edge\\User Data`
6681
+ },
6682
+ {
6683
+ browser: "Brave Browser",
6684
+ dir: `${userProfile}\\BraveSoftware\\Brave-Browser\\User Data`
6685
+ },
6263
6686
  { browser: "Vivaldi", dir: `${userProfile}\\Vivaldi\\User Data` },
6264
- { browser: "Opera", dir: `${userProfile}\\Opera Software\\Opera Stable` }
6687
+ {
6688
+ browser: "Opera",
6689
+ dir: `${userProfile}\\Opera Software\\Opera Stable`
6690
+ }
6265
6691
  ];
6266
6692
  const profiles = [];
6267
6693
  for (const { browser, dir } of browserPaths) {
@@ -6410,7 +6836,11 @@ try {
6410
6836
  const output2 = await runPowerShellScript(script, 15e3);
6411
6837
  const reminders = output2.split("\n").filter(Boolean).map((line) => {
6412
6838
  const [name, due, listName] = line.split("|");
6413
- return { name: name || "Untitled", due: due || void 0, list: listName || void 0 };
6839
+ return {
6840
+ name: name || "Untitled",
6841
+ due: due || void 0,
6842
+ list: listName || void 0
6843
+ };
6414
6844
  });
6415
6845
  return { success: true, data: { reminders, count: reminders.length } };
6416
6846
  } catch (err) {
@@ -6447,7 +6877,10 @@ try {
6447
6877
  data: { title, dueDate, list, method: "outlook-task" }
6448
6878
  };
6449
6879
  }
6450
- return { success: false, error: "Failed to create reminder. Is Outlook installed?" };
6880
+ return {
6881
+ success: false,
6882
+ error: "Failed to create reminder. Is Outlook installed?"
6883
+ };
6451
6884
  } catch (err) {
6452
6885
  return {
6453
6886
  success: false,
@@ -6564,7 +6997,9 @@ try {
6564
6997
  name: f.replace(/\.(txt|md)$/, ""),
6565
6998
  modified: stat.mtime.toISOString()
6566
6999
  };
6567
- }).sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
7000
+ }).sort(
7001
+ (a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime()
7002
+ );
6568
7003
  const max = limit || 20;
6569
7004
  return {
6570
7005
  success: true,
@@ -6617,7 +7052,12 @@ ${body}`;
6617
7052
  await runPowerShell(`Start-Process "${gmailUrl}"`);
6618
7053
  return {
6619
7054
  success: true,
6620
- data: { to, subject, method: "gmail", note: "Gmail compose window opened" }
7055
+ data: {
7056
+ to,
7057
+ subject,
7058
+ method: "gmail",
7059
+ note: "Gmail compose window opened"
7060
+ }
6621
7061
  };
6622
7062
  } catch (err) {
6623
7063
  return {
@@ -6773,7 +7213,10 @@ try {
6773
7213
  const srcPath = safePath3(source);
6774
7214
  const dstPath = safePath3(destination);
6775
7215
  if (!srcPath || !dstPath) {
6776
- return { success: false, error: "Access denied to source or destination." };
7216
+ return {
7217
+ success: false,
7218
+ error: "Access denied to source or destination."
7219
+ };
6777
7220
  }
6778
7221
  if (!existsSync3(srcPath)) {
6779
7222
  return { success: false, error: `Source not found: ${source}` };
@@ -6795,7 +7238,10 @@ try {
6795
7238
  const srcPath = safePath3(source);
6796
7239
  const dstPath = safePath3(destination);
6797
7240
  if (!srcPath || !dstPath) {
6798
- return { success: false, error: "Access denied to source or destination." };
7241
+ return {
7242
+ success: false,
7243
+ error: "Access denied to source or destination."
7244
+ };
6799
7245
  }
6800
7246
  if (!existsSync3(srcPath)) {
6801
7247
  return { success: false, error: `Source not found: ${source}` };
@@ -7171,12 +7617,19 @@ $input.u.ki.dwFlags = [WinInput]::KEYEVENTF_KEYUP
7171
7617
  }
7172
7618
  return {
7173
7619
  success: true,
7174
- data: { nowPlaying: output2, state: "playing", source: "window_title" }
7620
+ data: {
7621
+ nowPlaying: output2,
7622
+ state: "playing",
7623
+ source: "window_title"
7624
+ }
7175
7625
  };
7176
7626
  }
7177
7627
  return {
7178
7628
  success: true,
7179
- data: { state: "paused_or_idle", note: "Spotify may be paused or not playing" }
7629
+ data: {
7630
+ state: "paused_or_idle",
7631
+ note: "Spotify may be paused or not playing"
7632
+ }
7180
7633
  };
7181
7634
  } catch {
7182
7635
  return {
@@ -7250,7 +7703,10 @@ $input.u.ki.dwFlags = [WinInput]::KEYEVENTF_KEYUP
7250
7703
  }
7251
7704
  const lightId = await resolveHueLight2(config, light);
7252
7705
  if (!lightId) {
7253
- return { success: false, error: `Light '${light}' not found on Hue bridge` };
7706
+ return {
7707
+ success: false,
7708
+ error: `Light '${light}' not found on Hue bridge`
7709
+ };
7254
7710
  }
7255
7711
  const state = { on: true };
7256
7712
  if (brightness !== void 0) {
@@ -7263,7 +7719,12 @@ $input.u.ki.dwFlags = [WinInput]::KEYEVENTF_KEYUP
7263
7719
  state.xy = [x, y];
7264
7720
  }
7265
7721
  }
7266
- const res = await hueRequest2(config, `lights/${lightId}/state`, "PUT", state);
7722
+ const res = await hueRequest2(
7723
+ config,
7724
+ `lights/${lightId}/state`,
7725
+ "PUT",
7726
+ state
7727
+ );
7267
7728
  return {
7268
7729
  success: true,
7269
7730
  data: { light: lightId, action: "on", ...state, response: res }
@@ -7285,7 +7746,9 @@ $input.u.ki.dwFlags = [WinInput]::KEYEVENTF_KEYUP
7285
7746
  if (!lightId) {
7286
7747
  return { success: false, error: `Light '${light}' not found` };
7287
7748
  }
7288
- const res = await hueRequest2(config, `lights/${lightId}/state`, "PUT", { on: false });
7749
+ const res = await hueRequest2(config, `lights/${lightId}/state`, "PUT", {
7750
+ on: false
7751
+ });
7289
7752
  return {
7290
7753
  success: true,
7291
7754
  data: { light: lightId, action: "off", response: res }
@@ -7555,11 +8018,11 @@ $input.u.ki.dwFlags = [WinInput]::KEYEVENTF_KEYUP
7555
8018
  try {
7556
8019
  const url = getSonosApiUrl2();
7557
8020
  if (!url) return { success: false, error: "Sonos not configured." };
7558
- const res = await sonosRequest2(
7559
- url,
7560
- `${encodeURIComponent(room)}/state`
7561
- );
7562
- return { success: true, data: { room, ...res } };
8021
+ const res = await sonosRequest2(url, `${encodeURIComponent(room)}/state`);
8022
+ return {
8023
+ success: true,
8024
+ data: { room, ...res }
8025
+ };
7563
8026
  } catch (err) {
7564
8027
  return {
7565
8028
  success: false,
@@ -7621,7 +8084,9 @@ async function loadWhisper(model = "base.en") {
7621
8084
  try {
7622
8085
  const sw = require_dist();
7623
8086
  const { Whisper, manager: manager2 } = sw;
7624
- console.log(` \u{1F3A4} Loading Whisper ${model} STT (downloading ~74-142MB, first use only)...`);
8087
+ console.log(
8088
+ ` \u{1F3A4} Loading Whisper ${model} STT (downloading ~74-142MB, first use only)...`
8089
+ );
7625
8090
  await manager2.download(model, (p) => {
7626
8091
  if (p % 25 === 0) process.stdout.write(`\r \u{1F4E5} ${p}% `);
7627
8092
  });
@@ -7634,7 +8099,10 @@ async function loadWhisper(model = "base.en") {
7634
8099
  } catch (err) {
7635
8100
  whisperState = "failed";
7636
8101
  const msg = err.message ?? String(err);
7637
- console.warn(" \u2139\uFE0F smart-whisper STT unavailable (optional):", msg.slice(0, 120));
8102
+ console.warn(
8103
+ " \u2139\uFE0F smart-whisper STT unavailable (optional):",
8104
+ msg.slice(0, 120)
8105
+ );
7638
8106
  }
7639
8107
  })();
7640
8108
  return whisperLoadPromise;
@@ -7670,7 +8138,10 @@ function decodeWav(buffer) {
7670
8138
  const channels = buffer.readUInt16LE(fmtOffset + 10);
7671
8139
  const sampleRate = buffer.readUInt32LE(fmtOffset + 12);
7672
8140
  const bitsPerSample = buffer.readUInt16LE(fmtOffset + 22);
7673
- if (audioFmt !== 1) throw new Error(`Unsupported WAV format: ${audioFmt} (only PCM=1 supported)`);
8141
+ if (audioFmt !== 1)
8142
+ throw new Error(
8143
+ `Unsupported WAV format: ${audioFmt} (only PCM=1 supported)`
8144
+ );
7674
8145
  let dataOffset = 36;
7675
8146
  let dataSize = 0;
7676
8147
  while (dataOffset < buffer.length - 8) {
@@ -7738,7 +8209,10 @@ async function transcribeWithWhisper(pcmOrWav, inputSampleRate, opts) {
7738
8209
  durationMs: Date.now() - t0
7739
8210
  };
7740
8211
  } catch (err) {
7741
- console.warn(" \u26A0\uFE0F Whisper transcription failed:", err.message.slice(0, 100));
8212
+ console.warn(
8213
+ " \u26A0\uFE0F Whisper transcription failed:",
8214
+ err.message.slice(0, 100)
8215
+ );
7742
8216
  return null;
7743
8217
  }
7744
8218
  }
@@ -7899,7 +8373,10 @@ async function streamChatResponse(params, hooks, options) {
7899
8373
  if (options?.signal?.aborted || err?.name === "AbortError") {
7900
8374
  return { ok: false, error: "aborted" };
7901
8375
  }
7902
- return { ok: false, error: err.message || "stream read failed" };
8376
+ return {
8377
+ ok: false,
8378
+ error: err.message || "stream read failed"
8379
+ };
7903
8380
  }
7904
8381
  if (chunk.done) break;
7905
8382
  buffer += decoder.decode(chunk.value, { stream: true });
@@ -8170,7 +8647,11 @@ async function runIdeChatTui(params) {
8170
8647
  const bars = 4 + Math.round(amp * 12);
8171
8648
  return `[${"=".repeat(bars).padEnd(16, " ")}]`;
8172
8649
  });
8173
- const now = () => (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
8650
+ const now = () => (/* @__PURE__ */ new Date()).toLocaleTimeString([], {
8651
+ hour: "2-digit",
8652
+ minute: "2-digit",
8653
+ second: "2-digit"
8654
+ });
8174
8655
  const formatTimestamp = (at) => new Date(at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
8175
8656
  const shortValue = (value, max = 28) => value.length > max ? `${value.slice(0, max - 3)}...` : value;
8176
8657
  const pushAssistantNote = (message) => {
@@ -8206,7 +8687,9 @@ async function runIdeChatTui(params) {
8206
8687
  if (busy) {
8207
8688
  const dots = ".".repeat(pulseTick % 3 + 1);
8208
8689
  chunks.push(`\u25CF PULSO ${formatTimestamp(Date.now())}`);
8209
- chunks.push(...(currentAssistant || `thinking${dots}`).split("\n").map((line) => ` ${line}`));
8690
+ chunks.push(
8691
+ ...(currentAssistant || `thinking${dots}`).split("\n").map((line) => ` ${line}`)
8692
+ );
8210
8693
  chunks.push("");
8211
8694
  }
8212
8695
  return chunks.join("\n");
@@ -8220,11 +8703,9 @@ async function runIdeChatTui(params) {
8220
8703
  const pulseFrame = pulseFrames[pulseTick];
8221
8704
  const logoPulse = busy ? "\u25C9" : "\u25CE";
8222
8705
  logoBox.setContent(
8223
- [
8224
- ` ${logoPulse} ${pulseFrame}`,
8225
- " PULSO",
8226
- ` ${IDE_VERSION}`
8227
- ].join("\n")
8706
+ [` ${logoPulse} ${pulseFrame}`, " PULSO", ` ${IDE_VERSION}`].join(
8707
+ "\n"
8708
+ )
8228
8709
  );
8229
8710
  heroBox.setContent(
8230
8711
  [
@@ -8393,12 +8874,16 @@ async function runIdeChatTui(params) {
8393
8874
  if (cmd === "theme") {
8394
8875
  const normalizedTheme = value.toLowerCase();
8395
8876
  if (!normalizedTheme) {
8396
- pushAssistantNote(`Current theme: ${currentThemeName}
8397
- Available themes: pulso, claude`);
8877
+ pushAssistantNote(
8878
+ `Current theme: ${currentThemeName}
8879
+ Available themes: pulso, claude`
8880
+ );
8398
8881
  pushTimeline("theme unchanged");
8399
8882
  } else if (normalizedTheme !== "pulso" && normalizedTheme !== "claude") {
8400
- pushAssistantNote(`Invalid theme: ${value}
8401
- Use /theme pulso or /theme claude`);
8883
+ pushAssistantNote(
8884
+ `Invalid theme: ${value}
8885
+ Use /theme pulso or /theme claude`
8886
+ );
8402
8887
  pushTimeline("invalid theme");
8403
8888
  } else {
8404
8889
  currentThemeName = normalizedTheme;
@@ -8424,8 +8909,10 @@ Use /theme pulso or /theme claude`);
8424
8909
  return "handled";
8425
8910
  }
8426
8911
  if (normalized.startsWith("/")) {
8427
- pushAssistantNote(`Unknown command: ${normalized}
8428
- Use /help to list available commands.`);
8912
+ pushAssistantNote(
8913
+ `Unknown command: ${normalized}
8914
+ Use /help to list available commands.`
8915
+ );
8429
8916
  pushTimeline("unknown command");
8430
8917
  render();
8431
8918
  return "handled";
@@ -8435,7 +8922,9 @@ Use /help to list available commands.`);
8435
8922
  const sendPrompt = async (raw) => {
8436
8923
  const prompt = raw.trim();
8437
8924
  if (!prompt) return;
8438
- if (prompt.startsWith("/") || ["help", "clear", "quit", "exit", "settings", "stats", "theme"].includes(prompt.toLowerCase())) {
8925
+ if (prompt.startsWith("/") || ["help", "clear", "quit", "exit", "settings", "stats", "theme"].includes(
8926
+ prompt.toLowerCase()
8927
+ )) {
8439
8928
  const commandResult = handleCommand2(prompt);
8440
8929
  if (commandResult === "exit") {
8441
8930
  exit();
@@ -8517,7 +9006,9 @@ Use /help to list available commands.`);
8517
9006
  at: Date.now(),
8518
9007
  content: currentAssistant || "(empty response)"
8519
9008
  });
8520
- pushTimeline(`usage +$${usageCost.toFixed(4)} \xB7 tok ${usageIn}/${usageOut}`);
9009
+ pushTimeline(
9010
+ `usage +$${usageCost.toFixed(4)} \xB7 tok ${usageIn}/${usageOut}`
9011
+ );
8521
9012
  }
8522
9013
  currentAssistant = "";
8523
9014
  render();
@@ -8621,7 +9112,8 @@ async function runInteractiveChat(params) {
8621
9112
  const prompt = promptRaw.trim();
8622
9113
  const normalized = prompt.toLowerCase();
8623
9114
  if (!prompt) continue;
8624
- if (normalized === "/exit" || normalized === "/quit" || normalized === "exit" || normalized === "quit") break;
9115
+ if (normalized === "/exit" || normalized === "/quit" || normalized === "exit" || normalized === "quit")
9116
+ break;
8625
9117
  if (normalized === "clear" || normalized === "/clear") {
8626
9118
  if (output.isTTY) {
8627
9119
  output.write("\x1Bc");
@@ -8686,7 +9178,11 @@ async function runInteractiveChat(params) {
8686
9178
  }
8687
9179
  totalCost += result.costUsd ?? 0;
8688
9180
  if (typeof result.costUsd === "number") {
8689
- console.log(dimText(` [cost: $${result.costUsd.toFixed(4)} | total: $${totalCost.toFixed(4)}]`));
9181
+ console.log(
9182
+ dimText(
9183
+ ` [cost: $${result.costUsd.toFixed(4)} | total: $${totalCost.toFixed(4)}]`
9184
+ )
9185
+ );
8690
9186
  console.log("");
8691
9187
  } else {
8692
9188
  console.log("");
@@ -8714,12 +9210,7 @@ async function deviceLogin(apiUrl) {
8714
9210
  console.error(` Failed to request device code: ${err}`);
8715
9211
  return null;
8716
9212
  }
8717
- const {
8718
- device_code,
8719
- user_code,
8720
- verification_url,
8721
- interval
8722
- } = await codeRes.json();
9213
+ const { device_code, user_code, verification_url, interval } = await codeRes.json();
8723
9214
  console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
8724
9215
  console.log(" \u2502 \u2502");
8725
9216
  console.log(" \u2502 Open this URL in your browser: \u2502");
@@ -8800,9 +9291,15 @@ if (helpRequested || subCommand === "help") {
8800
9291
  console.log(" Pulso Companion Commands");
8801
9292
  console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
8802
9293
  console.log(` ${COMMAND_LOGIN.padEnd(26)} Authenticate device`);
8803
- console.log(` ${COMMAND_LOGOUT.padEnd(26)} Revoke token + clear local creds`);
8804
- console.log(` ${`${INVOKED_AS_PULSO ? "pulso status" : "pulso-companion status"}`.padEnd(26)} Show companion status`);
8805
- console.log(` ${COMMAND_CHAT.padEnd(26)} Interactive terminal IDE (default)`);
9294
+ console.log(
9295
+ ` ${COMMAND_LOGOUT.padEnd(26)} Revoke token + clear local creds`
9296
+ );
9297
+ console.log(
9298
+ ` ${`${INVOKED_AS_PULSO ? "pulso status" : "pulso-companion status"}`.padEnd(26)} Show companion status`
9299
+ );
9300
+ console.log(
9301
+ ` ${COMMAND_CHAT.padEnd(26)} Interactive terminal IDE (default)`
9302
+ );
8806
9303
  console.log(` ${`${COMMAND_CHAT} --plain`.padEnd(26)} Plain terminal chat`);
8807
9304
  console.log(` ${COMMAND_IDE.padEnd(26)} Full-screen terminal IDE`);
8808
9305
  console.log(` ${COMMAND_DAEMON.padEnd(26)} Start companion daemon`);
@@ -8858,7 +9355,9 @@ if (subCommand === "logout") {
8858
9355
  if (res.ok) {
8859
9356
  console.log("\n Token revoked on server.");
8860
9357
  } else {
8861
- console.log("\n Warning: Could not revoke token on server (may already be expired).");
9358
+ console.log(
9359
+ "\n Warning: Could not revoke token on server (may already be expired)."
9360
+ );
8862
9361
  }
8863
9362
  } catch {
8864
9363
  console.log("\n Warning: Could not reach server to revoke token.");
@@ -8925,8 +9424,10 @@ if (!TOKEN) {
8925
9424
  console.log(" No token found. Starting browser device login...\n");
8926
9425
  const creds = await deviceLogin(API_URL);
8927
9426
  if (!creds?.token) {
8928
- console.error(` Login failed. Run '${COMMAND_LOGIN}' and approve in browser.
8929
- `);
9427
+ console.error(
9428
+ ` Login failed. Run '${COMMAND_LOGIN}' and approve in browser.
9429
+ `
9430
+ );
8930
9431
  process.exit(1);
8931
9432
  }
8932
9433
  TOKEN = creds.token;
@@ -8946,7 +9447,9 @@ var WAKE_WORD_SENSITIVITY_RAW = process.env.PULSO_WAKE_WORD_SENSITIVITY ?? proce
8946
9447
  var WAKE_WORD_VAD_THRESHOLD_RAW = process.env.PULSO_WAKE_WORD_VAD_THRESHOLD ?? process.argv.find((_, i, a) => a[i - 1] === "--wake-word-vad-threshold") ?? "";
8947
9448
  var WAKE_WORD_END_SILENCE_MS_RAW = process.env.PULSO_WAKE_WORD_END_SILENCE_MS ?? process.argv.find((_, i, a) => a[i - 1] === "--wake-word-end-silence-ms") ?? "";
8948
9449
  var WAKE_WORD_CALIBRATION_MS_RAW = process.env.PULSO_WAKE_WORD_CALIBRATION_MS ?? process.argv.find((_, i, a) => a[i - 1] === "--wake-word-calibration-ms") ?? "";
8949
- var WAKE_WORD_LOCAL_STT_BUDGET_MS_RAW = process.env.PULSO_WAKE_WORD_LOCAL_STT_BUDGET_MS ?? process.argv.find((_, i, a) => a[i - 1] === "--wake-word-local-stt-budget-ms") ?? "";
9450
+ var WAKE_WORD_LOCAL_STT_BUDGET_MS_RAW = process.env.PULSO_WAKE_WORD_LOCAL_STT_BUDGET_MS ?? process.argv.find(
9451
+ (_, i, a) => a[i - 1] === "--wake-word-local-stt-budget-ms"
9452
+ ) ?? "";
8950
9453
  var WS_BASE = API_URL.replace("https://", "wss://").replace("http://", "ws://") + "/ws/companion";
8951
9454
  var HOME4 = homedir4();
8952
9455
  var RECONNECT_DELAY = 5e3;
@@ -8976,9 +9479,15 @@ function acquireCompanionLock() {
8976
9479
  try {
8977
9480
  process.kill(existingPid, 0);
8978
9481
  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");
9482
+ console.log(
9483
+ " \u26A0\uFE0F Another Pulso Companion instance is already running."
9484
+ );
9485
+ console.log(
9486
+ ` PID: ${existingPid}${parsed?.startedAt ? ` (${parsed.startedAt})` : ""}`
9487
+ );
9488
+ console.log(
9489
+ " Exiting this instance to avoid command collisions.\n"
9490
+ );
8982
9491
  process.exit(0);
8983
9492
  } catch {
8984
9493
  }
@@ -9038,27 +9547,27 @@ function runAppleScript2(script) {
9038
9547
  return new Promise((resolve5, reject) => {
9039
9548
  const tmpPath = `/tmp/pulso-as-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.scpt`;
9040
9549
  writeFileSync5(tmpPath, script, "utf-8");
9041
- exec5(
9042
- `osascript ${tmpPath}`,
9043
- { timeout: 15e3 },
9044
- (err, stdout, stderr) => {
9045
- try {
9046
- unlinkSync5(tmpPath);
9047
- } catch {
9048
- }
9049
- if (err) reject(new Error(stderr || err.message));
9050
- else resolve5(stdout.trim());
9550
+ exec5(`osascript ${tmpPath}`, { timeout: 15e3 }, (err, stdout, stderr) => {
9551
+ try {
9552
+ unlinkSync5(tmpPath);
9553
+ } catch {
9051
9554
  }
9052
- );
9555
+ if (err) reject(new Error(stderr || err.message));
9556
+ else resolve5(stdout.trim());
9557
+ });
9053
9558
  });
9054
9559
  }
9055
9560
  function runShell4(cmd, timeout = 1e4) {
9056
9561
  return new Promise((resolve5, reject) => {
9057
9562
  const shell = process.env.SHELL || "/bin/zsh";
9058
- exec5(cmd, { timeout, shell, env: { ...process.env, PATH: augmentedPath2() } }, (err, stdout, stderr) => {
9059
- if (err) reject(new Error(stderr || err.message));
9060
- else resolve5(stdout.trim());
9061
- });
9563
+ exec5(
9564
+ cmd,
9565
+ { timeout, shell, env: { ...process.env, PATH: augmentedPath2() } },
9566
+ (err, stdout, stderr) => {
9567
+ if (err) reject(new Error(stderr || err.message));
9568
+ else resolve5(stdout.trim());
9569
+ }
9570
+ );
9062
9571
  });
9063
9572
  }
9064
9573
  function augmentedPath2() {
@@ -9093,11 +9602,14 @@ function runSwift2(code, timeout = 1e4) {
9093
9602
  }
9094
9603
  async function hasScreenRecordingPermission2() {
9095
9604
  try {
9096
- const out = await runSwift2(`
9605
+ const out = await runSwift2(
9606
+ `
9097
9607
  import Cocoa
9098
9608
  import CoreGraphics
9099
9609
  print(CGPreflightScreenCaptureAccess() ? "granted" : "denied")
9100
- `, 6e3);
9610
+ `,
9611
+ 6e3
9612
+ );
9101
9613
  return out.trim().toLowerCase() === "granted";
9102
9614
  } catch {
9103
9615
  return false;
@@ -9210,7 +9722,10 @@ var ADAPTER_COMMANDS = {
9210
9722
  const buttonName = p.button;
9211
9723
  const procName = p.procName ?? activeDialog?.procName;
9212
9724
  if (!procName || !buttonName) {
9213
- return { success: false, error: "No active dialog or button not specified" };
9725
+ return {
9726
+ success: false,
9727
+ error: "No active dialog or button not specified"
9728
+ };
9214
9729
  }
9215
9730
  const script = `
9216
9731
  tell application "System Events"
@@ -9231,7 +9746,9 @@ tell application "System Events"
9231
9746
  end tell`;
9232
9747
  try {
9233
9748
  await runAppleScript2(script);
9234
- console.log(` \u2713 Dialog action: clicked "${buttonName}" in [${procName}]`);
9749
+ console.log(
9750
+ ` \u2713 Dialog action: clicked "${buttonName}" in [${procName}]`
9751
+ );
9235
9752
  activeDialog = null;
9236
9753
  lastDialogSignature = null;
9237
9754
  return { success: true, clicked: buttonName };
@@ -9242,9 +9759,18 @@ end tell`;
9242
9759
  sys_clipboard_read: (a, _) => a.clipboardRead(),
9243
9760
  sys_clipboard_write: (a, p) => a.clipboardWrite(p.text),
9244
9761
  sys_screenshot: (a, _) => a.screenshot(),
9245
- sys_mouse_click: (a, p) => a.mouseClick(Number(p.x), Number(p.y), p.button ?? "left"),
9762
+ sys_mouse_click: (a, p) => a.mouseClick(
9763
+ Number(p.x),
9764
+ Number(p.y),
9765
+ p.button ?? "left"
9766
+ ),
9246
9767
  sys_mouse_double_click: (a, p) => a.mouseDoubleClick(Number(p.x), Number(p.y)),
9247
- sys_mouse_scroll: (a, p) => a.mouseScroll(Number(p.scrollY), Number(p.scrollX) || 0, Number(p.x) || 0, Number(p.y) || 0),
9768
+ sys_mouse_scroll: (a, p) => a.mouseScroll(
9769
+ Number(p.scrollY),
9770
+ Number(p.scrollX) || 0,
9771
+ Number(p.x) || 0,
9772
+ Number(p.y) || 0
9773
+ ),
9248
9774
  sys_mouse_move: (a, p) => a.mouseMove(Number(p.x), Number(p.y)),
9249
9775
  sys_drag: (a, p) => a.drag(Number(p.fromX), Number(p.fromY), Number(p.toX), Number(p.toY)),
9250
9776
  sys_get_cursor_position: (a, _) => a.getCursorPosition(),
@@ -9257,7 +9783,10 @@ end tell`;
9257
9783
  sys_browser_list_tabs: (a, _) => a.browserListTabs(),
9258
9784
  sys_browser_navigate: (a, p) => a.browserNavigate(p.url, p.browser),
9259
9785
  sys_browser_new_tab: (a, p) => a.browserNewTab(p.url, p.browser),
9260
- sys_browser_read_page: (a, p) => a.browserReadPage(p.browser, Number(p.maxLength) || void 0),
9786
+ sys_browser_read_page: (a, p) => a.browserReadPage(
9787
+ p.browser,
9788
+ Number(p.maxLength) || void 0
9789
+ ),
9261
9790
  sys_browser_execute_js: (a, p) => a.browserExecuteJs(p.code, p.browser),
9262
9791
  sys_browser_list_profiles: (a, _) => a.browserListProfiles(),
9263
9792
  sys_calendar_list: (a, p) => a.calendarList(Number(p.days) || void 0),
@@ -9277,26 +9806,61 @@ end tell`;
9277
9806
  sys_imessage_send: (a, p) => a.sendMessage(p.to, p.message),
9278
9807
  sys_system_info: (a, _) => a.getSystemInfo(),
9279
9808
  sys_dnd: (a, p) => a.dnd(p.enabled),
9280
- sys_shell: (a, p) => a.shell(p.command, p.cwd, Number(p.timeout) || void 0),
9809
+ sys_shell: (a, p) => a.shell(
9810
+ p.command,
9811
+ p.cwd,
9812
+ Number(p.timeout) || void 0
9813
+ ),
9281
9814
  sys_run_shortcut: (a, p) => a.runShortcut(p.name, p.input),
9282
9815
  sys_file_read: (a, p) => a.fileRead(p.path),
9283
9816
  sys_file_write: (a, p) => a.fileWrite(p.path, p.content),
9284
- sys_file_list: (a, p) => a.fileList(p.path, p.showHidden),
9285
- sys_file_move: (a, p) => a.fileMove(p.source ?? p.from, p.destination ?? p.to),
9286
- sys_file_copy: (a, p) => a.fileCopy(p.source ?? p.from, p.destination ?? p.to),
9817
+ sys_file_list: (a, p) => a.fileList(
9818
+ p.path,
9819
+ p.showHidden
9820
+ ),
9821
+ sys_file_move: (a, p) => a.fileMove(
9822
+ p.source ?? p.from,
9823
+ p.destination ?? p.to
9824
+ ),
9825
+ sys_file_copy: (a, p) => a.fileCopy(
9826
+ p.source ?? p.from,
9827
+ p.destination ?? p.to
9828
+ ),
9287
9829
  sys_file_delete: (a, p) => a.fileDelete(p.path),
9288
9830
  sys_file_info: (a, p) => a.fileInfo(p.path),
9289
- sys_download: (a, p) => a.download(p.url, p.destination ?? p.path ?? void 0),
9831
+ sys_download: (a, p) => a.download(
9832
+ p.url,
9833
+ p.destination ?? p.path ?? void 0
9834
+ ),
9290
9835
  sys_window_list: (a, _) => a.windowList(),
9291
9836
  sys_window_focus: (a, p) => a.windowFocus(p.app),
9292
- sys_window_resize: (a, p) => a.windowResize(p.app, Number(p.x) || void 0, Number(p.y) || void 0, Number(p.width) || void 0, Number(p.height) || void 0),
9837
+ sys_window_resize: (a, p) => a.windowResize(
9838
+ p.app,
9839
+ Number(p.x) || void 0,
9840
+ Number(p.y) || void 0,
9841
+ Number(p.width) || void 0,
9842
+ Number(p.height) || void 0
9843
+ ),
9293
9844
  sys_notes_list: (a, p) => a.notesList(Number(p.limit) || void 0),
9294
- sys_notes_create: (a, p) => a.notesCreate(p.title, p.body, p.folder),
9845
+ sys_notes_create: (a, p) => a.notesCreate(
9846
+ p.title,
9847
+ p.body,
9848
+ p.folder
9849
+ ),
9295
9850
  sys_contacts_search: (a, p) => a.contactsSearch(p.query),
9296
9851
  sys_ocr: (a, p) => a.ocr(p.imagePath ?? p.path),
9297
- sys_email_send: (a, p) => a.emailSend(p.to, p.subject, p.body, p.method),
9852
+ sys_email_send: (a, p) => a.emailSend(
9853
+ p.to,
9854
+ p.subject,
9855
+ p.body,
9856
+ p.method
9857
+ ),
9298
9858
  sys_spotify: (a, p) => a.spotify(p.action, p),
9299
- sys_hue_lights_on: (a, p) => a.hueLightsOn(p.light, Number(p.brightness) || void 0, p.color),
9859
+ sys_hue_lights_on: (a, p) => a.hueLightsOn(
9860
+ p.light,
9861
+ Number(p.brightness) || void 0,
9862
+ p.color
9863
+ ),
9300
9864
  sys_hue_lights_off: (a, p) => a.hueLightsOff(p.light),
9301
9865
  sys_hue_lights_color: (a, p) => a.hueLightsColor(p.light, p.color),
9302
9866
  sys_hue_lights_brightness: (a, p) => a.hueLightsBrightness(p.light, Number(p.brightness)),
@@ -9305,7 +9869,11 @@ end tell`;
9305
9869
  sys_sonos_play: (a, p) => a.sonosPlay(p.room),
9306
9870
  sys_sonos_pause: (a, p) => a.sonosPause(p.room),
9307
9871
  sys_sonos_volume: (a, p) => a.sonosVolume(p.room, Number(p.level)),
9308
- sys_sonos_play_uri: (a, p) => a.sonosPlayUri(p.room, p.uri, p.title),
9872
+ sys_sonos_play_uri: (a, p) => a.sonosPlayUri(
9873
+ p.room,
9874
+ p.uri,
9875
+ p.title
9876
+ ),
9309
9877
  sys_sonos_rooms: (a, _) => a.sonosRooms(),
9310
9878
  sys_sonos_next: (a, p) => a.sonosNext(p.room),
9311
9879
  sys_sonos_previous: (a, p) => a.sonosPrevious(p.room),
@@ -9340,7 +9908,10 @@ async function handleCommand(command, params, streamCb) {
9340
9908
  });
9341
9909
  clearTimeout(timeout);
9342
9910
  if (!res.ok) {
9343
- return { success: true, data: { running: false, error: `HTTP ${res.status}` } };
9911
+ return {
9912
+ success: true,
9913
+ data: { running: false, error: `HTTP ${res.status}` }
9914
+ };
9344
9915
  }
9345
9916
  const data = await res.json();
9346
9917
  const models = (data.models ?? []).map((m) => ({
@@ -9351,7 +9922,12 @@ async function handleCommand(command, params, streamCb) {
9351
9922
  }));
9352
9923
  return {
9353
9924
  success: true,
9354
- data: { running: true, url: "http://localhost:11434", modelCount: models.length, models }
9925
+ data: {
9926
+ running: true,
9927
+ url: "http://localhost:11434",
9928
+ modelCount: models.length,
9929
+ models
9930
+ }
9355
9931
  };
9356
9932
  } catch {
9357
9933
  return { success: true, data: { running: false } };
@@ -9366,7 +9942,10 @@ async function handleCommand(command, params, streamCb) {
9366
9942
  return { success: true, data: result };
9367
9943
  }
9368
9944
  if (adapter.platform !== "macos") {
9369
- return { success: false, error: `Command ${command} is not available on ${adapter.platform}. It is currently macOS-only.` };
9945
+ return {
9946
+ success: false,
9947
+ error: `Command ${command} is not available on ${adapter.platform}. It is currently macOS-only.`
9948
+ };
9370
9949
  }
9371
9950
  switch (command) {
9372
9951
  // ── macOS-only commands (not in the cross-platform adapter interface) ──
@@ -9389,21 +9968,42 @@ async function handleCommand(command, params, streamCb) {
9389
9968
  const regPath = `/tmp/pulso-ss-region-${ts2}.png`;
9390
9969
  const regJpg = `/tmp/pulso-ss-region-${ts2}.jpg`;
9391
9970
  try {
9392
- await runShell4(`screencapture -x -R${rx},${ry},${rw},${rh} ${regPath}`, 15e3);
9971
+ await runShell4(
9972
+ `screencapture -x -R${rx},${ry},${rw},${rh} ${regPath}`,
9973
+ 15e3
9974
+ );
9393
9975
  } catch (e) {
9394
- return { success: false, error: `Region screenshot failed: ${e.message}`, errorCode: "SCREENSHOT_FAILED" };
9976
+ return {
9977
+ success: false,
9978
+ error: `Region screenshot failed: ${e.message}`,
9979
+ errorCode: "SCREENSHOT_FAILED"
9980
+ };
9395
9981
  }
9396
9982
  if (!existsSync4(regPath))
9397
- return { success: false, error: "Region screenshot failed", errorCode: "SCREENSHOT_FAILED" };
9983
+ return {
9984
+ success: false,
9985
+ error: "Region screenshot failed",
9986
+ errorCode: "SCREENSHOT_FAILED"
9987
+ };
9398
9988
  try {
9399
- await runShell4(`sips --setProperty format jpeg --setProperty formatOptions 85 ${regPath} --out ${regJpg}`, 1e4);
9989
+ await runShell4(
9990
+ `sips --setProperty format jpeg --setProperty formatOptions 85 ${regPath} --out ${regJpg}`,
9991
+ 1e4
9992
+ );
9400
9993
  } catch {
9401
9994
  const rb = readFileSync4(regPath);
9402
9995
  try {
9403
9996
  unlinkSync5(regPath);
9404
9997
  } catch {
9405
9998
  }
9406
- return { success: true, data: { image: `data:image/png;base64,${rb.toString("base64")}`, format: "png", region: { x: rx, y: ry, width: rw, height: rh } } };
9999
+ return {
10000
+ success: true,
10001
+ data: {
10002
+ image: `data:image/png;base64,${rb.toString("base64")}`,
10003
+ format: "png",
10004
+ region: { x: rx, y: ry, width: rw, height: rh }
10005
+ }
10006
+ };
9407
10007
  }
9408
10008
  const rb2 = readFileSync4(regJpg);
9409
10009
  try {
@@ -9441,7 +10041,16 @@ for (i, screen) in screens.enumerated() {
9441
10041
  print(result)`;
9442
10042
  const raw = await runSwift2(swift, 15e3);
9443
10043
  const displays = raw.trim().split("\n").filter(Boolean).map((line) => {
9444
- const [index, name, origin, size, visOrigin, visSize, scale, isMain] = line.split("|");
10044
+ const [
10045
+ index,
10046
+ name,
10047
+ origin,
10048
+ size,
10049
+ visOrigin,
10050
+ visSize,
10051
+ scale,
10052
+ isMain
10053
+ ] = line.split("|");
9445
10054
  const [ox, oy] = (origin || "0,0").split(",").map(Number);
9446
10055
  const [sw2, sh2] = (size || "0,0").split(",").map(Number);
9447
10056
  const [vox, voy] = (visOrigin || "0,0").split(",").map(Number);
@@ -9470,7 +10079,10 @@ print(result)`;
9470
10079
  }
9471
10080
  };
9472
10081
  } catch (e) {
9473
- return { success: false, error: `Failed to list displays: ${e.message}` };
10082
+ return {
10083
+ success: false,
10084
+ error: `Failed to list displays: ${e.message}`
10085
+ };
9474
10086
  }
9475
10087
  }
9476
10088
  // ── NEW: System Settings ──────────────────────────────
@@ -9508,7 +10120,9 @@ print(result)`;
9508
10120
  `open "x-apple.systempreferences:com.apple.settings.${pane}" 2>/dev/null || open "x-apple.systempreferences:${paneId}" 2>/dev/null || open "System Preferences"`
9509
10121
  );
9510
10122
  } else {
9511
- await runShell4(`open "System Preferences" 2>/dev/null || open -a "System Settings"`);
10123
+ await runShell4(
10124
+ `open "System Preferences" 2>/dev/null || open -a "System Settings"`
10125
+ );
9512
10126
  }
9513
10127
  return { success: true, data: { pane: pane || "main" } };
9514
10128
  }
@@ -9706,9 +10320,7 @@ print(result)`;
9706
10320
  const device = params.device;
9707
10321
  if (!device) return { success: false, error: "Missing device name" };
9708
10322
  try {
9709
- await runShell4(
9710
- `SwitchAudioSource -s "${device}" 2>/dev/null`
9711
- );
10323
+ await runShell4(`SwitchAudioSource -s "${device}" 2>/dev/null`);
9712
10324
  return { success: true, data: { switched: device } };
9713
10325
  } catch {
9714
10326
  return {
@@ -9730,12 +10342,8 @@ print(result)`;
9730
10342
  case "sys_trash": {
9731
10343
  const trashAction = params.action || "info";
9732
10344
  if (trashAction === "info") {
9733
- const count = await runShell4(
9734
- `ls -1 ~/.Trash 2>/dev/null | wc -l`
9735
- );
9736
- const size = await runShell4(
9737
- `du -sh ~/.Trash 2>/dev/null | cut -f1`
9738
- );
10345
+ const count = await runShell4(`ls -1 ~/.Trash 2>/dev/null | wc -l`);
10346
+ const size = await runShell4(`du -sh ~/.Trash 2>/dev/null | cut -f1`);
9739
10347
  return {
9740
10348
  success: true,
9741
10349
  data: {
@@ -9744,9 +10352,7 @@ print(result)`;
9744
10352
  }
9745
10353
  };
9746
10354
  } else if (trashAction === "empty") {
9747
- await runAppleScript2(
9748
- `tell application "Finder" to empty the trash`
9749
- );
10355
+ await runAppleScript2(`tell application "Finder" to empty the trash`);
9750
10356
  return { success: true, data: { emptied: true } };
9751
10357
  }
9752
10358
  return { success: false, error: "Use action: info or empty" };
@@ -9778,9 +10384,7 @@ print(result)`;
9778
10384
  case "sys_disk_info": {
9779
10385
  const df = await runShell4(`df -h / | tail -1`);
9780
10386
  const parts = df.trim().split(/\s+/);
9781
- const volumes = await runShell4(
9782
- `ls -1 /Volumes 2>/dev/null`
9783
- );
10387
+ const volumes = await runShell4(`ls -1 /Volumes 2>/dev/null`);
9784
10388
  return {
9785
10389
  success: true,
9786
10390
  data: {
@@ -9810,9 +10414,7 @@ print(result)`;
9810
10414
  await runShell4(`shortcuts run "Set ${mode}" 2>/dev/null`);
9811
10415
  return { success: true, data: { mode } };
9812
10416
  } catch {
9813
- await runShell4(
9814
- `shortcuts run "Toggle Do Not Disturb" 2>/dev/null`
9815
- );
10417
+ await runShell4(`shortcuts run "Toggle Do Not Disturb" 2>/dev/null`);
9816
10418
  return {
9817
10419
  success: true,
9818
10420
  data: {
@@ -9877,13 +10479,18 @@ print(result)`;
9877
10479
  await runShell4(`pmset sleepnow`);
9878
10480
  return { success: true, data: { sleeping: true } };
9879
10481
  }
9880
- return { success: false, error: "Use action: status, caffeinate, sleep" };
10482
+ return {
10483
+ success: false,
10484
+ error: "Use action: status, caffeinate, sleep"
10485
+ };
9881
10486
  }
9882
10487
  // ── NEW: Printer Management ─────────────────────────────
9883
10488
  case "sys_printer": {
9884
10489
  const prAction = params.action || "list";
9885
10490
  if (prAction === "list") {
9886
- const result = await runShell4(`lpstat -p 2>/dev/null || echo "No printers"`);
10491
+ const result = await runShell4(
10492
+ `lpstat -p 2>/dev/null || echo "No printers"`
10493
+ );
9887
10494
  return { success: true, data: { printers: result.trim() } };
9888
10495
  } else if (prAction === "print") {
9889
10496
  const file = params.file;
@@ -10016,7 +10623,9 @@ print(result.stdout[:5000])
10016
10623
  } else if (svcAction === "start") {
10017
10624
  const name = params.name;
10018
10625
  if (!name) return { success: false, error: "Missing service name" };
10019
- await runShell4(`launchctl kickstart gui/$(id -u)/${name} 2>/dev/null`);
10626
+ await runShell4(
10627
+ `launchctl kickstart gui/$(id -u)/${name} 2>/dev/null`
10628
+ );
10020
10629
  return { success: true, data: { started: name } };
10021
10630
  } else if (svcAction === "stop") {
10022
10631
  const name = params.name;
@@ -10099,7 +10708,9 @@ print(result.stdout[:5000])
10099
10708
  return;
10100
10709
  }
10101
10710
  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);
10711
+ const textBlocks = evt.message.content.filter(
10712
+ (p) => p?.type === "text" && typeof p?.text === "string"
10713
+ ).map((p) => p.text);
10103
10714
  if (textBlocks.length > 0) {
10104
10715
  assistantSnapshotText = textBlocks.join("");
10105
10716
  }
@@ -10258,12 +10869,18 @@ print(result.stdout[:5000])
10258
10869
  const version = await runShell4("codex --version 2>/dev/null", 5e3);
10259
10870
  let authStatus = "unknown";
10260
10871
  try {
10261
- const status = await runShell4("codex auth whoami 2>&1 || codex --help 2>&1 | head -5", 1e4);
10872
+ const status = await runShell4(
10873
+ "codex auth whoami 2>&1 || codex --help 2>&1 | head -5",
10874
+ 1e4
10875
+ );
10262
10876
  const lc = status.toLowerCase();
10263
10877
  authStatus = lc.includes("not logged in") || lc.includes("not authenticated") || lc.includes("sign in") || lc.includes("no api key") ? "not_authenticated" : "authenticated";
10264
10878
  } catch {
10265
10879
  try {
10266
- await runShell4("security find-generic-password -s 'openai-codex' 2>/dev/null || security find-generic-password -s 'codex' 2>/dev/null", 5e3);
10880
+ await runShell4(
10881
+ "security find-generic-password -s 'openai-codex' 2>/dev/null || security find-generic-password -s 'codex' 2>/dev/null",
10882
+ 5e3
10883
+ );
10267
10884
  authStatus = "authenticated";
10268
10885
  } catch {
10269
10886
  authStatus = "not_authenticated";
@@ -10340,11 +10957,304 @@ print(result.stdout[:5000])
10340
10957
  const testText = params.text || "Hello! This is Pulso voice test. Kokoro TTS is working correctly.";
10341
10958
  try {
10342
10959
  await speak(testText, { engine: "auto" });
10343
- return { success: true, data: { spoken: testText, engine: getTTSInfo().engine } };
10960
+ return {
10961
+ success: true,
10962
+ data: { spoken: testText, engine: getTTSInfo().engine }
10963
+ };
10344
10964
  } catch (err) {
10345
10965
  return { success: false, error: err.message };
10346
10966
  }
10347
10967
  }
10968
+ // ── IDE Integration ────────────────────────────────────
10969
+ // Helper: extract open workspace paths from a VS Code/Cursor/Windsurf storage.json
10970
+ // Storage format: windowsState.lastActiveWindow / openedWindows
10971
+ // Each window has workspaceIdentifier.configURIPath (workspace file) OR folderUri (folder)
10972
+ case "sys_ide_list_open": {
10973
+ let readIdeWorkspaces2 = function(storagePath) {
10974
+ const storage = JSON.parse(readFileSync4(storagePath, "utf-8"));
10975
+ const ws2 = storage["windowsState"] ?? {};
10976
+ const allWindows = [
10977
+ ws2["lastActiveWindow"],
10978
+ ...Array.isArray(ws2["openedWindows"]) ? ws2["openedWindows"] : []
10979
+ ].filter(Boolean);
10980
+ const seen = /* @__PURE__ */ new Set();
10981
+ const paths = [];
10982
+ for (const w of allWindows) {
10983
+ const configURI = w["workspaceIdentifier"]?.["configURIPath"] ?? "";
10984
+ const folderURI = w["folderUri"] ?? "";
10985
+ for (const uri of [configURI, folderURI]) {
10986
+ if (!uri) continue;
10987
+ let p = uri.replace(/^file:\/\//, "");
10988
+ if (p.endsWith(".code-workspace")) p = dirname(p);
10989
+ if (p && !seen.has(p)) {
10990
+ seen.add(p);
10991
+ paths.push(p);
10992
+ }
10993
+ }
10994
+ }
10995
+ const activePath = (() => {
10996
+ const aw = ws2["lastActiveWindow"];
10997
+ if (!aw) return null;
10998
+ const configURI = aw["workspaceIdentifier"]?.["configURIPath"] ?? "";
10999
+ const folderURI = aw["folderUri"] ?? "";
11000
+ const raw = configURI || folderURI;
11001
+ if (!raw) return null;
11002
+ let p = raw.replace(/^file:\/\//, "");
11003
+ if (p.endsWith(".code-workspace")) p = dirname(p);
11004
+ return p || null;
11005
+ })();
11006
+ return { active: activePath, all: paths };
11007
+ };
11008
+ var readIdeWorkspaces = readIdeWorkspaces2;
11009
+ return new Promise((resolve5) => {
11010
+ exec5("ps aux", { timeout: 5e3 }, (err, stdout) => {
11011
+ if (err) {
11012
+ resolve5({ success: false, error: err.message });
11013
+ return;
11014
+ }
11015
+ const IDE_PATTERNS = {
11016
+ "Cursor Helper": "Cursor",
11017
+ "Cursor.app": "Cursor",
11018
+ "Code Helper": "VS Code",
11019
+ "Visual Studio Code": "VS Code",
11020
+ "Windsurf Helper": "Windsurf",
11021
+ "Windsurf.app": "Windsurf",
11022
+ "zed": "Zed",
11023
+ "WebStorm": "WebStorm",
11024
+ "IntelliJ IDEA": "IntelliJ IDEA",
11025
+ "PyCharm": "PyCharm",
11026
+ "GoLand": "GoLand"
11027
+ };
11028
+ const running = /* @__PURE__ */ new Set();
11029
+ for (const line of stdout.split("\n")) {
11030
+ for (const [pattern, ideName] of Object.entries(IDE_PATTERNS)) {
11031
+ if (line.includes(pattern) && !line.includes("grep")) {
11032
+ running.add(ideName);
11033
+ }
11034
+ }
11035
+ }
11036
+ const home = homedir4();
11037
+ const storagePaths = [
11038
+ { ide: "VS Code", path: join5(home, "Library/Application Support/Code/User/globalStorage/storage.json") },
11039
+ { ide: "Cursor", path: join5(home, "Library/Application Support/Cursor/User/globalStorage/storage.json") },
11040
+ { ide: "Windsurf", path: join5(home, "Library/Application Support/Windsurf/User/globalStorage/storage.json") }
11041
+ ];
11042
+ const ides = [];
11043
+ for (const { ide: ideName, path: storagePath } of storagePaths) {
11044
+ if (!existsSync4(storagePath)) continue;
11045
+ try {
11046
+ const { active, all } = readIdeWorkspaces2(storagePath);
11047
+ ides.push({ ide: ideName, active, workspaces: all, running: running.has(ideName) });
11048
+ } catch {
11049
+ }
11050
+ }
11051
+ if (running.has("Zed") && !ides.find((i) => i.ide === "Zed")) {
11052
+ ides.push({ ide: "Zed", active: null, workspaces: [], running: true });
11053
+ }
11054
+ resolve5({
11055
+ success: true,
11056
+ data: {
11057
+ ides: ides.length > 0 ? ides : [],
11058
+ count: ides.length,
11059
+ note: ides.length === 0 ? "No IDEs detected." : void 0
11060
+ }
11061
+ });
11062
+ });
11063
+ });
11064
+ }
11065
+ case "sys_ide_get_context": {
11066
+ const targetIde = params.ide ?? "";
11067
+ const home = homedir4();
11068
+ const storageMap = {
11069
+ vscode: { label: "VS Code", path: join5(home, "Library/Application Support/Code/User/globalStorage/storage.json") },
11070
+ cursor: { label: "Cursor", path: join5(home, "Library/Application Support/Cursor/User/globalStorage/storage.json") },
11071
+ windsurf: { label: "Windsurf", path: join5(home, "Library/Application Support/Windsurf/User/globalStorage/storage.json") }
11072
+ };
11073
+ const ideKey = targetIde.toLowerCase().replace(/[\s-]/g, "");
11074
+ const pathsToTry = ideKey && storageMap[ideKey] ? [{ key: ideKey, ...storageMap[ideKey] }] : Object.entries(storageMap).map(([key, v]) => ({ key, ...v }));
11075
+ for (const { key: _key, label, path: storagePath } of pathsToTry) {
11076
+ if (!existsSync4(storagePath)) continue;
11077
+ try {
11078
+ const storage = JSON.parse(readFileSync4(storagePath, "utf-8"));
11079
+ const ws2 = storage["windowsState"] ?? {};
11080
+ const allWindows = [
11081
+ ws2["lastActiveWindow"],
11082
+ ...Array.isArray(ws2["openedWindows"]) ? ws2["openedWindows"] : []
11083
+ ].filter(Boolean);
11084
+ const seen = /* @__PURE__ */ new Set();
11085
+ const workspaces = [];
11086
+ for (const w of allWindows) {
11087
+ const configURI = w["workspaceIdentifier"]?.["configURIPath"] ?? "";
11088
+ const folderURI = w["folderUri"] ?? "";
11089
+ for (const uri of [configURI, folderURI]) {
11090
+ if (!uri) continue;
11091
+ let p = uri.replace(/^file:\/\//, "");
11092
+ if (p.endsWith(".code-workspace")) p = dirname(p);
11093
+ if (p && !seen.has(p)) {
11094
+ seen.add(p);
11095
+ workspaces.push(p);
11096
+ }
11097
+ }
11098
+ }
11099
+ const activeWindow = ws2["lastActiveWindow"];
11100
+ const activeConfigURI = activeWindow?.["workspaceIdentifier"]?.["configURIPath"] ?? "";
11101
+ const activeFolderURI = activeWindow?.["folderUri"] ?? "";
11102
+ let activeWorkspace = (activeConfigURI || activeFolderURI).replace(/^file:\/\//, "");
11103
+ if (activeWorkspace.endsWith(".code-workspace")) activeWorkspace = dirname(activeWorkspace);
11104
+ return {
11105
+ success: true,
11106
+ data: {
11107
+ ide: label,
11108
+ activeWorkspace: activeWorkspace || null,
11109
+ openWorkspaces: workspaces
11110
+ }
11111
+ };
11112
+ } catch {
11113
+ }
11114
+ }
11115
+ return {
11116
+ success: false,
11117
+ error: "No IDE context found. Make sure VS Code, Cursor, or Windsurf has been opened."
11118
+ };
11119
+ }
11120
+ case "sys_ide_run_terminal": {
11121
+ const command2 = params.command;
11122
+ if (!command2) return { success: false, error: "Missing command" };
11123
+ let cwd = params.workspace;
11124
+ if (!cwd) {
11125
+ const home = homedir4();
11126
+ for (const storagePath of [
11127
+ join5(home, "Library/Application Support/Cursor/User/globalStorage/storage.json"),
11128
+ join5(home, "Library/Application Support/Code/User/globalStorage/storage.json"),
11129
+ join5(home, "Library/Application Support/Windsurf/User/globalStorage/storage.json")
11130
+ ]) {
11131
+ if (!existsSync4(storagePath)) continue;
11132
+ try {
11133
+ const storage = JSON.parse(readFileSync4(storagePath, "utf-8"));
11134
+ const ws2 = storage["windowsState"] ?? {};
11135
+ const aw = ws2["lastActiveWindow"];
11136
+ if (!aw) continue;
11137
+ const configURI = aw["workspaceIdentifier"]?.["configURIPath"] ?? "";
11138
+ const folderURI = aw["folderUri"] ?? "";
11139
+ const raw = configURI || folderURI;
11140
+ if (!raw) continue;
11141
+ let p = raw.replace(/^file:\/\//, "");
11142
+ if (p.endsWith(".code-workspace")) p = dirname(p);
11143
+ if (p) {
11144
+ cwd = p;
11145
+ break;
11146
+ }
11147
+ } catch {
11148
+ }
11149
+ }
11150
+ }
11151
+ const timeout = 3e4;
11152
+ return new Promise((resolve5) => {
11153
+ exec5(command2, { cwd: cwd || homedir4(), timeout }, (err, stdout, stderr) => {
11154
+ if (err && !stdout) {
11155
+ resolve5({
11156
+ success: false,
11157
+ error: `Command failed: ${stderr || err.message}`.slice(0, 2e3)
11158
+ });
11159
+ } else {
11160
+ resolve5({
11161
+ success: true,
11162
+ data: {
11163
+ command: command2,
11164
+ cwd: cwd || homedir4(),
11165
+ output: (stdout + (stderr ? `
11166
+ STDERR: ${stderr}` : "")).slice(0, 1e4),
11167
+ truncated: (stdout + stderr).length > 1e4,
11168
+ exitCode: err?.code ?? 0
11169
+ }
11170
+ });
11171
+ }
11172
+ });
11173
+ });
11174
+ }
11175
+ case "sys_ide_read_terminal": {
11176
+ const lines = Number(params.lines) || 50;
11177
+ const home = homedir4();
11178
+ const historyPaths = [
11179
+ join5(home, ".zsh_history"),
11180
+ join5(home, ".bash_history"),
11181
+ join5(home, ".local/share/fish/fish_history")
11182
+ ];
11183
+ for (const histPath of historyPaths) {
11184
+ if (!existsSync4(histPath)) continue;
11185
+ try {
11186
+ const content = readFileSync4(histPath, "utf-8");
11187
+ const allLines = content.split("\n").filter(Boolean);
11188
+ const commands = allLines.map((l) => l.startsWith(": ") ? l.replace(/^:\s*\d+:\d+;/, "") : l).filter((l) => !l.startsWith("#")).slice(-lines);
11189
+ return {
11190
+ success: true,
11191
+ data: {
11192
+ source: histPath,
11193
+ lines: commands,
11194
+ count: commands.length
11195
+ }
11196
+ };
11197
+ } catch {
11198
+ }
11199
+ }
11200
+ return {
11201
+ success: false,
11202
+ error: "No shell history found. Make sure zsh, bash, or fish history is enabled."
11203
+ };
11204
+ }
11205
+ case "sys_ide_send_to_claude": {
11206
+ const prompt = params.prompt;
11207
+ if (!prompt) return { success: false, error: "Missing prompt" };
11208
+ let cwd = params.workspace;
11209
+ if (!cwd) {
11210
+ const home = homedir4();
11211
+ for (const storagePath of [
11212
+ join5(home, "Library/Application Support/Cursor/User/globalStorage/storage.json"),
11213
+ join5(home, "Library/Application Support/Code/User/globalStorage/storage.json"),
11214
+ join5(home, "Library/Application Support/Windsurf/User/globalStorage/storage.json")
11215
+ ]) {
11216
+ if (!existsSync4(storagePath)) continue;
11217
+ try {
11218
+ const storage = JSON.parse(readFileSync4(storagePath, "utf-8"));
11219
+ const ws2 = storage["windowsState"] ?? {};
11220
+ const aw = ws2["lastActiveWindow"];
11221
+ if (!aw) continue;
11222
+ const configURI = aw["workspaceIdentifier"]?.["configURIPath"] ?? "";
11223
+ const folderURI = aw["folderUri"] ?? "";
11224
+ const raw = configURI || folderURI;
11225
+ if (!raw) continue;
11226
+ let p = raw.replace(/^file:\/\//, "");
11227
+ if (p.endsWith(".code-workspace")) p = dirname(p);
11228
+ if (p) {
11229
+ cwd = p;
11230
+ break;
11231
+ }
11232
+ } catch {
11233
+ }
11234
+ }
11235
+ }
11236
+ const claudeCmd = `claude -p ${JSON.stringify(prompt)}`;
11237
+ return new Promise((resolve5) => {
11238
+ exec5(claudeCmd, { cwd: cwd || homedir4(), timeout: 12e4, env: { ...process.env, PATH: augmentedPath2() } }, (err, stdout, stderr) => {
11239
+ if (err && !stdout) {
11240
+ resolve5({
11241
+ success: false,
11242
+ error: err.message.includes("not found") || err.message.includes("ENOENT") ? "Claude Code CLI not found. Install it with: npm install -g @anthropic-ai/claude-code" : `Claude Code error: ${stderr || err.message}`.slice(0, 2e3)
11243
+ });
11244
+ } else {
11245
+ resolve5({
11246
+ success: true,
11247
+ data: {
11248
+ prompt,
11249
+ workspace: cwd || homedir4(),
11250
+ response: stdout.trim().slice(0, 1e4),
11251
+ truncated: stdout.length > 1e4
11252
+ }
11253
+ });
11254
+ }
11255
+ });
11256
+ });
11257
+ }
10348
11258
  default:
10349
11259
  return { success: false, error: `Unknown command: ${command}` };
10350
11260
  }
@@ -10375,8 +11285,12 @@ function startImessageMonitor() {
10375
11285
  lastImessageRowId = parseInt(initResult, 10) || 0;
10376
11286
  console.log(` \u2713 iMessage: monitoring from ROWID ${lastImessageRowId}`);
10377
11287
  } catch (err) {
10378
- console.log(` \u26A0 iMessage: failed to read chat.db \u2014 ${err.message}`);
10379
- console.log(" Grant Full Disk Access to Terminal/iTerm in System Settings \u2192 Privacy & Security");
11288
+ console.log(
11289
+ ` \u26A0 iMessage: failed to read chat.db \u2014 ${err.message}`
11290
+ );
11291
+ console.log(
11292
+ " Grant Full Disk Access to Terminal/iTerm in System Settings \u2192 Privacy & Security"
11293
+ );
10380
11294
  return;
10381
11295
  }
10382
11296
  imessageTimer = setInterval(async () => {
@@ -10410,16 +11324,20 @@ function startImessageMonitor() {
10410
11324
  if (rowId <= lastImessageRowId) continue;
10411
11325
  lastImessageRowId = rowId;
10412
11326
  if (!text || text.startsWith("\uFFFC")) continue;
10413
- console.log(`
10414
- \u{1F4AC} iMessage from ${senderName || senderId}: ${text.slice(0, 80)}`);
10415
- ws.send(JSON.stringify({
10416
- type: "imessage_incoming",
10417
- from: senderId || "unknown",
10418
- fromName: senderName || senderId || "Unknown",
10419
- chatId: chatId || senderId || "unknown",
10420
- text,
10421
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
10422
- }));
11327
+ console.log(
11328
+ `
11329
+ \u{1F4AC} iMessage from ${senderName || senderId}: ${text.slice(0, 80)}`
11330
+ );
11331
+ ws.send(
11332
+ JSON.stringify({
11333
+ type: "imessage_incoming",
11334
+ from: senderId || "unknown",
11335
+ fromName: senderName || senderId || "Unknown",
11336
+ chatId: chatId || senderId || "unknown",
11337
+ text,
11338
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11339
+ })
11340
+ );
10423
11341
  }
10424
11342
  } catch {
10425
11343
  }
@@ -10500,18 +11418,22 @@ end tell`;
10500
11418
  }
10501
11419
  } catch {
10502
11420
  }
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
- }));
11421
+ console.log(
11422
+ `
11423
+ \u{1F514} Permission dialog: [${procName}] "${title}" \u2014 buttons: ${buttons.join(", ")}`
11424
+ );
11425
+ ws.send(
11426
+ JSON.stringify({
11427
+ type: "permission_dialog",
11428
+ dialogId,
11429
+ procName,
11430
+ title: title || procName,
11431
+ message: desc,
11432
+ buttons,
11433
+ screenshot,
11434
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11435
+ })
11436
+ );
10515
11437
  } catch {
10516
11438
  }
10517
11439
  }, DIALOG_POLL_INTERVAL);
@@ -10541,13 +11463,22 @@ var CAPABILITY_PROBES = [
10541
11463
  name: "spotify",
10542
11464
  test: async () => {
10543
11465
  try {
10544
- await runShell4("pgrep -x Spotify >/dev/null 2>&1 || ls /Applications/Spotify.app >/dev/null 2>&1");
11466
+ await runShell4(
11467
+ "pgrep -x Spotify >/dev/null 2>&1 || ls /Applications/Spotify.app >/dev/null 2>&1"
11468
+ );
10545
11469
  return true;
10546
11470
  } catch {
10547
11471
  return false;
10548
11472
  }
10549
11473
  },
10550
- tools: ["sys_spotify_play", "sys_spotify_pause", "sys_spotify_current", "sys_spotify_next", "sys_spotify_previous", "sys_spotify_search"]
11474
+ tools: [
11475
+ "sys_spotify_play",
11476
+ "sys_spotify_pause",
11477
+ "sys_spotify_current",
11478
+ "sys_spotify_next",
11479
+ "sys_spotify_previous",
11480
+ "sys_spotify_search"
11481
+ ]
10551
11482
  },
10552
11483
  {
10553
11484
  name: "tts",
@@ -10577,7 +11508,10 @@ var CAPABILITY_PROBES = [
10577
11508
  name: "claude_cli",
10578
11509
  test: async () => {
10579
11510
  try {
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);
11511
+ await runShell4(
11512
+ '(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)',
11513
+ 5e3
11514
+ );
10581
11515
  return true;
10582
11516
  } catch {
10583
11517
  return false;
@@ -10589,7 +11523,10 @@ var CAPABILITY_PROBES = [
10589
11523
  name: "codex_cli",
10590
11524
  test: async () => {
10591
11525
  try {
10592
- await runShell4("which codex >/dev/null 2>&1 && codex --version >/dev/null 2>&1", 5e3);
11526
+ await runShell4(
11527
+ "which codex >/dev/null 2>&1 && codex --version >/dev/null 2>&1",
11528
+ 5e3
11529
+ );
10593
11530
  return true;
10594
11531
  } catch {
10595
11532
  return false;
@@ -10657,8 +11594,11 @@ async function probeCapabilities() {
10657
11594
  }
10658
11595
  }
10659
11596
  const cap = { available, unavailable, tools: Array.from(tools) };
10660
- console.log(` \u2705 Available: ${available.join(", ") || "all adapter tools"}`);
10661
- if (unavailable.length) console.log(` \u26A0\uFE0F Unavailable: ${unavailable.join(", ")}`);
11597
+ console.log(
11598
+ ` \u2705 Available: ${available.join(", ") || "all adapter tools"}`
11599
+ );
11600
+ if (unavailable.length)
11601
+ console.log(` \u26A0\uFE0F Unavailable: ${unavailable.join(", ")}`);
10662
11602
  console.log(` \u{1F4E6} ${cap.tools.length} tools verified`);
10663
11603
  return cap;
10664
11604
  }
@@ -10706,18 +11646,20 @@ async function connect() {
10706
11646
  console.log(" Waiting for commands from Pulso agent...");
10707
11647
  probeCapabilities().then((cap) => {
10708
11648
  verifiedCapabilities = cap;
10709
- ws.send(JSON.stringify({
10710
- type: "extension_ready",
10711
- platform: adapter.platform,
10712
- version: "0.4.3",
10713
- accessLevel: ACCESS_LEVEL3,
10714
- homeDir: HOME4,
10715
- hostname: hostname3(),
10716
- capabilities: cap.available,
10717
- unavailable: cap.unavailable,
10718
- tools: cap.tools,
10719
- totalTools: cap.tools.length
10720
- }));
11649
+ ws.send(
11650
+ JSON.stringify({
11651
+ type: "extension_ready",
11652
+ platform: adapter.platform,
11653
+ version: "0.4.3",
11654
+ accessLevel: ACCESS_LEVEL3,
11655
+ homeDir: HOME4,
11656
+ hostname: hostname3(),
11657
+ capabilities: cap.available,
11658
+ unavailable: cap.unavailable,
11659
+ tools: cap.tools,
11660
+ totalTools: cap.tools.length
11661
+ })
11662
+ );
10721
11663
  });
10722
11664
  if (heartbeatTimer) clearInterval(heartbeatTimer);
10723
11665
  heartbeatTimer = setInterval(() => {
@@ -10752,7 +11694,11 @@ async function connect() {
10752
11694
  } catch {
10753
11695
  }
10754
11696
  };
10755
- const result = await handleCommand(msg.command, msg.params ?? {}, streamCb);
11697
+ const result = await handleCommand(
11698
+ msg.command,
11699
+ msg.params ?? {},
11700
+ streamCb
11701
+ );
10756
11702
  console.log(
10757
11703
  ` \u2192 ${result.success ? "\u2705" : "\u274C"}`,
10758
11704
  result.success ? JSON.stringify(result.data).slice(0, 200) : result.error
@@ -10884,7 +11830,10 @@ function discoverWakeWordKeywordPath() {
10884
11830
  const explicit = WAKE_WORD_KEYWORD_PATH.trim();
10885
11831
  if (explicit) {
10886
11832
  if (existsSync4(explicit)) {
10887
- return { path: explicit, source: "PULSO_WAKE_WORD_PATH / --wake-word-path" };
11833
+ return {
11834
+ path: explicit,
11835
+ source: "PULSO_WAKE_WORD_PATH / --wake-word-path"
11836
+ };
10888
11837
  }
10889
11838
  console.log(
10890
11839
  ` \u26A0\uFE0F Wake word file not found at configured path: ${explicit}`
@@ -10943,10 +11892,15 @@ function discoverWakeWordKeywordPath() {
10943
11892
  function discoverWakeWordLanguageModelPath(keywordPath, fallbackModelPath) {
10944
11893
  const explicit = WAKE_WORD_MODEL_PATH.trim();
10945
11894
  if (explicit && existsSync4(explicit)) {
10946
- return { path: explicit, source: "PULSO_WAKE_WORD_MODEL_PATH / --wake-word-model-path" };
11895
+ return {
11896
+ path: explicit,
11897
+ source: "PULSO_WAKE_WORD_MODEL_PATH / --wake-word-model-path"
11898
+ };
10947
11899
  }
10948
11900
  if (explicit && !existsSync4(explicit)) {
10949
- console.log(` \u26A0\uFE0F Language model file not found at configured path: ${explicit}`);
11901
+ console.log(
11902
+ ` \u26A0\uFE0F Language model file not found at configured path: ${explicit}`
11903
+ );
10950
11904
  }
10951
11905
  const direct = join5(HOME4, ".pulso-wake-word-model.pv");
10952
11906
  if (existsSync4(direct)) {
@@ -11001,7 +11955,10 @@ function buildWakeWordDeviceCandidates(devices, explicitDeviceIndex) {
11001
11955
  }
11002
11956
  function startWakeWordRecorder(PvRecorder, frameLength, devices) {
11003
11957
  const explicitDeviceIndex = parseWakeWordDeviceIndex(WAKE_WORD_DEVICE_INDEX);
11004
- const candidates = buildWakeWordDeviceCandidates(devices, explicitDeviceIndex);
11958
+ const candidates = buildWakeWordDeviceCandidates(
11959
+ devices,
11960
+ explicitDeviceIndex
11961
+ );
11005
11962
  const errors = [];
11006
11963
  for (const candidate of candidates) {
11007
11964
  let recorder = null;
@@ -11024,7 +11981,9 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`
11024
11981
  }
11025
11982
  function loadWakeRecorderEngine() {
11026
11983
  if (process.platform !== "darwin") {
11027
- throw new Error("Wake word runtime assets are currently packaged for macOS only.");
11984
+ throw new Error(
11985
+ "Wake word runtime assets are currently packaged for macOS only."
11986
+ );
11028
11987
  }
11029
11988
  const archDir = process.arch === "arm64" ? "arm64" : "x86_64";
11030
11989
  const recorderLibraryPath = resolvePicovoiceAsset(
@@ -11045,7 +12004,9 @@ function loadWakeRecorderEngine() {
11045
12004
  }
11046
12005
  function loadWakeWordEngines() {
11047
12006
  if (process.platform !== "darwin") {
11048
- throw new Error("Wake word runtime assets are currently packaged for macOS only.");
12007
+ throw new Error(
12008
+ "Wake word runtime assets are currently packaged for macOS only."
12009
+ );
11049
12010
  }
11050
12011
  const archDir = process.arch === "arm64" ? "arm64" : "x86_64";
11051
12012
  const porcupineModelPath = resolvePicovoiceAsset(
@@ -11100,21 +12061,16 @@ async function startPicovoiceWakeWordDetection() {
11100
12061
  return;
11101
12062
  }
11102
12063
  try {
11103
- const {
11104
- Porcupine,
11105
- PvRecorder,
11106
- porcupineModelPath,
11107
- porcupineLibraryPath
11108
- } = loadWakeWordEngines();
12064
+ const { Porcupine, PvRecorder, porcupineModelPath, porcupineLibraryPath } = loadWakeWordEngines();
11109
12065
  const keyword = discoverWakeWordKeywordPath();
11110
12066
  if (!keyword) {
12067
+ console.log(" \u26A0\uFE0F Wake word model not found at ~/.pulso-wake-word.ppn");
11111
12068
  console.log(
11112
- " \u26A0\uFE0F Wake word model not found at ~/.pulso-wake-word.ppn"
12069
+ ' "Hey Pulso" requires a Picovoice keyword model file (.ppn).'
11113
12070
  );
11114
12071
  console.log(
11115
- ' "Hey Pulso" requires a Picovoice keyword model file (.ppn).'
12072
+ " Create it at https://console.picovoice.ai/ and save it to:"
11116
12073
  );
11117
- console.log(" Create it at https://console.picovoice.ai/ and save it to:");
11118
12074
  console.log(" ~/.pulso-wake-word.ppn");
11119
12075
  console.log(" (or set PULSO_WAKE_WORD_PATH / --wake-word-path)\n");
11120
12076
  return;
@@ -11267,12 +12223,16 @@ async function maybeGetWakeLocalTranscript(chunks, totalSamples, sampleRate, bud
11267
12223
  const timedOut = /* @__PURE__ */ Symbol("wake-stt-timeout");
11268
12224
  const sttResult = await Promise.race([
11269
12225
  transcribe(merged, sampleRate, { model: "tiny.en", language: "auto" }),
11270
- new Promise((resolve5) => setTimeout(() => resolve5(timedOut), budgetMs))
12226
+ new Promise(
12227
+ (resolve5) => setTimeout(() => resolve5(timedOut), budgetMs)
12228
+ )
11271
12229
  ]);
11272
12230
  if (sttResult === timedOut || !sttResult?.text) return void 0;
11273
12231
  const transcript = sttResult.text.trim();
11274
12232
  if (!transcript) return void 0;
11275
- console.log(` \u{1F9E0} Local STT (${sttResult.durationMs ?? budgetMs}ms): "${transcript}"`);
12233
+ console.log(
12234
+ ` \u{1F9E0} Local STT (${sttResult.durationMs ?? budgetMs}ms): "${transcript}"`
12235
+ );
11276
12236
  return transcript;
11277
12237
  } catch {
11278
12238
  return void 0;
@@ -11307,7 +12267,9 @@ async function startSemanticWakeWordDetection() {
11307
12267
  devices
11308
12268
  );
11309
12269
  const selectedDeviceName = selectedDeviceIndex >= 0 ? devices[selectedDeviceIndex] || `device ${selectedDeviceIndex}` : "OS default device";
11310
- const calibrationFrames = Math.ceil(wakeCalibrationMs / 1e3 * sampleRate / frameLength);
12270
+ const calibrationFrames = Math.ceil(
12271
+ wakeCalibrationMs / 1e3 * sampleRate / frameLength
12272
+ );
11311
12273
  let noiseFloor = 0;
11312
12274
  if (calibrationFrames > 0) {
11313
12275
  const samples = [];
@@ -11337,9 +12299,14 @@ async function startSemanticWakeWordDetection() {
11337
12299
  " \u{1F9E0} Trigger phrase: say 'Hey Pulso' (or 'Ok Pulso', 'Ola Pulso', 'Pulso')\n"
11338
12300
  );
11339
12301
  const minSpeechFrames = Math.ceil(0.22 * sampleRate / frameLength);
11340
- const maxSilenceFrames = Math.ceil(wakeEndSilenceMs / 1e3 * sampleRate / frameLength);
12302
+ const maxSilenceFrames = Math.ceil(
12303
+ wakeEndSilenceMs / 1e3 * sampleRate / frameLength
12304
+ );
11341
12305
  const maxRecordFrames = Math.ceil(10 * sampleRate / frameLength);
11342
- const preRollFrames = Math.max(1, Math.ceil(0.35 * sampleRate / frameLength));
12306
+ const preRollFrames = Math.max(
12307
+ 1,
12308
+ Math.ceil(0.35 * sampleRate / frameLength)
12309
+ );
11343
12310
  const sendCooldownMs = 700;
11344
12311
  let speaking = false;
11345
12312
  let framesCaptured = 0;
@@ -11397,7 +12364,9 @@ async function startSemanticWakeWordDetection() {
11397
12364
  ...localTranscript ? { localTranscript } : {}
11398
12365
  })
11399
12366
  );
11400
- console.log(` \u{1F4E4} Semantic wake probe sent (${(durationMs / 1e3).toFixed(1)}s)`);
12367
+ console.log(
12368
+ ` \u{1F4E4} Semantic wake probe sent (${(durationMs / 1e3).toFixed(1)}s)`
12369
+ );
11401
12370
  exec5("afplay /System/Library/Sounds/Pop.aiff");
11402
12371
  cooldownUntil = Date.now() + sendCooldownMs;
11403
12372
  }
@@ -11482,7 +12451,7 @@ process.on("exit", () => {
11482
12451
  });
11483
12452
  console.log("");
11484
12453
  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");
11485
- console.log(` \u2551 Pulso ${platformName} Companion v0.4.3 \u2551`);
12454
+ console.log(` \u2551 Pulso ${platformName} Companion v0.4.5 \u2551`);
11486
12455
  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");
11487
12456
  console.log("");
11488
12457
  console.log(` Platform: ${currentPlatform}`);