File size: 3,791 Bytes
fce80d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Browser control — open URLs and run web searches in the default browser.
// Multi-OS: macOS (open + osascript), Linux (xdg-open), Windows (start).

import { exec } from 'node:child_process';
import { promisify } from 'node:util';
import os from 'node:os';

const pexec = promisify(exec);
const PLATFORM = os.platform();

/** Build a search URL for the requested site. Exported for tests. */
export function searchUrl(query, site = 'web') {
  const q = encodeURIComponent(String(query || '').slice(0, 300));
  switch (String(site).toLowerCase()) {
    case 'youtube':
      return `https://www.youtube.com/results?search_query=${q}`;
    case 'google':
      return `https://www.google.com/search?q=${q}`;
    case 'web':
    default:
      return `https://www.bing.com/search?q=${q}`;
  }
}

/** Validate a URL for the open action. Exported for tests. */
export function validateUrl(url) {
  try {
    const u = new URL(String(url));
    if (!['http:', 'https:'].includes(u.protocol)) return null;
    return u.toString();
  } catch {
    return null;
  }
}

async function openWith(cmd, timeoutMs = 8000) {
  try {
    await pexec(cmd, { timeout: timeoutMs, maxBuffer: 1 << 20 });
    return { exitCode: 0, opened: true };
  } catch (err) {
    return { exitCode: err.code ?? 1, error: err.message, opened: false };
  }
}

export const browser = {
  name: 'browser',
  description: `Open URLs or run web searches (YouTube, Google, web) in the default browser (${PLATFORM}).`,
  args: {
    action: 'string — open | search | watch',
    url: 'string — full URL (for open)',
    query: 'string — search terms (for search/watch)',
    site: 'string — youtube | google | web (default web)',
  },
  platform: PLATFORM,

  async run(args) {
    const action = String(args.action || 'open').toLowerCase();

    let url = null;
    if (action === 'open') {
      url = validateUrl(args.url);
      if (!url) return { error: 'A valid http(s) URL is required (url=...)' };
    } else if (action === 'search' || action === 'watch') {
      const site = args.site || (action === 'watch' ? 'youtube' : 'web');
      url = searchUrl(args.query || '', site);
      if (!String(args.query || '').trim()) return { error: 'query required' };
    } else {
      return { error: `Unknown action: ${action} (use open | search | watch)` };
    }

    if (PLATFORM === 'darwin') {
      const res = await openWith(`open "${url}"`);
      if (action === 'watch') {
        // Best effort: ask Safari to play the first video result. Requires
        // "Allow JavaScript from Apple Events" in Safari — falls back to the
        // results page if the setting is off.
        try {
          await pexec(
            `osascript -e 'tell application "Safari" to activate' -e 'delay 2' -e 'tell application "Safari" to do JavaScript "document.querySelector(\\"ytd-video-renderer a#video-title\\")?.click()" in front document'`,
            { timeout: 12000 }
          );
          res.autoplayAttempted = true;
        } catch {
          res.autoplayAttempted = false;
          res.note = 'Opened the results page. Click the first result to play (Safari automation requires "Allow JavaScript from Apple Events").';
        }
      }
      return { ...res, url };
    }
    if (PLATFORM === 'linux') {
      const res = await openWith(`xdg-open "${url}"`);
      return { ...res, url, note: action === 'watch' ? 'Opened the search results page — click the first result to play.' : undefined };
    }
    if (PLATFORM === 'win32') {
      const res = await openWith(`start "" "${url}"`);
      return { ...res, url, note: action === 'watch' ? 'Opened the search results page — click the first result to play.' : undefined };
    }
    return { error: `Unsupported platform: ${PLATFORM}` };
  },
};