@phnx-labs/agents-cli 1.18.3 → 1.18.4
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 +78 -0
- package/README.md +14 -7
- package/dist/commands/browser.js +355 -118
- package/dist/lib/browser/devices.d.ts +11 -0
- package/dist/lib/browser/devices.js +14 -3
- package/dist/lib/browser/ipc.js +29 -9
- package/dist/lib/browser/profiles.d.ts +23 -1
- package/dist/lib/browser/profiles.js +63 -3
- package/dist/lib/browser/service.d.ts +41 -10
- package/dist/lib/browser/service.js +321 -64
- package/dist/lib/browser/types.d.ts +55 -2
- package/dist/lib/browser/types.js +20 -0
- package/dist/lib/help.js +30 -3
- package/dist/lib/types.d.ts +12 -1
- package/package.json +1 -1
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { DeviceDescriptor } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Default viewport for newly-created profiles. Matches Safari's logical
|
|
4
|
+
* resolution on a 14-inch MacBook Pro (M1/M2/M3 Pro/Max) — the most common
|
|
5
|
+
* shape this CLI sees in practice. Shared with the `MacBook Pro` device
|
|
6
|
+
* preset below so both surfaces agree.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEFAULT_VIEWPORT: {
|
|
9
|
+
readonly width: 1512;
|
|
10
|
+
readonly height: 982;
|
|
11
|
+
readonly deviceScaleFactor: 2;
|
|
12
|
+
};
|
|
2
13
|
export declare const DEVICES: Record<string, DeviceDescriptor>;
|
|
3
14
|
export declare function getDevice(name: string): DeviceDescriptor | undefined;
|
|
4
15
|
export declare function listDevices(): string[];
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default viewport for newly-created profiles. Matches Safari's logical
|
|
3
|
+
* resolution on a 14-inch MacBook Pro (M1/M2/M3 Pro/Max) — the most common
|
|
4
|
+
* shape this CLI sees in practice. Shared with the `MacBook Pro` device
|
|
5
|
+
* preset below so both surfaces agree.
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_VIEWPORT = {
|
|
8
|
+
width: 1512,
|
|
9
|
+
height: 982,
|
|
10
|
+
deviceScaleFactor: 2,
|
|
11
|
+
};
|
|
1
12
|
export const DEVICES = {
|
|
2
13
|
'iPhone 14': {
|
|
3
14
|
width: 390,
|
|
@@ -12,9 +23,9 @@ export const DEVICES = {
|
|
|
12
23
|
mobile: true,
|
|
13
24
|
},
|
|
14
25
|
'MacBook Pro': {
|
|
15
|
-
width:
|
|
16
|
-
height:
|
|
17
|
-
deviceScaleFactor:
|
|
26
|
+
width: DEFAULT_VIEWPORT.width,
|
|
27
|
+
height: DEFAULT_VIEWPORT.height,
|
|
28
|
+
deviceScaleFactor: DEFAULT_VIEWPORT.deviceScaleFactor,
|
|
18
29
|
mobile: false,
|
|
19
30
|
},
|
|
20
31
|
};
|
package/dist/lib/browser/ipc.js
CHANGED
|
@@ -70,6 +70,7 @@ export class BrowserIPCServer {
|
|
|
70
70
|
const result = await this.service.start(request.profile, {
|
|
71
71
|
taskName: request.taskName,
|
|
72
72
|
url: request.url,
|
|
73
|
+
endpointName: request.endpoint,
|
|
73
74
|
});
|
|
74
75
|
return {
|
|
75
76
|
ok: true,
|
|
@@ -78,13 +79,6 @@ export class BrowserIPCServer {
|
|
|
78
79
|
windowTargetId: result.windowId,
|
|
79
80
|
};
|
|
80
81
|
}
|
|
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
82
|
case 'done': {
|
|
89
83
|
if (!request.task) {
|
|
90
84
|
return { ok: false, error: 'Task required' };
|
|
@@ -153,12 +147,38 @@ export class BrowserIPCServer {
|
|
|
153
147
|
const result = await this.service.evaluate(request.task, request.tabId, request.expr);
|
|
154
148
|
return { ok: true, result };
|
|
155
149
|
}
|
|
150
|
+
case 'record-start': {
|
|
151
|
+
if (!request.task)
|
|
152
|
+
return { ok: false, error: 'Task required' };
|
|
153
|
+
try {
|
|
154
|
+
const r = await this.service.recordStart(request.task, request.tabId, {
|
|
155
|
+
fps: request.fps,
|
|
156
|
+
duration: request.duration,
|
|
157
|
+
maxMb: request.maxMb,
|
|
158
|
+
});
|
|
159
|
+
return { ok: true, path: r.path, fps: r.fps, durationCapSec: r.durationCapSec, maxMb: r.maxMb };
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
case 'record-stop': {
|
|
166
|
+
if (!request.task)
|
|
167
|
+
return { ok: false, error: 'Task required' };
|
|
168
|
+
try {
|
|
169
|
+
const r = await this.service.recordStop(request.task);
|
|
170
|
+
return { ok: true, path: r.path, bytes: r.bytes, durationMs: r.durationMs, stopReason: r.reason };
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
156
176
|
case 'screenshot': {
|
|
157
177
|
if (!request.task) {
|
|
158
178
|
return { ok: false, error: 'Task required' };
|
|
159
179
|
}
|
|
160
|
-
const
|
|
161
|
-
return { ok: true, path:
|
|
180
|
+
const shot = await this.service.screenshot(request.task, request.tabId, request.path, request.quality);
|
|
181
|
+
return { ok: true, path: shot.path, bytes: shot.bytes, width: shot.width, height: shot.height };
|
|
162
182
|
}
|
|
163
183
|
case 'refs': {
|
|
164
184
|
if (!request.task) {
|
|
@@ -13,7 +13,29 @@ export declare function createProfile(profile: BrowserProfile): Promise<void>;
|
|
|
13
13
|
export declare function updateProfile(profile: BrowserProfile): Promise<void>;
|
|
14
14
|
export declare function deleteProfile(name: string): Promise<void>;
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Resolve a profile's endpoint presets into a normalized map regardless of
|
|
17
|
+
* whether the YAML uses the legacy `string[]` shape or the new map shape.
|
|
18
|
+
* The legacy entries get auto-named `endpoint-0`, `endpoint-1`, ... .
|
|
19
|
+
*/
|
|
20
|
+
export declare function getEndpointPresets(profile: BrowserProfile): Record<string, import('./types.js').EndpointPreset>;
|
|
21
|
+
/**
|
|
22
|
+
* Pick the endpoint preset to use. Order:
|
|
23
|
+
* 1. Explicit name passed in (errors if unknown)
|
|
24
|
+
* 2. `profile.defaultEndpoint` if set
|
|
25
|
+
* 3. First entry (preserves legacy string[] behavior)
|
|
26
|
+
*
|
|
27
|
+
* Returns the resolved name + the preset (with per-endpoint overrides
|
|
28
|
+
* already applied to binary / targetFilter), so callers don't have to
|
|
29
|
+
* remember the precedence rules.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveEndpoint(profile: BrowserProfile, endpointName?: string): {
|
|
32
|
+
name: string;
|
|
33
|
+
target: string;
|
|
34
|
+
binary?: string;
|
|
35
|
+
targetFilter?: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Extract the port intended by the profile's default endpoint.
|
|
17
39
|
* Returns undefined for endpoint shapes that don't carry a port (e.g. ws:// without one).
|
|
18
40
|
*/
|
|
19
41
|
export declare function extractConfiguredPort(profile: BrowserProfile): number | undefined;
|
|
@@ -17,6 +17,7 @@ function configToProfile(name, config) {
|
|
|
17
17
|
electron: config.electron,
|
|
18
18
|
targetFilter: config.targetFilter,
|
|
19
19
|
endpoints: config.endpoints,
|
|
20
|
+
defaultEndpoint: config.defaultEndpoint,
|
|
20
21
|
chrome: config.chrome,
|
|
21
22
|
secrets: config.secrets,
|
|
22
23
|
viewport: config.viewport,
|
|
@@ -35,6 +36,8 @@ function profileToConfig(profile) {
|
|
|
35
36
|
config.electron = profile.electron;
|
|
36
37
|
if (profile.targetFilter)
|
|
37
38
|
config.targetFilter = profile.targetFilter;
|
|
39
|
+
if (profile.defaultEndpoint)
|
|
40
|
+
config.defaultEndpoint = profile.defaultEndpoint;
|
|
38
41
|
if (profile.chrome)
|
|
39
42
|
config.chrome = profile.chrome;
|
|
40
43
|
if (profile.secrets)
|
|
@@ -125,13 +128,70 @@ export async function deleteProfile(name) {
|
|
|
125
128
|
writeMeta(meta);
|
|
126
129
|
}
|
|
127
130
|
/**
|
|
128
|
-
*
|
|
131
|
+
* Resolve a profile's endpoint presets into a normalized map regardless of
|
|
132
|
+
* whether the YAML uses the legacy `string[]` shape or the new map shape.
|
|
133
|
+
* The legacy entries get auto-named `endpoint-0`, `endpoint-1`, ... .
|
|
134
|
+
*/
|
|
135
|
+
export function getEndpointPresets(profile) {
|
|
136
|
+
if (Array.isArray(profile.endpoints)) {
|
|
137
|
+
const out = {};
|
|
138
|
+
profile.endpoints.forEach((target, i) => {
|
|
139
|
+
out[`endpoint-${i}`] = { target };
|
|
140
|
+
});
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
return profile.endpoints;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Pick the endpoint preset to use. Order:
|
|
147
|
+
* 1. Explicit name passed in (errors if unknown)
|
|
148
|
+
* 2. `profile.defaultEndpoint` if set
|
|
149
|
+
* 3. First entry (preserves legacy string[] behavior)
|
|
150
|
+
*
|
|
151
|
+
* Returns the resolved name + the preset (with per-endpoint overrides
|
|
152
|
+
* already applied to binary / targetFilter), so callers don't have to
|
|
153
|
+
* remember the precedence rules.
|
|
154
|
+
*/
|
|
155
|
+
export function resolveEndpoint(profile, endpointName) {
|
|
156
|
+
const presets = getEndpointPresets(profile);
|
|
157
|
+
const names = Object.keys(presets);
|
|
158
|
+
if (names.length === 0) {
|
|
159
|
+
throw new Error(`Profile "${profile.name}" has no endpoints configured`);
|
|
160
|
+
}
|
|
161
|
+
let chosenName;
|
|
162
|
+
if (endpointName) {
|
|
163
|
+
if (!presets[endpointName]) {
|
|
164
|
+
throw new Error(`Endpoint "${endpointName}" not found on profile "${profile.name}". ` +
|
|
165
|
+
`Available: ${names.join(', ')}`);
|
|
166
|
+
}
|
|
167
|
+
chosenName = endpointName;
|
|
168
|
+
}
|
|
169
|
+
else if (profile.defaultEndpoint && presets[profile.defaultEndpoint]) {
|
|
170
|
+
chosenName = profile.defaultEndpoint;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
chosenName = names[0];
|
|
174
|
+
}
|
|
175
|
+
const preset = presets[chosenName];
|
|
176
|
+
return {
|
|
177
|
+
name: chosenName,
|
|
178
|
+
target: preset.target,
|
|
179
|
+
binary: preset.binary ?? profile.binary,
|
|
180
|
+
targetFilter: preset.targetFilter ?? profile.targetFilter,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Extract the port intended by the profile's default endpoint.
|
|
129
185
|
* Returns undefined for endpoint shapes that don't carry a port (e.g. ws:// without one).
|
|
130
186
|
*/
|
|
131
187
|
export function extractConfiguredPort(profile) {
|
|
132
|
-
const
|
|
133
|
-
|
|
188
|
+
const presets = getEndpointPresets(profile);
|
|
189
|
+
const firstName = profile.defaultEndpoint && presets[profile.defaultEndpoint]
|
|
190
|
+
? profile.defaultEndpoint
|
|
191
|
+
: Object.keys(presets)[0];
|
|
192
|
+
if (!firstName)
|
|
134
193
|
return undefined;
|
|
194
|
+
const endpoint = presets[firstName].target;
|
|
135
195
|
let url;
|
|
136
196
|
try {
|
|
137
197
|
url = new URL(endpoint);
|
|
@@ -45,20 +45,13 @@ export declare class BrowserService {
|
|
|
45
45
|
start(profileName: string, opts?: {
|
|
46
46
|
taskName?: string;
|
|
47
47
|
url?: string;
|
|
48
|
+
endpointName?: string;
|
|
48
49
|
}): Promise<{
|
|
49
50
|
task: string;
|
|
50
51
|
name: string;
|
|
51
52
|
tabId?: string;
|
|
52
53
|
windowId?: string;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Launch (or attach to) the profile's browser without creating a task. Used by
|
|
56
|
-
* `agents browser profiles launch <name>` so users can warm up the browser —
|
|
57
|
-
* including the first-run onboarding flow — before any automation starts.
|
|
58
|
-
*/
|
|
59
|
-
launchProfile(profileName: string): Promise<{
|
|
60
|
-
port: number;
|
|
61
|
-
pid: number;
|
|
54
|
+
profile: string;
|
|
62
55
|
}>;
|
|
63
56
|
stop(taskName: string): Promise<{
|
|
64
57
|
ok: boolean;
|
|
@@ -93,7 +86,34 @@ export declare class BrowserService {
|
|
|
93
86
|
tabs(taskId?: string, profileName?: string): Promise<TabInfo[]>;
|
|
94
87
|
tabClose(taskId: string, tabHint?: string): Promise<void>;
|
|
95
88
|
evaluate(taskId: string, tabHint: string | undefined, expression: string): Promise<unknown>;
|
|
96
|
-
screenshot(taskId: string, tabHint?: string, outputPath?: string): Promise<
|
|
89
|
+
screenshot(taskId: string, tabHint?: string, outputPath?: string, quality?: 'compressed' | 'raw'): Promise<{
|
|
90
|
+
path: string;
|
|
91
|
+
bytes: number;
|
|
92
|
+
width: number;
|
|
93
|
+
height: number;
|
|
94
|
+
}>;
|
|
95
|
+
private recordings;
|
|
96
|
+
recordStart(taskId: string, tabHint?: string, opts?: {
|
|
97
|
+
fps?: number;
|
|
98
|
+
duration?: number;
|
|
99
|
+
maxMb?: number;
|
|
100
|
+
}): Promise<{
|
|
101
|
+
path: string;
|
|
102
|
+
fps: number;
|
|
103
|
+
durationCapSec: number;
|
|
104
|
+
maxMb: number;
|
|
105
|
+
}>;
|
|
106
|
+
recordStop(taskId: string, reason?: 'manual' | 'duration-cap' | 'size-cap'): Promise<{
|
|
107
|
+
path: string;
|
|
108
|
+
bytes: number;
|
|
109
|
+
durationMs: number;
|
|
110
|
+
reason: string;
|
|
111
|
+
}>;
|
|
112
|
+
recordStatus(taskId: string): Promise<{
|
|
113
|
+
recording: boolean;
|
|
114
|
+
path?: string;
|
|
115
|
+
elapsedMs?: number;
|
|
116
|
+
}>;
|
|
97
117
|
private refsCache;
|
|
98
118
|
refs(taskId: string, tabHint?: string, opts?: RefOpts): Promise<{
|
|
99
119
|
refs: string;
|
|
@@ -152,10 +172,21 @@ export declare class BrowserService {
|
|
|
152
172
|
shutdown(): Promise<void>;
|
|
153
173
|
private findAvailableFork;
|
|
154
174
|
private forkElectronProfile;
|
|
175
|
+
/**
|
|
176
|
+
* Connect to a profile at a specific endpoint preset. The caller has
|
|
177
|
+
* already resolved the endpoint and built the `effectiveProfile` with
|
|
178
|
+
* the per-endpoint binary/targetFilter overrides applied; we just use it.
|
|
179
|
+
*
|
|
180
|
+
* `effectiveProfile.name` is the composite identifier (`<profile>@<endpoint>`)
|
|
181
|
+
* so per-endpoint pid/port files don't collide when the same app runs
|
|
182
|
+
* locally and remotely at the same time.
|
|
183
|
+
*/
|
|
155
184
|
private connectProfile;
|
|
156
185
|
private connectEndpoint;
|
|
157
186
|
private enableDomains;
|
|
158
187
|
private getOrCreateWindow;
|
|
188
|
+
private hasTaskNamed;
|
|
189
|
+
private generateUniqueTaskName;
|
|
159
190
|
private findTask;
|
|
160
191
|
private getTabsForTask;
|
|
161
192
|
private getProfileStatus;
|