@pulso/companion 0.1.7 → 0.1.8

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 +215 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -541,6 +541,218 @@ print("\\(x),\\(y)")`;
541
541
  const [cx, cy] = pos.trim().split(",").map(Number);
542
542
  return { success: true, data: { x: cx, y: cy } };
543
543
  }
544
+ // ── Browser Automation ─────────────────────────────────
545
+ case "sys_browser_list_tabs": {
546
+ const browsers = ["Google Chrome", "Safari", "Arc", "Firefox", "Microsoft Edge"];
547
+ const allTabs = [];
548
+ for (const browser of browsers) {
549
+ try {
550
+ const running = await runAppleScript(
551
+ `tell application "System Events" to (name of processes) contains "${browser}"`
552
+ );
553
+ if (running.trim() !== "true") continue;
554
+ if (browser === "Safari") {
555
+ const tabData = await runAppleScript(`
556
+ tell application "Safari"
557
+ set tabList to ""
558
+ set activeURL to URL of front document
559
+ repeat with w in windows
560
+ repeat with t in tabs of w
561
+ set tabList to tabList & name of t & "|||" & URL of t & "|||"
562
+ end repeat
563
+ end repeat
564
+ return activeURL & "~~~" & tabList
565
+ end tell`);
566
+ const [activeURL, ...rest] = tabData.split("~~~");
567
+ const tabStr = rest.join("~~~");
568
+ const pairs = tabStr.split("|||").filter(Boolean);
569
+ for (let i = 0; i < pairs.length - 1; i += 2) {
570
+ allTabs.push({ browser: "Safari", title: pairs[i], url: pairs[i + 1], active: pairs[i + 1] === activeURL.trim() });
571
+ }
572
+ } else {
573
+ const tabData = await runAppleScript(`
574
+ tell application "${browser}"
575
+ set tabList to ""
576
+ set activeTabURL to URL of active tab of front window
577
+ repeat with w in windows
578
+ repeat with t in tabs of w
579
+ set tabList to tabList & title of t & "|||" & URL of t & "|||"
580
+ end repeat
581
+ end repeat
582
+ return activeTabURL & "~~~" & tabList
583
+ end tell`);
584
+ const [activeURL, ...rest] = tabData.split("~~~");
585
+ const tabStr = rest.join("~~~");
586
+ const pairs = tabStr.split("|||").filter(Boolean);
587
+ for (let i = 0; i < pairs.length - 1; i += 2) {
588
+ allTabs.push({ browser, title: pairs[i], url: pairs[i + 1], active: pairs[i + 1] === activeURL.trim() });
589
+ }
590
+ }
591
+ } catch {
592
+ }
593
+ }
594
+ return { success: true, data: { tabs: allTabs, count: allTabs.length } };
595
+ }
596
+ case "sys_browser_navigate": {
597
+ const url = params.url;
598
+ const browser = params.browser || "Google Chrome";
599
+ if (!url) return { success: false, error: "Missing URL" };
600
+ try {
601
+ if (browser === "Safari") {
602
+ await runAppleScript(`
603
+ tell application "Safari"
604
+ activate
605
+ if (count of windows) = 0 then make new document
606
+ set URL of front document to "${url.replace(/"/g, '\\"')}"
607
+ end tell`);
608
+ } else {
609
+ await runAppleScript(`
610
+ tell application "${browser.replace(/"/g, '\\"')}"
611
+ activate
612
+ if (count of windows) = 0 then
613
+ make new window
614
+ set URL of active tab of front window to "${url.replace(/"/g, '\\"')}"
615
+ else
616
+ set URL of active tab of front window to "${url.replace(/"/g, '\\"')}"
617
+ end if
618
+ end tell`);
619
+ }
620
+ return { success: true, data: { navigated: url, browser } };
621
+ } catch (err) {
622
+ return { success: false, error: `Failed to navigate: ${err.message}` };
623
+ }
624
+ }
625
+ case "sys_browser_new_tab": {
626
+ const url = params.url;
627
+ const browser = params.browser || "Google Chrome";
628
+ if (!url) return { success: false, error: "Missing URL" };
629
+ try {
630
+ if (browser === "Safari") {
631
+ await runAppleScript(`
632
+ tell application "Safari"
633
+ activate
634
+ tell front window to set current tab to (make new tab with properties {URL:"${url.replace(/"/g, '\\"')}"})
635
+ end tell`);
636
+ } else {
637
+ await runAppleScript(`
638
+ tell application "${browser.replace(/"/g, '\\"')}"
639
+ activate
640
+ tell front window to make new tab with properties {URL:"${url.replace(/"/g, '\\"')}"}
641
+ end tell`);
642
+ }
643
+ return { success: true, data: { opened: url, browser } };
644
+ } catch (err) {
645
+ return { success: false, error: `Failed to open tab: ${err.message}` };
646
+ }
647
+ }
648
+ case "sys_browser_read_page": {
649
+ const browser = params.browser || "Google Chrome";
650
+ const maxLen = Number(params.maxLength) || 8e3;
651
+ let content = "";
652
+ let method = "";
653
+ try {
654
+ if (browser === "Safari") {
655
+ content = await runAppleScript(`
656
+ tell application "Safari"
657
+ set pageText to do JavaScript "document.body.innerText" in front document
658
+ return pageText
659
+ end tell`);
660
+ method = "javascript";
661
+ } else {
662
+ content = await runAppleScript(`
663
+ tell application "${browser.replace(/"/g, '\\"')}"
664
+ set pageText to execute javascript "document.body.innerText" in active tab of front window
665
+ return pageText
666
+ end tell`);
667
+ method = "javascript";
668
+ }
669
+ } catch {
670
+ try {
671
+ const savedClipboard = await runShell("pbpaste 2>/dev/null || true");
672
+ await runAppleScript(`tell application "${browser.replace(/"/g, '\\"')}" to activate`);
673
+ await new Promise((r) => setTimeout(r, 300));
674
+ await runAppleScript('tell application "System Events" to keystroke "a" using command down');
675
+ await new Promise((r) => setTimeout(r, 200));
676
+ await runAppleScript('tell application "System Events" to keystroke "c" using command down');
677
+ await new Promise((r) => setTimeout(r, 300));
678
+ content = await runShell("pbpaste");
679
+ method = "clipboard";
680
+ await runAppleScript('tell application "System Events" to key code 53');
681
+ if (savedClipboard && savedClipboard !== content) {
682
+ execSync(`echo ${JSON.stringify(savedClipboard)} | pbcopy`);
683
+ }
684
+ } catch (clipErr) {
685
+ return { success: false, error: `Could not read page. Enable 'Allow JavaScript from Apple Events' in ${browser} or grant Accessibility permission. Error: ${clipErr.message}` };
686
+ }
687
+ }
688
+ let pageUrl = "";
689
+ let pageTitle = "";
690
+ try {
691
+ if (browser === "Safari") {
692
+ pageUrl = await runAppleScript('tell application "Safari" to return URL of front document');
693
+ pageTitle = await runAppleScript('tell application "Safari" to return name of front document');
694
+ } else {
695
+ pageUrl = await runAppleScript(`tell application "${browser.replace(/"/g, '\\"')}" to return URL of active tab of front window`);
696
+ pageTitle = await runAppleScript(`tell application "${browser.replace(/"/g, '\\"')}" to return title of active tab of front window`);
697
+ }
698
+ } catch {
699
+ }
700
+ return {
701
+ success: true,
702
+ data: {
703
+ title: pageTitle.trim(),
704
+ url: pageUrl.trim(),
705
+ content: content.slice(0, maxLen),
706
+ length: content.length,
707
+ truncated: content.length > maxLen,
708
+ method
709
+ }
710
+ };
711
+ }
712
+ case "sys_browser_execute_js": {
713
+ const js = params.code;
714
+ const browser = params.browser || "Google Chrome";
715
+ if (!js) return { success: false, error: "Missing JavaScript code" };
716
+ try {
717
+ let result;
718
+ if (browser === "Safari") {
719
+ result = await runAppleScript(`tell application "Safari" to do JavaScript ${JSON.stringify(js)} in front document`);
720
+ } else {
721
+ result = await runAppleScript(`tell application "${browser.replace(/"/g, '\\"')}" to execute javascript ${JSON.stringify(js)} in active tab of front window`);
722
+ }
723
+ return { success: true, data: { result: result.slice(0, 5e3) } };
724
+ } catch (err) {
725
+ return { success: false, error: `JS execution failed (enable 'Allow JavaScript from Apple Events' in browser): ${err.message}` };
726
+ }
727
+ }
728
+ // ── Email ──────────────────────────────────────────────
729
+ case "sys_email_send": {
730
+ const to = params.to;
731
+ const subject = params.subject;
732
+ const body = params.body;
733
+ const method = params.method || "mail";
734
+ if (!to || !subject || !body) return { success: false, error: "Missing to, subject, or body" };
735
+ if (method === "gmail") {
736
+ const gmailUrl = `https://mail.google.com/mail/u/0/?view=cm&fs=1&to=${encodeURIComponent(to)}&su=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
737
+ await runShell(`open "${gmailUrl}"`);
738
+ return { success: true, data: { method: "gmail", to, subject, note: "Gmail compose opened. User needs to click Send." } };
739
+ }
740
+ try {
741
+ await runAppleScript(`
742
+ tell application "Mail"
743
+ set newMessage to make new outgoing message with properties {subject:"${subject.replace(/"/g, '\\"')}", content:"${body.replace(/"/g, '\\"')}"}
744
+ tell newMessage
745
+ make new to recipient at end of to recipients with properties {address:"${to.replace(/"/g, '\\"')}"}
746
+ end tell
747
+ send newMessage
748
+ end tell`);
749
+ return { success: true, data: { method: "mail", to, subject, sent: true } };
750
+ } catch (err) {
751
+ const gmailUrl = `https://mail.google.com/mail/u/0/?view=cm&fs=1&to=${encodeURIComponent(to)}&su=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
752
+ await runShell(`open "${gmailUrl}"`);
753
+ return { success: true, data: { method: "gmail_fallback", to, subject, note: `Mail.app failed (${err.message}). Gmail compose opened instead.` } };
754
+ }
755
+ }
544
756
  case "sys_run_shortcut": {
545
757
  const name = params.name;
546
758
  const input = params.input;
@@ -575,6 +787,8 @@ function connect() {
575
787
  console.log(" \u2022 Clipboard access");
576
788
  console.log(" \u2022 Screenshots");
577
789
  console.log(" \u2022 Text-to-speech");
790
+ console.log(" \u2022 Browser automation (tabs, navigate, read pages)");
791
+ console.log(" \u2022 Email composition (Mail.app / Gmail)");
578
792
  console.log(" \u2022 Terminal commands");
579
793
  console.log(" \u2022 System notifications");
580
794
  console.log(" \u2022 macOS Shortcuts");
@@ -635,7 +849,7 @@ function scheduleReconnect() {
635
849
  }
636
850
  console.log("");
637
851
  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");
638
- console.log(" \u2551 \u{1FAC0} Pulso Mac Companion v0.1.5 \u2551");
852
+ console.log(" \u2551 \u{1FAC0} Pulso Mac Companion v0.1.8 \u2551");
639
853
  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");
640
854
  console.log("");
641
855
  connect();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pulso/companion",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "description": "Pulso Companion — gives your AI agent real control over your computer",
6
6
  "bin": {