@phnx-labs/agents-cli 1.14.4 → 1.14.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.14.5
4
+
5
+ **Browser: custom binary and Electron app support**
6
+
7
+ - Added `binary` field to browser profiles for specifying custom executable paths (e.g., Electron apps like Rush)
8
+ - Added `electron` field to browser profiles — when true, uses existing windows instead of creating new ones (Electron doesn't support `Target.createTarget`)
9
+ - New `custom` browser type that requires a binary path
10
+ - Works with both local and SSH-based browser connections
11
+ - Example profile for Rush: `agents browser profiles edit rush --browser custom --binary "/Applications/Rush.app/Contents/MacOS/Rush" --electron`
12
+
3
13
  ## Unreleased
4
14
 
5
15
  **System repo moved to `~/.agents-system`; `~/.agents` is now free for user-owned repos**
@@ -1,12 +1,12 @@
1
1
  import type { ChromeOptions } from './types.js';
2
2
  import type { BrowserType } from './types.js';
3
- export declare function findBrowserPath(browserType: BrowserType): string;
3
+ export declare function findBrowserPath(browserType: BrowserType, customBinary?: string): string;
4
4
  export interface LaunchResult {
5
5
  pid: number;
6
6
  port: number;
7
7
  wsUrl: string;
8
8
  }
9
- export declare function launchBrowser(profileName: string, browserType: BrowserType, port: number, options?: ChromeOptions, secrets?: string): Promise<LaunchResult>;
9
+ export declare function launchBrowser(profileName: string, browserType: BrowserType, port: number, options?: ChromeOptions, secrets?: string, customBinary?: string): Promise<LaunchResult>;
10
10
  export declare function attachToChrome(port: number): Promise<string>;
11
11
  export declare function killChrome(pid: number): void;
12
12
  export declare function getRunningChromeInfo(profileName: string): {
@@ -15,6 +15,7 @@ const BROWSER_PATHS = {
15
15
  chromium: ['/Applications/Chromium.app/Contents/MacOS/Chromium'],
16
16
  brave: ['/Applications/Brave Browser.app/Contents/MacOS/Brave Browser'],
17
17
  edge: ['/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'],
18
+ custom: [],
18
19
  },
19
20
  linux: {
20
21
  chrome: ['/usr/bin/google-chrome', '/usr/bin/google-chrome-stable'],
@@ -22,6 +23,7 @@ const BROWSER_PATHS = {
22
23
  chromium: ['/usr/bin/chromium', '/usr/bin/chromium-browser', '/snap/bin/chromium'],
23
24
  brave: ['/usr/bin/brave-browser', '/usr/bin/brave'],
24
25
  edge: ['/usr/bin/microsoft-edge'],
26
+ custom: [],
25
27
  },
26
28
  win32: {
27
29
  chrome: [
@@ -36,9 +38,19 @@ const BROWSER_PATHS = {
36
38
  edge: [
37
39
  'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
38
40
  ],
41
+ custom: [],
39
42
  },
40
43
  };
41
- export function findBrowserPath(browserType) {
44
+ export function findBrowserPath(browserType, customBinary) {
45
+ if (customBinary) {
46
+ if (!fs.existsSync(customBinary)) {
47
+ throw new Error(`Custom binary not found: ${customBinary}`);
48
+ }
49
+ return customBinary;
50
+ }
51
+ if (browserType === 'custom') {
52
+ throw new Error('browser: custom requires a binary path in the profile');
53
+ }
42
54
  const platform = os.platform();
43
55
  const platformPaths = BROWSER_PATHS[platform];
44
56
  if (!platformPaths) {
@@ -52,8 +64,8 @@ export function findBrowserPath(browserType) {
52
64
  }
53
65
  throw new Error(`Browser "${browserType}" not found. Install it first.`);
54
66
  }
55
- export async function launchBrowser(profileName, browserType, port, options = {}, secrets) {
56
- const browserPath = findBrowserPath(browserType);
67
+ export async function launchBrowser(profileName, browserType, port, options = {}, secrets, customBinary) {
68
+ const browserPath = findBrowserPath(browserType, customBinary);
57
69
  const runtimeDir = getProfileRuntimeDir(profileName);
58
70
  const userDataDir = path.join(runtimeDir, 'chrome-data');
59
71
  fs.mkdirSync(userDataDir, { recursive: true });
@@ -14,7 +14,7 @@ export async function connectLocal(endpoint, profile) {
14
14
  }
15
15
  catch {
16
16
  const newPort = allocatePort();
17
- const { pid, wsUrl } = await launchBrowser(profile.name, profile.browser, newPort, profile.chrome, profile.secrets);
17
+ const { pid, wsUrl } = await launchBrowser(profile.name, profile.browser, newPort, profile.chrome, profile.secrets, profile.binary);
18
18
  const cdp = new CDPClient();
19
19
  await cdp.connect(wsUrl);
20
20
  return { cdp, port: newPort, pid };
@@ -12,7 +12,7 @@ export async function connectSSH(endpoint, profile) {
12
12
  const remotePort = parseInt(url.searchParams.get('port') || '9222', 10);
13
13
  const localPort = allocatePort();
14
14
  try {
15
- await ensureRemoteBrowser(user, host, profile.browser, remotePort);
15
+ await ensureRemoteBrowser(user, host, profile.browser, remotePort, profile.binary);
16
16
  }
17
17
  catch {
18
18
  // Browser may already be running, continue
@@ -96,7 +96,7 @@ function tryConnect(port) {
96
96
  socket.on('error', reject);
97
97
  });
98
98
  }
99
- async function ensureRemoteBrowser(user, host, browserType, port) {
99
+ async function ensureRemoteBrowser(user, host, browserType, port, customBinary) {
100
100
  const browserPaths = {
101
101
  chrome: '/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome',
102
102
  comet: '/Applications/Comet.app/Contents/MacOS/Comet',
@@ -104,9 +104,18 @@ async function ensureRemoteBrowser(user, host, browserType, port) {
104
104
  brave: '/Applications/Brave\\ Browser.app/Contents/MacOS/Brave\\ Browser',
105
105
  edge: '/Applications/Microsoft\\ Edge.app/Contents/MacOS/Microsoft\\ Edge',
106
106
  };
107
- const browserPath = browserPaths[browserType];
108
- if (!browserPath) {
109
- throw new Error(`Unknown browser type: ${browserType}`);
107
+ let browserPath;
108
+ if (customBinary) {
109
+ browserPath = customBinary.replace(/ /g, '\\ ');
110
+ }
111
+ else if (browserType === 'custom') {
112
+ throw new Error('browser: custom requires a binary path in the profile');
113
+ }
114
+ else {
115
+ browserPath = browserPaths[browserType];
116
+ if (!browserPath) {
117
+ throw new Error(`Unknown browser type: ${browserType}`);
118
+ }
110
119
  }
111
120
  const remoteCmd = `${browserPath} --remote-debugging-port=${port} '--remote-allow-origins=*' --disable-background-timer-throttling --user-data-dir=/tmp/agents-browser-${port} </dev/null >/dev/null 2>&1 &`;
112
121
  return new Promise((resolve, reject) => {
@@ -28,7 +28,7 @@ export class BrowserService {
28
28
  const task = conn.tasks.get(finalTaskId);
29
29
  return { task: finalTaskId, windowTargetId: task.windowTargetId };
30
30
  }
31
- const { windowTargetId } = await this.createTaskWindow(conn.cdp, finalTaskId);
31
+ const { windowTargetId } = await this.createTaskWindow(conn, finalTaskId);
32
32
  const task = {
33
33
  id: finalTaskId,
34
34
  profile: profileName,
@@ -88,6 +88,19 @@ export class BrowserService {
88
88
  }
89
89
  async navigate(taskId, url, profileName) {
90
90
  const { conn, task } = await this.findTask(taskId, profileName);
91
+ if (conn.electron) {
92
+ const tabId = task.windowTargetId || task.tabIds[0];
93
+ if (!tabId) {
94
+ throw new Error('No existing tab to navigate in Electron app');
95
+ }
96
+ const sessionId = await this.getSessionId(conn, tabId);
97
+ await conn.cdp.send('Page.navigate', { url }, sessionId);
98
+ if (!task.tabIds.includes(tabId)) {
99
+ task.tabIds.push(tabId);
100
+ }
101
+ await this.saveTaskState(task.profile, conn.tasks);
102
+ return { tabId, url };
103
+ }
91
104
  const result = (await conn.cdp.send('Target.createTarget', {
92
105
  url,
93
106
  }));
@@ -255,6 +268,7 @@ export class BrowserService {
255
268
  cdp,
256
269
  port: existingInfo.port,
257
270
  pid: existingInfo.pid,
271
+ electron: profile.electron,
258
272
  tasks,
259
273
  sessionCache: new Map(),
260
274
  };
@@ -280,6 +294,7 @@ export class BrowserService {
280
294
  cdp: conn.cdp,
281
295
  port: conn.port,
282
296
  pid: conn.pid,
297
+ electron: profile.electron,
283
298
  tasks: conn.pid === 0 ? this.loadTaskState(profile.name) : new Map(),
284
299
  sessionCache: new Map(),
285
300
  };
@@ -291,6 +306,7 @@ export class BrowserService {
291
306
  cdp: conn.cdp,
292
307
  port: conn.port,
293
308
  pid: conn.pid,
309
+ electron: profile.electron,
294
310
  tasks: new Map(),
295
311
  sessionCache: new Map(),
296
312
  };
@@ -300,8 +316,13 @@ export class BrowserService {
300
316
  async enableDomains(cdp) {
301
317
  await cdp.send('Target.setDiscoverTargets', { discover: true });
302
318
  }
303
- async createTaskWindow(cdp, _taskId) {
304
- const result = (await cdp.send('Target.createTarget', {
319
+ async createTaskWindow(conn, _taskId) {
320
+ if (conn.electron) {
321
+ const { targetInfos } = (await conn.cdp.send('Target.getTargets'));
322
+ const pageTarget = targetInfos.find((t) => t.type === 'page');
323
+ return { windowTargetId: pageTarget?.targetId };
324
+ }
325
+ const result = (await conn.cdp.send('Target.createTarget', {
305
326
  url: 'about:blank',
306
327
  newWindow: true,
307
328
  }));
@@ -1,8 +1,10 @@
1
- export type BrowserType = 'chrome' | 'comet' | 'chromium' | 'brave' | 'edge';
1
+ export type BrowserType = 'chrome' | 'comet' | 'chromium' | 'brave' | 'edge' | 'custom';
2
2
  export interface BrowserProfile {
3
3
  name: string;
4
4
  description?: string;
5
5
  browser: BrowserType;
6
+ binary?: string;
7
+ electron?: boolean;
6
8
  endpoints: string[];
7
9
  chrome?: ChromeOptions;
8
10
  secrets?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.14.4",
3
+ "version": "1.14.5",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",