@phnx-labs/agents-cli 1.15.0 → 1.17.0
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 +143 -39
- package/README.md +6 -6
- package/dist/commands/alias.js +2 -2
- package/dist/commands/browser-picker.d.ts +21 -0
- package/dist/commands/browser-picker.js +114 -0
- package/dist/commands/browser.js +793 -83
- package/dist/commands/cloud.js +8 -0
- package/dist/commands/commands.js +72 -22
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/exec.js +70 -1
- package/dist/commands/hooks.js +71 -26
- package/dist/commands/mcp.js +81 -39
- package/dist/commands/plugins.js +224 -17
- package/dist/commands/prune.js +29 -1
- package/dist/commands/pull.js +3 -3
- package/dist/commands/repo.js +1 -1
- package/dist/commands/routines.js +2 -2
- package/dist/commands/secrets.js +154 -20
- package/dist/commands/sessions.js +62 -19
- package/dist/commands/{init.d.ts → setup.d.ts} +7 -6
- package/dist/commands/{init.js → setup.js} +22 -21
- package/dist/commands/skills.js +60 -19
- package/dist/commands/subagents.js +41 -13
- package/dist/commands/utils.d.ts +16 -0
- package/dist/commands/utils.js +32 -0
- package/dist/commands/view.js +78 -20
- package/dist/commands/workflows.d.ts +10 -0
- package/dist/commands/workflows.js +457 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +48 -36
- package/dist/lib/agents.js +2 -2
- package/dist/lib/auto-pull-worker.js +2 -3
- package/dist/lib/auto-pull.js +2 -2
- package/dist/lib/browser/cdp.d.ts +7 -1
- package/dist/lib/browser/cdp.js +32 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +41 -3
- package/dist/lib/browser/devices.d.ts +4 -0
- package/dist/lib/browser/devices.js +27 -0
- package/dist/lib/browser/drivers/local.js +22 -6
- package/dist/lib/browser/drivers/ssh.js +9 -2
- package/dist/lib/browser/input.d.ts +1 -0
- package/dist/lib/browser/input.js +3 -0
- package/dist/lib/browser/ipc.js +158 -23
- package/dist/lib/browser/profiles.d.ts +10 -2
- package/dist/lib/browser/profiles.js +122 -37
- package/dist/lib/browser/service.d.ts +91 -13
- package/dist/lib/browser/service.js +767 -132
- package/dist/lib/browser/types.d.ts +91 -3
- package/dist/lib/browser/types.js +16 -0
- package/dist/lib/cloud/rush.d.ts +28 -1
- package/dist/lib/cloud/rush.js +69 -14
- package/dist/lib/cloud/store.js +2 -2
- package/dist/lib/commands.d.ts +1 -15
- package/dist/lib/commands.js +11 -7
- package/dist/lib/daemon.js +2 -3
- package/dist/lib/doctor-diff.js +4 -4
- package/dist/lib/events.js +2 -2
- package/dist/lib/hooks.d.ts +11 -7
- package/dist/lib/hooks.js +138 -49
- package/dist/lib/migrate.d.ts +1 -1
- package/dist/lib/migrate.js +1237 -22
- package/dist/lib/models.js +2 -2
- package/dist/lib/permissions.d.ts +8 -66
- package/dist/lib/permissions.js +18 -18
- package/dist/lib/plugins.d.ts +94 -24
- package/dist/lib/plugins.js +702 -123
- package/dist/lib/pty-server.js +9 -10
- package/dist/lib/resource-patterns.d.ts +41 -0
- package/dist/lib/resource-patterns.js +82 -0
- package/dist/lib/resources/hooks.d.ts +5 -1
- package/dist/lib/resources/hooks.js +21 -4
- package/dist/lib/resources/index.d.ts +17 -0
- package/dist/lib/resources/index.js +7 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources/workflows.d.ts +24 -0
- package/dist/lib/resources/workflows.js +110 -0
- package/dist/lib/resources.d.ts +6 -1
- package/dist/lib/resources.js +12 -2
- package/dist/lib/rotate.js +3 -4
- package/dist/lib/session/active.d.ts +3 -0
- package/dist/lib/session/active.js +92 -6
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +18 -0
- package/dist/lib/session/db.js +109 -5
- package/dist/lib/session/discover.d.ts +6 -0
- package/dist/lib/session/discover.js +55 -29
- package/dist/lib/session/team-filter.js +2 -2
- package/dist/lib/shims.d.ts +4 -52
- package/dist/lib/shims.js +23 -15
- package/dist/lib/skills.js +6 -2
- package/dist/lib/sqlite.js +10 -4
- package/dist/lib/state.d.ts +101 -16
- package/dist/lib/state.js +179 -31
- package/dist/lib/subagents.d.ts +28 -0
- package/dist/lib/subagents.js +98 -1
- package/dist/lib/sync-manifest.d.ts +1 -1
- package/dist/lib/sync-manifest.js +3 -3
- package/dist/lib/teams/persistence.js +15 -5
- package/dist/lib/teams/registry.js +2 -2
- package/dist/lib/types.d.ts +75 -17
- package/dist/lib/types.js +3 -3
- package/dist/lib/usage.js +2 -2
- package/dist/lib/versions.d.ts +3 -0
- package/dist/lib/versions.js +158 -47
- package/dist/lib/workflows.d.ts +79 -0
- package/dist/lib/workflows.js +233 -0
- package/package.json +1 -5
- package/scripts/postinstall.js +60 -59
- package/dist/commands/fork.d.ts +0 -10
- package/dist/commands/fork.js +0 -146
package/dist/lib/browser/ipc.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as net from 'net';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
-
import {
|
|
4
|
+
import { getHelpersDir } from '../state.js';
|
|
5
5
|
import { startDaemon } from '../daemon.js';
|
|
6
6
|
const SOCKET_NAME = 'browser.sock';
|
|
7
7
|
export function getSocketPath() {
|
|
8
|
-
return path.join(
|
|
8
|
+
return path.join(getHelpersDir(), SOCKET_NAME);
|
|
9
9
|
}
|
|
10
10
|
export class BrowserIPCServer {
|
|
11
11
|
server = null;
|
|
@@ -67,8 +67,30 @@ export class BrowserIPCServer {
|
|
|
67
67
|
if (!request.profile) {
|
|
68
68
|
return { ok: false, error: 'Profile required' };
|
|
69
69
|
}
|
|
70
|
-
const result = await this.service.start(request.profile,
|
|
71
|
-
|
|
70
|
+
const result = await this.service.start(request.profile, {
|
|
71
|
+
taskName: request.taskName,
|
|
72
|
+
url: request.url,
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
ok: true,
|
|
76
|
+
task: result.name,
|
|
77
|
+
tabId: result.tabId,
|
|
78
|
+
windowTargetId: result.windowId,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
case 'launch-profile': {
|
|
82
|
+
if (!request.profile) {
|
|
83
|
+
return { ok: false, error: 'Profile required' };
|
|
84
|
+
}
|
|
85
|
+
const result = await this.service.launchProfile(request.profile);
|
|
86
|
+
return { ok: true, port: result.port, pid: result.pid };
|
|
87
|
+
}
|
|
88
|
+
case 'done': {
|
|
89
|
+
if (!request.task) {
|
|
90
|
+
return { ok: false, error: 'Task required' };
|
|
91
|
+
}
|
|
92
|
+
const result = await this.service.done(request.task);
|
|
93
|
+
return { ok: result.ok, error: result.ok ? undefined : 'Task not found' };
|
|
72
94
|
}
|
|
73
95
|
case 'stop': {
|
|
74
96
|
if (request.task) {
|
|
@@ -85,6 +107,10 @@ export class BrowserIPCServer {
|
|
|
85
107
|
const profiles = await this.service.status(request.profile);
|
|
86
108
|
return { ok: true, profiles };
|
|
87
109
|
}
|
|
110
|
+
case 'history': {
|
|
111
|
+
const history = await this.service.getHistory(request.limit ?? 10);
|
|
112
|
+
return { ok: true, history };
|
|
113
|
+
}
|
|
88
114
|
case 'navigate': {
|
|
89
115
|
if (!request.task || !request.url) {
|
|
90
116
|
return { ok: false, error: 'Task and URL required' };
|
|
@@ -92,20 +118,37 @@ export class BrowserIPCServer {
|
|
|
92
118
|
const result = await this.service.navigate(request.task, request.url, request.profile);
|
|
93
119
|
return { ok: true, tabId: result.tabId };
|
|
94
120
|
}
|
|
95
|
-
case '
|
|
96
|
-
|
|
97
|
-
|
|
121
|
+
case 'tab-add': {
|
|
122
|
+
if (!request.task || !request.url) {
|
|
123
|
+
return { ok: false, error: 'Task and URL required' };
|
|
124
|
+
}
|
|
125
|
+
const result = await this.service.tabAdd(request.task, request.url, request.profile);
|
|
126
|
+
return { ok: true, tabId: result.tabId };
|
|
98
127
|
}
|
|
99
|
-
case '
|
|
128
|
+
case 'tab-focus': {
|
|
129
|
+
if (!request.task || !request.tabId) {
|
|
130
|
+
return { ok: false, error: 'Task and tabId required' };
|
|
131
|
+
}
|
|
132
|
+
const result = await this.service.tabFocus(request.task, request.tabId);
|
|
133
|
+
return { ok: true, tabId: result.tabId };
|
|
134
|
+
}
|
|
135
|
+
case 'tab-close': {
|
|
100
136
|
if (!request.task) {
|
|
101
137
|
return { ok: false, error: 'Task required' };
|
|
102
138
|
}
|
|
103
|
-
await this.service.
|
|
139
|
+
await this.service.tabClose(request.task, request.tabId);
|
|
104
140
|
return { ok: true };
|
|
105
141
|
}
|
|
142
|
+
case 'tab-list': {
|
|
143
|
+
if (!request.task) {
|
|
144
|
+
return { ok: false, error: 'Task required' };
|
|
145
|
+
}
|
|
146
|
+
const tabs = await this.service.tabList(request.task);
|
|
147
|
+
return { ok: true, tabs: tabs.map(t => ({ id: t.id, url: t.url, title: t.title, task: request.task })) };
|
|
148
|
+
}
|
|
106
149
|
case 'evaluate': {
|
|
107
|
-
if (!request.task ||
|
|
108
|
-
return { ok: false, error: 'Task
|
|
150
|
+
if (!request.task || !request.expr) {
|
|
151
|
+
return { ok: false, error: 'Task and expression required' };
|
|
109
152
|
}
|
|
110
153
|
const result = await this.service.evaluate(request.task, request.tabId, request.expr);
|
|
111
154
|
return { ok: true, result };
|
|
@@ -128,33 +171,125 @@ export class BrowserIPCServer {
|
|
|
128
171
|
return { ok: true, refs };
|
|
129
172
|
}
|
|
130
173
|
case 'click': {
|
|
131
|
-
if (!request.task ||
|
|
132
|
-
return { ok: false, error: 'Task
|
|
174
|
+
if (!request.task || request.ref === undefined) {
|
|
175
|
+
return { ok: false, error: 'Task and ref required' };
|
|
133
176
|
}
|
|
134
|
-
await this.service.click(request.task, request.
|
|
177
|
+
await this.service.click(request.task, request.ref, request.tabId);
|
|
135
178
|
return { ok: true };
|
|
136
179
|
}
|
|
137
180
|
case 'type': {
|
|
138
|
-
if (!request.task ||
|
|
139
|
-
return { ok: false, error: 'Task,
|
|
181
|
+
if (!request.task || request.ref === undefined || !request.text) {
|
|
182
|
+
return { ok: false, error: 'Task, ref, and text required' };
|
|
140
183
|
}
|
|
141
|
-
await this.service.type(request.task, request.
|
|
184
|
+
await this.service.type(request.task, request.ref, request.text, request.tabId);
|
|
142
185
|
return { ok: true };
|
|
143
186
|
}
|
|
144
187
|
case 'press': {
|
|
145
|
-
if (!request.task || !request.
|
|
146
|
-
return { ok: false, error: 'Task
|
|
188
|
+
if (!request.task || !request.key) {
|
|
189
|
+
return { ok: false, error: 'Task and key required' };
|
|
147
190
|
}
|
|
148
|
-
await this.service.press(request.task, request.
|
|
191
|
+
await this.service.press(request.task, request.key, request.tabId);
|
|
149
192
|
return { ok: true };
|
|
150
193
|
}
|
|
151
194
|
case 'hover': {
|
|
152
|
-
if (!request.task ||
|
|
153
|
-
return { ok: false, error: 'Task
|
|
195
|
+
if (!request.task || request.ref === undefined) {
|
|
196
|
+
return { ok: false, error: 'Task and ref required' };
|
|
197
|
+
}
|
|
198
|
+
await this.service.hover(request.task, request.ref, request.tabId);
|
|
199
|
+
return { ok: true };
|
|
200
|
+
}
|
|
201
|
+
case 'scroll': {
|
|
202
|
+
if (!request.task) {
|
|
203
|
+
return { ok: false, error: 'Task required' };
|
|
204
|
+
}
|
|
205
|
+
await this.service.scroll(request.task, request.scrollX ?? 0, request.scrollY ?? 0, request.scrollAtX, request.scrollAtY, request.tabId);
|
|
206
|
+
return { ok: true };
|
|
207
|
+
}
|
|
208
|
+
case 'set-viewport': {
|
|
209
|
+
if (!request.task || !request.width || !request.height) {
|
|
210
|
+
return { ok: false, error: 'Task, width, and height required' };
|
|
211
|
+
}
|
|
212
|
+
await this.service.setViewport(request.task, request.width, request.height, {
|
|
213
|
+
mobile: request.mobile,
|
|
214
|
+
deviceScaleFactor: request.deviceScaleFactor,
|
|
215
|
+
tabHint: request.tabId,
|
|
216
|
+
});
|
|
217
|
+
return { ok: true };
|
|
218
|
+
}
|
|
219
|
+
case 'set-device': {
|
|
220
|
+
if (!request.task || !request.deviceName) {
|
|
221
|
+
return { ok: false, error: 'Task and device name required' };
|
|
154
222
|
}
|
|
155
|
-
await this.service.
|
|
223
|
+
await this.service.setDevice(request.task, request.deviceName, request.tabId);
|
|
156
224
|
return { ok: true };
|
|
157
225
|
}
|
|
226
|
+
case 'console': {
|
|
227
|
+
if (!request.task) {
|
|
228
|
+
return { ok: false, error: 'Task required' };
|
|
229
|
+
}
|
|
230
|
+
const logs = await this.service.getConsoleLogs(request.task, {
|
|
231
|
+
level: request.level,
|
|
232
|
+
clear: request.clear,
|
|
233
|
+
tabHint: request.tabId,
|
|
234
|
+
});
|
|
235
|
+
return { ok: true, logs };
|
|
236
|
+
}
|
|
237
|
+
case 'errors': {
|
|
238
|
+
if (!request.task) {
|
|
239
|
+
return { ok: false, error: 'Task required' };
|
|
240
|
+
}
|
|
241
|
+
const errors = await this.service.getErrors(request.task, {
|
|
242
|
+
clear: request.clear,
|
|
243
|
+
tabHint: request.tabId,
|
|
244
|
+
});
|
|
245
|
+
return { ok: true, errors };
|
|
246
|
+
}
|
|
247
|
+
case 'requests': {
|
|
248
|
+
if (!request.task) {
|
|
249
|
+
return { ok: false, error: 'Task required' };
|
|
250
|
+
}
|
|
251
|
+
const requests = await this.service.getNetworkRequests(request.task, {
|
|
252
|
+
filter: request.filter,
|
|
253
|
+
clear: request.clear,
|
|
254
|
+
tabHint: request.tabId,
|
|
255
|
+
});
|
|
256
|
+
return { ok: true, requests };
|
|
257
|
+
}
|
|
258
|
+
case 'response-body': {
|
|
259
|
+
if (!request.task || !request.urlPattern) {
|
|
260
|
+
return { ok: false, error: 'Task and URL pattern required' };
|
|
261
|
+
}
|
|
262
|
+
const body = await this.service.getResponseBody(request.task, request.urlPattern, {
|
|
263
|
+
timeout: request.timeout,
|
|
264
|
+
maxChars: request.maxChars,
|
|
265
|
+
tabHint: request.tabId,
|
|
266
|
+
});
|
|
267
|
+
return { ok: true, body };
|
|
268
|
+
}
|
|
269
|
+
case 'wait': {
|
|
270
|
+
if (!request.task || !request.waitType || request.waitValue === undefined) {
|
|
271
|
+
return { ok: false, error: 'Task, wait type, and wait value required' };
|
|
272
|
+
}
|
|
273
|
+
await this.service.wait(request.task, request.waitType, request.waitValue, {
|
|
274
|
+
timeout: request.timeout,
|
|
275
|
+
tabHint: request.tabId,
|
|
276
|
+
});
|
|
277
|
+
return { ok: true };
|
|
278
|
+
}
|
|
279
|
+
case 'set-download-path': {
|
|
280
|
+
if (!request.task || !request.downloadPath) {
|
|
281
|
+
return { ok: false, error: 'Task and download path required' };
|
|
282
|
+
}
|
|
283
|
+
await this.service.setDownloadPath(request.task, request.downloadPath, request.tabId);
|
|
284
|
+
return { ok: true };
|
|
285
|
+
}
|
|
286
|
+
case 'wait-download': {
|
|
287
|
+
if (!request.task) {
|
|
288
|
+
return { ok: false, error: 'Task required' };
|
|
289
|
+
}
|
|
290
|
+
const downloadPath = await this.service.waitForDownload(request.task, request.timeout);
|
|
291
|
+
return { ok: true, downloadPath };
|
|
292
|
+
}
|
|
158
293
|
default:
|
|
159
294
|
return { ok: false, error: `Unknown action: ${request.action}` };
|
|
160
295
|
}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import type { BrowserProfile } from './types.js';
|
|
2
2
|
export type { BrowserProfile } from './types.js';
|
|
3
|
-
export declare function getBrowserProfilesDir(): string;
|
|
4
3
|
export declare function getBrowserRuntimeDir(): string;
|
|
5
|
-
export declare function getProfilePath(name: string): string;
|
|
6
4
|
export declare function getProfileRuntimeDir(name: string): string;
|
|
7
5
|
export declare function listProfiles(): Promise<BrowserProfile[]>;
|
|
8
6
|
export declare function getProfile(name: string): Promise<BrowserProfile | null>;
|
|
7
|
+
/**
|
|
8
|
+
* Find a port in 9222–9399 that is not already claimed by another profile
|
|
9
|
+
* and is not currently in use by any OS process.
|
|
10
|
+
*/
|
|
11
|
+
export declare function findFreeProfilePort(): Promise<number>;
|
|
9
12
|
export declare function createProfile(profile: BrowserProfile): Promise<void>;
|
|
10
13
|
export declare function updateProfile(profile: BrowserProfile): Promise<void>;
|
|
11
14
|
export declare function deleteProfile(name: string): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Extract the port intended by the profile's first endpoint.
|
|
17
|
+
* Returns undefined for endpoint shapes that don't carry a port (e.g. ws:// without one).
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractConfiguredPort(profile: BrowserProfile): number | undefined;
|
|
@@ -1,61 +1,146 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
1
|
import * as path from 'path';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
export function getBrowserProfilesDir() {
|
|
7
|
-
return path.join(getUserAgentsDir(), 'browser', 'profiles');
|
|
8
|
-
}
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { getBrowserRuntimeDir as getBrowserRuntimeDirRoot, readMeta, writeMeta, } from '../state.js';
|
|
4
|
+
import { findBrowserPath } from './chrome.js';
|
|
9
5
|
export function getBrowserRuntimeDir() {
|
|
10
|
-
|
|
11
|
-
return path.join(agentsDir, 'browser');
|
|
12
|
-
}
|
|
13
|
-
export function getProfilePath(name) {
|
|
14
|
-
return path.join(getBrowserProfilesDir(), `${name}.yaml`);
|
|
6
|
+
return getBrowserRuntimeDirRoot();
|
|
15
7
|
}
|
|
16
8
|
export function getProfileRuntimeDir(name) {
|
|
17
9
|
return path.join(getBrowserRuntimeDir(), name);
|
|
18
10
|
}
|
|
11
|
+
function configToProfile(name, config) {
|
|
12
|
+
return {
|
|
13
|
+
name,
|
|
14
|
+
description: config.description,
|
|
15
|
+
browser: config.browser,
|
|
16
|
+
binary: config.binary,
|
|
17
|
+
electron: config.electron,
|
|
18
|
+
endpoints: config.endpoints,
|
|
19
|
+
chrome: config.chrome,
|
|
20
|
+
secrets: config.secrets,
|
|
21
|
+
viewport: config.viewport,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function profileToConfig(profile) {
|
|
25
|
+
const config = {
|
|
26
|
+
browser: profile.browser,
|
|
27
|
+
endpoints: profile.endpoints,
|
|
28
|
+
};
|
|
29
|
+
if (profile.description)
|
|
30
|
+
config.description = profile.description;
|
|
31
|
+
if (profile.binary)
|
|
32
|
+
config.binary = profile.binary;
|
|
33
|
+
if (profile.electron)
|
|
34
|
+
config.electron = profile.electron;
|
|
35
|
+
if (profile.chrome)
|
|
36
|
+
config.chrome = profile.chrome;
|
|
37
|
+
if (profile.secrets)
|
|
38
|
+
config.secrets = profile.secrets;
|
|
39
|
+
if (profile.viewport)
|
|
40
|
+
config.viewport = profile.viewport;
|
|
41
|
+
return config;
|
|
42
|
+
}
|
|
19
43
|
export async function listProfiles() {
|
|
20
|
-
const
|
|
21
|
-
if (!
|
|
44
|
+
const meta = readMeta();
|
|
45
|
+
if (!meta.browser)
|
|
22
46
|
return [];
|
|
23
|
-
|
|
24
|
-
const profiles = [];
|
|
25
|
-
for (const file of files) {
|
|
26
|
-
const content = fs.readFileSync(path.join(dir, file), 'utf-8');
|
|
27
|
-
const profile = yaml.parse(content);
|
|
28
|
-
profiles.push(profile);
|
|
29
|
-
}
|
|
30
|
-
return profiles;
|
|
47
|
+
return Object.entries(meta.browser).map(([name, config]) => configToProfile(name, config));
|
|
31
48
|
}
|
|
32
49
|
export async function getProfile(name) {
|
|
33
|
-
const
|
|
34
|
-
|
|
50
|
+
const meta = readMeta();
|
|
51
|
+
const config = meta.browser?.[name];
|
|
52
|
+
if (!config)
|
|
35
53
|
return null;
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
return configToProfile(name, config);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Find a port in 9222–9399 that is not already claimed by another profile
|
|
58
|
+
* and is not currently in use by any OS process.
|
|
59
|
+
*/
|
|
60
|
+
export async function findFreeProfilePort() {
|
|
61
|
+
const profiles = await listProfiles();
|
|
62
|
+
const usedByProfile = new Set();
|
|
63
|
+
for (const p of profiles) {
|
|
64
|
+
const port = extractConfiguredPort(p);
|
|
65
|
+
if (port !== undefined)
|
|
66
|
+
usedByProfile.add(port);
|
|
67
|
+
}
|
|
68
|
+
for (let port = 9222; port <= 9399; port++) {
|
|
69
|
+
if (usedByProfile.has(port))
|
|
70
|
+
continue;
|
|
71
|
+
try {
|
|
72
|
+
execSync(`lsof -i :${port}`, { stdio: 'ignore' });
|
|
73
|
+
// lsof succeeded → something is listening → port is in use
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// lsof threw → nothing on this port → it's free
|
|
77
|
+
return port;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
throw new Error('No available ports in range 9222-9399');
|
|
38
81
|
}
|
|
39
82
|
export async function createProfile(profile) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
const filePath = getProfilePath(profile.name);
|
|
43
|
-
if (fs.existsSync(filePath)) {
|
|
83
|
+
const meta = readMeta();
|
|
84
|
+
if (meta.browser?.[profile.name]) {
|
|
44
85
|
throw new Error(`Profile "${profile.name}" already exists`);
|
|
45
86
|
}
|
|
46
|
-
|
|
87
|
+
// Check for port collision with existing profiles
|
|
88
|
+
const newPort = extractConfiguredPort(profile);
|
|
89
|
+
if (newPort !== undefined && meta.browser) {
|
|
90
|
+
for (const [existingName, existingConfig] of Object.entries(meta.browser)) {
|
|
91
|
+
const existingProfile = configToProfile(existingName, existingConfig);
|
|
92
|
+
const existingPort = extractConfiguredPort(existingProfile);
|
|
93
|
+
if (existingPort === newPort) {
|
|
94
|
+
throw new Error(`Port ${newPort} is already used by profile "${existingName}". ` +
|
|
95
|
+
`Each profile must own a unique port. Use a different port or omit --endpoint to auto-assign.`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Resolve the browser binary at create time. Fails fast with an actionable
|
|
100
|
+
// error ("Comet not installed at /Applications/Comet.app") rather than
|
|
101
|
+
// deferring the failure to the first task. `findBrowserPath` short-circuits
|
|
102
|
+
// for browser=custom without a binary by throwing — same outcome.
|
|
103
|
+
findBrowserPath(profile.browser, profile.binary);
|
|
104
|
+
meta.browser = meta.browser ?? {};
|
|
105
|
+
meta.browser[profile.name] = profileToConfig(profile);
|
|
106
|
+
writeMeta(meta);
|
|
47
107
|
}
|
|
48
108
|
export async function updateProfile(profile) {
|
|
49
|
-
const
|
|
50
|
-
if (!
|
|
109
|
+
const meta = readMeta();
|
|
110
|
+
if (!meta.browser?.[profile.name]) {
|
|
51
111
|
throw new Error(`Profile "${profile.name}" does not exist`);
|
|
52
112
|
}
|
|
53
|
-
|
|
113
|
+
meta.browser[profile.name] = profileToConfig(profile);
|
|
114
|
+
writeMeta(meta);
|
|
54
115
|
}
|
|
55
116
|
export async function deleteProfile(name) {
|
|
56
|
-
const
|
|
57
|
-
if (!
|
|
117
|
+
const meta = readMeta();
|
|
118
|
+
if (!meta.browser?.[name]) {
|
|
58
119
|
throw new Error(`Profile "${name}" does not exist`);
|
|
59
120
|
}
|
|
60
|
-
|
|
121
|
+
delete meta.browser[name];
|
|
122
|
+
writeMeta(meta);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Extract the port intended by the profile's first endpoint.
|
|
126
|
+
* Returns undefined for endpoint shapes that don't carry a port (e.g. ws:// without one).
|
|
127
|
+
*/
|
|
128
|
+
export function extractConfiguredPort(profile) {
|
|
129
|
+
const endpoint = profile.endpoints[0];
|
|
130
|
+
if (!endpoint)
|
|
131
|
+
return undefined;
|
|
132
|
+
let url;
|
|
133
|
+
try {
|
|
134
|
+
url = new URL(endpoint);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
if (url.port)
|
|
140
|
+
return parseInt(url.port, 10);
|
|
141
|
+
if (url.protocol === 'cdp:')
|
|
142
|
+
return 9222;
|
|
143
|
+
if (url.protocol === 'ssh:')
|
|
144
|
+
return 9222;
|
|
145
|
+
return undefined;
|
|
61
146
|
}
|
|
@@ -1,13 +1,36 @@
|
|
|
1
|
-
import { type TabInfo, type ProfileStatus } from './types.js';
|
|
1
|
+
import { type TabInfo, type ProfileStatus, type HistoricalTask } from './types.js';
|
|
2
2
|
import { type RefOpts, type RefNode } from './refs.js';
|
|
3
3
|
export declare class BrowserService {
|
|
4
4
|
private connections;
|
|
5
5
|
private forkingProfiles;
|
|
6
|
-
|
|
6
|
+
private consoleLogs;
|
|
7
|
+
private pageErrors;
|
|
8
|
+
private networkRequests;
|
|
9
|
+
private pendingDownloads;
|
|
10
|
+
private enabledSessions;
|
|
11
|
+
start(profileName: string, opts?: {
|
|
12
|
+
taskName?: string;
|
|
13
|
+
url?: string;
|
|
14
|
+
}): Promise<{
|
|
7
15
|
task: string;
|
|
8
|
-
|
|
16
|
+
name: string;
|
|
17
|
+
tabId?: string;
|
|
18
|
+
windowId?: string;
|
|
9
19
|
}>;
|
|
10
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Launch (or attach to) the profile's browser without creating a task. Used by
|
|
22
|
+
* `agents browser profiles launch <name>` so users can warm up the browser —
|
|
23
|
+
* including the first-run onboarding flow — before any automation starts.
|
|
24
|
+
*/
|
|
25
|
+
launchProfile(profileName: string): Promise<{
|
|
26
|
+
port: number;
|
|
27
|
+
pid: number;
|
|
28
|
+
}>;
|
|
29
|
+
stop(taskName: string): Promise<{
|
|
30
|
+
ok: boolean;
|
|
31
|
+
profile?: string;
|
|
32
|
+
}>;
|
|
33
|
+
done(taskName: string): Promise<{
|
|
11
34
|
ok: boolean;
|
|
12
35
|
profile?: string;
|
|
13
36
|
}>;
|
|
@@ -15,28 +38,81 @@ export declare class BrowserService {
|
|
|
15
38
|
navigate(taskId: string, url: string, profileName?: string): Promise<{
|
|
16
39
|
tabId: string;
|
|
17
40
|
url: string;
|
|
41
|
+
created: boolean;
|
|
42
|
+
}>;
|
|
43
|
+
tabAdd(taskId: string, url: string, profileName?: string): Promise<{
|
|
44
|
+
tabId: string;
|
|
45
|
+
url: string;
|
|
18
46
|
}>;
|
|
47
|
+
tabFocus(taskId: string, tabHint: string): Promise<{
|
|
48
|
+
tabId: string;
|
|
49
|
+
}>;
|
|
50
|
+
tabList(taskId: string): Promise<Array<{
|
|
51
|
+
id: string;
|
|
52
|
+
url: string;
|
|
53
|
+
title: string;
|
|
54
|
+
current: boolean;
|
|
55
|
+
}>>;
|
|
56
|
+
private resolveTabHint;
|
|
57
|
+
private resolveCurrentTab;
|
|
58
|
+
private getCdpTargetId;
|
|
19
59
|
tabs(taskId?: string, profileName?: string): Promise<TabInfo[]>;
|
|
20
|
-
|
|
21
|
-
evaluate(taskId: string,
|
|
22
|
-
screenshot(taskId: string,
|
|
60
|
+
tabClose(taskId: string, tabHint?: string): Promise<void>;
|
|
61
|
+
evaluate(taskId: string, tabHint: string | undefined, expression: string): Promise<unknown>;
|
|
62
|
+
screenshot(taskId: string, tabHint?: string, outputPath?: string): Promise<string>;
|
|
23
63
|
private refsCache;
|
|
24
|
-
refs(taskId: string,
|
|
64
|
+
refs(taskId: string, tabHint?: string, opts?: RefOpts): Promise<{
|
|
25
65
|
refs: string;
|
|
26
66
|
nodeMap: Map<number, RefNode>;
|
|
27
67
|
}>;
|
|
28
|
-
click(taskId: string,
|
|
29
|
-
type(taskId: string,
|
|
30
|
-
press(taskId: string,
|
|
31
|
-
hover(taskId: string,
|
|
68
|
+
click(taskId: string, ref: number, tabHint?: string): Promise<void>;
|
|
69
|
+
type(taskId: string, ref: number, text: string, tabHint?: string): Promise<void>;
|
|
70
|
+
press(taskId: string, key: string, tabHint?: string): Promise<void>;
|
|
71
|
+
hover(taskId: string, ref: number, tabHint?: string): Promise<void>;
|
|
72
|
+
scroll(taskId: string, deltaX: number, deltaY: number, atX?: number, atY?: number, tabHint?: string): Promise<void>;
|
|
32
73
|
status(profileName?: string): Promise<ProfileStatus[]>;
|
|
74
|
+
private reconcileFromDisk;
|
|
75
|
+
setViewport(taskId: string, width: number, height: number, options?: {
|
|
76
|
+
mobile?: boolean;
|
|
77
|
+
deviceScaleFactor?: number;
|
|
78
|
+
tabHint?: string;
|
|
79
|
+
}): Promise<void>;
|
|
80
|
+
setDevice(taskId: string, deviceName: string, tabHint?: string): Promise<void>;
|
|
81
|
+
private enableRuntimeForSession;
|
|
82
|
+
getConsoleLogs(taskId: string, options?: {
|
|
83
|
+
level?: string;
|
|
84
|
+
clear?: boolean;
|
|
85
|
+
tabHint?: string;
|
|
86
|
+
}): Promise<import('./types.js').ConsoleEntry[]>;
|
|
87
|
+
getErrors(taskId: string, options?: {
|
|
88
|
+
clear?: boolean;
|
|
89
|
+
tabHint?: string;
|
|
90
|
+
}): Promise<import('./types.js').ErrorEntry[]>;
|
|
91
|
+
private enableNetworkForSession;
|
|
92
|
+
getNetworkRequests(taskId: string, options?: {
|
|
93
|
+
filter?: string;
|
|
94
|
+
clear?: boolean;
|
|
95
|
+
tabHint?: string;
|
|
96
|
+
}): Promise<import('./types.js').NetworkRequest[]>;
|
|
97
|
+
getResponseBody(taskId: string, urlPattern: string, options?: {
|
|
98
|
+
timeout?: number;
|
|
99
|
+
maxChars?: number;
|
|
100
|
+
tabHint?: string;
|
|
101
|
+
}): Promise<string>;
|
|
102
|
+
wait(taskId: string, type: 'time' | 'selector' | 'url' | 'function' | 'load', value: string | number, options?: {
|
|
103
|
+
timeout?: number;
|
|
104
|
+
tabHint?: string;
|
|
105
|
+
}): Promise<void>;
|
|
106
|
+
setDownloadPath(taskId: string, downloadPath: string, tabHint?: string): Promise<void>;
|
|
107
|
+
waitForDownload(taskId: string, timeout?: number): Promise<string>;
|
|
108
|
+
private findTaskBySession;
|
|
33
109
|
shutdown(): Promise<void>;
|
|
34
110
|
private findAvailableFork;
|
|
35
111
|
private forkElectronProfile;
|
|
36
112
|
private connectProfile;
|
|
37
113
|
private connectEndpoint;
|
|
38
114
|
private enableDomains;
|
|
39
|
-
private
|
|
115
|
+
private getOrCreateWindow;
|
|
40
116
|
private findTask;
|
|
41
117
|
private getTabsForTask;
|
|
42
118
|
private getProfileStatus;
|
|
@@ -45,4 +121,6 @@ export declare class BrowserService {
|
|
|
45
121
|
private invalidateTargetCache;
|
|
46
122
|
private saveTaskState;
|
|
47
123
|
private loadTaskState;
|
|
124
|
+
private saveToHistory;
|
|
125
|
+
getHistory(limit?: number): Promise<HistoricalTask[]>;
|
|
48
126
|
}
|