agent-browser-loop 0.2.2 → 0.3.1
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/.claude/skills/agent-browser-loop/REFERENCE.md +131 -0
- package/.claude/skills/agent-browser-loop/SKILL.md +36 -4
- package/README.md +39 -17
- package/package.json +1 -1
- package/src/browser.ts +22 -0
- package/src/cli.ts +461 -33
- package/src/commands.ts +11 -0
- package/src/daemon.ts +170 -8
- package/src/index.ts +19 -0
- package/src/profiles.ts +414 -0
- package/src/server.ts +148 -0
- package/src/version.ts +19 -0
package/src/cli.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
string,
|
|
14
14
|
subcommands,
|
|
15
15
|
} from "cmd-ts";
|
|
16
|
+
import { VERSION } from "./version";
|
|
16
17
|
import type { AgentBrowserOptions } from "./browser";
|
|
17
18
|
import type { StepAction } from "./commands";
|
|
18
19
|
import { parseBrowserConfig } from "./config";
|
|
@@ -24,8 +25,17 @@ import {
|
|
|
24
25
|
isDaemonRunning,
|
|
25
26
|
} from "./daemon";
|
|
26
27
|
import { log, withLog } from "./log";
|
|
28
|
+
import {
|
|
29
|
+
deleteProfile,
|
|
30
|
+
importProfile,
|
|
31
|
+
listProfiles,
|
|
32
|
+
loadProfile,
|
|
33
|
+
resolveProfilePath,
|
|
34
|
+
resolveStorageStateOption,
|
|
35
|
+
saveProfile,
|
|
36
|
+
} from "./profiles";
|
|
27
37
|
import { startBrowserServer } from "./server";
|
|
28
|
-
import type { BrowserCliConfig } from "./types";
|
|
38
|
+
import type { BrowserCliConfig, StorageState } from "./types";
|
|
29
39
|
|
|
30
40
|
// ============================================================================
|
|
31
41
|
// Config Loading
|
|
@@ -80,6 +90,8 @@ async function loadConfig(configPath: string): Promise<BrowserCliConfig> {
|
|
|
80
90
|
return parseBrowserConfig(exported);
|
|
81
91
|
}
|
|
82
92
|
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Shared CLI Options
|
|
83
95
|
// ============================================================================
|
|
84
96
|
// Shared CLI Options
|
|
85
97
|
// ============================================================================
|
|
@@ -119,16 +131,62 @@ const jsonFlag = flag({
|
|
|
119
131
|
description: "Output as JSON instead of text",
|
|
120
132
|
});
|
|
121
133
|
|
|
134
|
+
const profileOption = option({
|
|
135
|
+
long: "profile",
|
|
136
|
+
short: "p",
|
|
137
|
+
type: optional(string),
|
|
138
|
+
description:
|
|
139
|
+
"Load profile and save back on close (use --no-save for read-only)",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const noSaveFlag = flag({
|
|
143
|
+
long: "no-save",
|
|
144
|
+
description: "Don't save profile changes on close (read-only)",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const globalFlag = flag({
|
|
148
|
+
long: "global",
|
|
149
|
+
description: "Save to user-level global profiles (~/.config/agent-browser/)",
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const privateFlag = flag({
|
|
153
|
+
long: "private",
|
|
154
|
+
description: "Save to project .private/ (gitignored, for secrets)",
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const widthOption = option({
|
|
158
|
+
long: "width",
|
|
159
|
+
short: "W",
|
|
160
|
+
type: optional(number),
|
|
161
|
+
description: "Viewport width in pixels (default: 1280)",
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const heightOption = option({
|
|
165
|
+
long: "height",
|
|
166
|
+
short: "H",
|
|
167
|
+
type: optional(number),
|
|
168
|
+
description: "Viewport height in pixels (default: 720)",
|
|
169
|
+
});
|
|
170
|
+
|
|
122
171
|
// ============================================================================
|
|
123
172
|
// Browser Options Resolution
|
|
124
173
|
// ============================================================================
|
|
125
174
|
|
|
175
|
+
type SessionBrowserOptions = AgentBrowserOptions & {
|
|
176
|
+
profile?: string;
|
|
177
|
+
noSave?: boolean;
|
|
178
|
+
};
|
|
179
|
+
|
|
126
180
|
async function resolveBrowserOptions(args: {
|
|
127
181
|
configPath?: string;
|
|
128
182
|
headless?: boolean;
|
|
129
183
|
headed?: boolean;
|
|
130
184
|
bundled?: boolean;
|
|
131
|
-
|
|
185
|
+
profile?: string;
|
|
186
|
+
noSave?: boolean;
|
|
187
|
+
width?: number;
|
|
188
|
+
height?: number;
|
|
189
|
+
}): Promise<SessionBrowserOptions> {
|
|
132
190
|
const configPath = await findConfigPath(args.configPath);
|
|
133
191
|
const config = configPath ? await loadConfig(configPath) : undefined;
|
|
134
192
|
|
|
@@ -143,18 +201,30 @@ async function resolveBrowserOptions(args: {
|
|
|
143
201
|
|
|
144
202
|
const useSystemChrome = args.bundled ? false : config?.useSystemChrome;
|
|
145
203
|
|
|
204
|
+
// Resolve storage state from profile or config
|
|
205
|
+
const storageState = resolveStorageStateOption(
|
|
206
|
+
args.profile,
|
|
207
|
+
config?.storageStatePath,
|
|
208
|
+
);
|
|
209
|
+
|
|
146
210
|
return {
|
|
147
211
|
headless,
|
|
148
212
|
executablePath: config?.executablePath,
|
|
149
213
|
useSystemChrome,
|
|
150
214
|
allowSystemChromeHeadless: config?.allowSystemChromeHeadless,
|
|
151
|
-
viewportWidth: config?.viewportWidth,
|
|
152
|
-
viewportHeight: config?.viewportHeight,
|
|
215
|
+
viewportWidth: args.width ?? config?.viewportWidth,
|
|
216
|
+
viewportHeight: args.height ?? config?.viewportHeight,
|
|
153
217
|
userDataDir: config?.userDataDir,
|
|
154
218
|
timeout: config?.timeout,
|
|
155
219
|
captureNetwork: config?.captureNetwork,
|
|
156
220
|
networkLogLimit: config?.networkLogLimit,
|
|
157
|
-
|
|
221
|
+
// Use resolved storage state (object or path)
|
|
222
|
+
storageState: typeof storageState === "object" ? storageState : undefined,
|
|
223
|
+
storageStatePath:
|
|
224
|
+
typeof storageState === "string" ? storageState : undefined,
|
|
225
|
+
// Track profile for save-on-close
|
|
226
|
+
profile: args.profile,
|
|
227
|
+
noSave: args.noSave,
|
|
158
228
|
};
|
|
159
229
|
}
|
|
160
230
|
|
|
@@ -171,6 +241,7 @@ async function resolveBrowserOptions(args: {
|
|
|
171
241
|
* press:Enter
|
|
172
242
|
* scroll:down
|
|
173
243
|
* scroll:down:500
|
|
244
|
+
* resize:1920:1080
|
|
174
245
|
*/
|
|
175
246
|
function parseAction(actionStr: string): StepAction {
|
|
176
247
|
const parts = actionStr.split(":");
|
|
@@ -207,6 +278,15 @@ function parseAction(actionStr: string): StepAction {
|
|
|
207
278
|
return { type: "select", ref, value };
|
|
208
279
|
}
|
|
209
280
|
|
|
281
|
+
case "resize": {
|
|
282
|
+
const width = Number.parseInt(parts[1], 10);
|
|
283
|
+
const height = Number.parseInt(parts[2], 10);
|
|
284
|
+
if (Number.isNaN(width) || Number.isNaN(height)) {
|
|
285
|
+
throw new Error("resize requires width:height (e.g. resize:1920:1080)");
|
|
286
|
+
}
|
|
287
|
+
return { type: "resize", width, height };
|
|
288
|
+
}
|
|
289
|
+
|
|
210
290
|
default:
|
|
211
291
|
throw new Error(`Unknown action type: ${type}`);
|
|
212
292
|
}
|
|
@@ -320,6 +400,10 @@ const openCommand = command({
|
|
|
320
400
|
headed: headedFlag,
|
|
321
401
|
config: configOption,
|
|
322
402
|
json: jsonFlag,
|
|
403
|
+
profile: profileOption,
|
|
404
|
+
noSave: noSaveFlag,
|
|
405
|
+
width: widthOption,
|
|
406
|
+
height: heightOption,
|
|
323
407
|
},
|
|
324
408
|
handler: async (args) => {
|
|
325
409
|
const browserOptions = await resolveBrowserOptions({
|
|
@@ -351,13 +435,16 @@ const openCommand = command({
|
|
|
351
435
|
if (args.json) {
|
|
352
436
|
const jsonData =
|
|
353
437
|
typeof response.data === "object" && response.data !== null
|
|
354
|
-
? { ...(response.data as object), sessionId }
|
|
355
|
-
: { data: response.data, sessionId };
|
|
438
|
+
? { ...(response.data as object), sessionId, profile: args.profile }
|
|
439
|
+
: { data: response.data, sessionId, profile: args.profile };
|
|
356
440
|
console.log(JSON.stringify(jsonData, null, 2));
|
|
357
441
|
} else {
|
|
358
442
|
if (args.new && sessionId) {
|
|
359
443
|
console.log(`Session: ${sessionId}`);
|
|
360
444
|
}
|
|
445
|
+
if (args.profile) {
|
|
446
|
+
console.log(`Profile: ${args.profile}`);
|
|
447
|
+
}
|
|
361
448
|
console.log(data.text ?? "Navigated successfully");
|
|
362
449
|
}
|
|
363
450
|
},
|
|
@@ -380,6 +467,7 @@ const actCommand = command({
|
|
|
380
467
|
long: "no-state",
|
|
381
468
|
description: "Don't return state after actions",
|
|
382
469
|
}),
|
|
470
|
+
profile: profileOption,
|
|
383
471
|
},
|
|
384
472
|
handler: async (args) => {
|
|
385
473
|
if (args.actions.length === 0) {
|
|
@@ -494,13 +582,9 @@ const waitCommand = command({
|
|
|
494
582
|
process.exit(1);
|
|
495
583
|
}
|
|
496
584
|
|
|
497
|
-
const client =
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
"Daemon not running. Use 'agent-browser open <url>' first.",
|
|
501
|
-
);
|
|
502
|
-
process.exit(1);
|
|
503
|
-
}
|
|
585
|
+
const client = await ensureDaemon(args.session ?? "default", undefined, {
|
|
586
|
+
createIfMissing: false,
|
|
587
|
+
});
|
|
504
588
|
|
|
505
589
|
const response = await client.wait(condition, { timeoutMs: args.timeout });
|
|
506
590
|
|
|
@@ -527,13 +611,9 @@ const stateCommand = command({
|
|
|
527
611
|
json: jsonFlag,
|
|
528
612
|
},
|
|
529
613
|
handler: async (args) => {
|
|
530
|
-
const client =
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
"Daemon not running. Use 'agent-browser open <url>' first.",
|
|
534
|
-
);
|
|
535
|
-
process.exit(1);
|
|
536
|
-
}
|
|
614
|
+
const client = await ensureDaemon(args.session ?? "default", undefined, {
|
|
615
|
+
createIfMissing: false,
|
|
616
|
+
});
|
|
537
617
|
|
|
538
618
|
const response = await client.state({
|
|
539
619
|
format: args.json ? "json" : "text",
|
|
@@ -571,13 +651,9 @@ const screenshotCommand = command({
|
|
|
571
651
|
}),
|
|
572
652
|
},
|
|
573
653
|
handler: async (args) => {
|
|
574
|
-
const client =
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
"Daemon not running. Use 'agent-browser open <url>' first.",
|
|
578
|
-
);
|
|
579
|
-
process.exit(1);
|
|
580
|
-
}
|
|
654
|
+
const client = await ensureDaemon(args.session ?? "default", undefined, {
|
|
655
|
+
createIfMissing: false,
|
|
656
|
+
});
|
|
581
657
|
|
|
582
658
|
const response = await client.screenshot({
|
|
583
659
|
fullPage: args.fullPage,
|
|
@@ -606,6 +682,40 @@ const screenshotCommand = command({
|
|
|
606
682
|
},
|
|
607
683
|
});
|
|
608
684
|
|
|
685
|
+
// --- resize ---
|
|
686
|
+
const resizeCommand = command({
|
|
687
|
+
name: "resize",
|
|
688
|
+
description: "Resize browser viewport",
|
|
689
|
+
args: {
|
|
690
|
+
width: positional({ type: number, displayName: "width" }),
|
|
691
|
+
height: positional({ type: number, displayName: "height" }),
|
|
692
|
+
session: sessionOption,
|
|
693
|
+
json: jsonFlag,
|
|
694
|
+
},
|
|
695
|
+
handler: async (args) => {
|
|
696
|
+
const client = await ensureDaemon(args.session ?? "default", undefined, {
|
|
697
|
+
createIfMissing: false,
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
const response = await client.command({
|
|
701
|
+
type: "resize",
|
|
702
|
+
width: args.width,
|
|
703
|
+
height: args.height,
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
if (!response.success) {
|
|
707
|
+
console.error("Error:", response.error);
|
|
708
|
+
process.exit(1);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (args.json) {
|
|
712
|
+
console.log(JSON.stringify({ width: args.width, height: args.height }));
|
|
713
|
+
} else {
|
|
714
|
+
console.log(`Viewport resized to ${args.width}x${args.height}`);
|
|
715
|
+
}
|
|
716
|
+
},
|
|
717
|
+
});
|
|
718
|
+
|
|
609
719
|
// --- close ---
|
|
610
720
|
const closeCommand = command({
|
|
611
721
|
name: "close",
|
|
@@ -906,12 +1016,325 @@ const installSkillCommand = command({
|
|
|
906
1016
|
},
|
|
907
1017
|
});
|
|
908
1018
|
|
|
1019
|
+
// ============================================================================
|
|
1020
|
+
// Profile Commands
|
|
1021
|
+
// ============================================================================
|
|
1022
|
+
|
|
1023
|
+
const profileListCommand = command({
|
|
1024
|
+
name: "list",
|
|
1025
|
+
description: "List all available profiles",
|
|
1026
|
+
args: {
|
|
1027
|
+
json: jsonFlag,
|
|
1028
|
+
},
|
|
1029
|
+
handler: async (args) => {
|
|
1030
|
+
const profiles = listProfiles();
|
|
1031
|
+
|
|
1032
|
+
if (args.json) {
|
|
1033
|
+
console.log(JSON.stringify(profiles, null, 2));
|
|
1034
|
+
} else {
|
|
1035
|
+
if (profiles.length === 0) {
|
|
1036
|
+
console.log("No profiles found.");
|
|
1037
|
+
console.log("\nCreate a profile with:");
|
|
1038
|
+
console.log(" agent-browser profile login <name> --url <login-url>");
|
|
1039
|
+
console.log(" agent-browser profile save <name>");
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
console.log(`Profiles (${profiles.length}):\n`);
|
|
1044
|
+
for (const p of profiles) {
|
|
1045
|
+
const scopeLabel =
|
|
1046
|
+
p.scope === "global"
|
|
1047
|
+
? "[global]"
|
|
1048
|
+
: p.scope === "local-private"
|
|
1049
|
+
? "[private]"
|
|
1050
|
+
: "[local]";
|
|
1051
|
+
console.log(` ${p.name} ${scopeLabel}`);
|
|
1052
|
+
if (p.meta?.description) {
|
|
1053
|
+
console.log(` ${p.meta.description}`);
|
|
1054
|
+
}
|
|
1055
|
+
if (p.meta?.origins?.length) {
|
|
1056
|
+
console.log(` Origins: ${p.meta.origins.join(", ")}`);
|
|
1057
|
+
}
|
|
1058
|
+
if (p.meta?.lastUsedAt) {
|
|
1059
|
+
console.log(` Last used: ${p.meta.lastUsedAt}`);
|
|
1060
|
+
}
|
|
1061
|
+
console.log();
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
},
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
const profileShowCommand = command({
|
|
1068
|
+
name: "show",
|
|
1069
|
+
description: "Show profile contents",
|
|
1070
|
+
args: {
|
|
1071
|
+
name: positional({ type: string, displayName: "name" }),
|
|
1072
|
+
json: jsonFlag,
|
|
1073
|
+
},
|
|
1074
|
+
handler: async (args) => {
|
|
1075
|
+
const profile = loadProfile(args.name);
|
|
1076
|
+
if (!profile) {
|
|
1077
|
+
console.error(`Profile not found: ${args.name}`);
|
|
1078
|
+
process.exit(1);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
if (args.json) {
|
|
1082
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
1083
|
+
} else {
|
|
1084
|
+
const resolved = resolveProfilePath(args.name);
|
|
1085
|
+
console.log(`Profile: ${args.name}`);
|
|
1086
|
+
console.log(`Path: ${resolved?.path}`);
|
|
1087
|
+
console.log(`Scope: ${resolved?.scope}`);
|
|
1088
|
+
console.log();
|
|
1089
|
+
if (profile._meta?.description) {
|
|
1090
|
+
console.log(`Description: ${profile._meta.description}`);
|
|
1091
|
+
}
|
|
1092
|
+
if (profile._meta?.origins?.length) {
|
|
1093
|
+
console.log(`Origins: ${profile._meta.origins.join(", ")}`);
|
|
1094
|
+
}
|
|
1095
|
+
if (profile._meta?.createdAt) {
|
|
1096
|
+
console.log(`Created: ${profile._meta.createdAt}`);
|
|
1097
|
+
}
|
|
1098
|
+
if (profile._meta?.lastUsedAt) {
|
|
1099
|
+
console.log(`Last used: ${profile._meta.lastUsedAt}`);
|
|
1100
|
+
}
|
|
1101
|
+
console.log();
|
|
1102
|
+
console.log(`Cookies: ${profile.cookies.length}`);
|
|
1103
|
+
for (const cookie of profile.cookies) {
|
|
1104
|
+
console.log(` - ${cookie.name} (${cookie.domain})`);
|
|
1105
|
+
}
|
|
1106
|
+
console.log();
|
|
1107
|
+
console.log(`LocalStorage origins: ${profile.origins.length}`);
|
|
1108
|
+
for (const origin of profile.origins) {
|
|
1109
|
+
console.log(
|
|
1110
|
+
` - ${origin.origin}: ${origin.localStorage.length} items`,
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
},
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
const profileSaveCommand = command({
|
|
1118
|
+
name: "save",
|
|
1119
|
+
description: "Save current session storage to a profile",
|
|
1120
|
+
args: {
|
|
1121
|
+
name: positional({ type: string, displayName: "name" }),
|
|
1122
|
+
session: sessionOption,
|
|
1123
|
+
global: globalFlag,
|
|
1124
|
+
private: privateFlag,
|
|
1125
|
+
description: option({
|
|
1126
|
+
long: "description",
|
|
1127
|
+
short: "d",
|
|
1128
|
+
type: optional(string),
|
|
1129
|
+
description: "Profile description",
|
|
1130
|
+
}),
|
|
1131
|
+
},
|
|
1132
|
+
handler: async (args) => {
|
|
1133
|
+
const client = await ensureDaemon(args.session ?? "default", undefined, {
|
|
1134
|
+
createIfMissing: false,
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
// Get storage state from session via command
|
|
1138
|
+
const response = await client.command({
|
|
1139
|
+
type: "saveStorageState",
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
if (!response.success) {
|
|
1143
|
+
console.error("Error:", response.error);
|
|
1144
|
+
process.exit(1);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
const storageState = response.data as StorageState;
|
|
1148
|
+
|
|
1149
|
+
// Extract origins from storage state
|
|
1150
|
+
const origins = [
|
|
1151
|
+
...new Set([
|
|
1152
|
+
...storageState.cookies.map((c) => c.domain),
|
|
1153
|
+
...storageState.origins.map((o) => o.origin),
|
|
1154
|
+
]),
|
|
1155
|
+
].filter(Boolean);
|
|
1156
|
+
|
|
1157
|
+
const savedPath = saveProfile(args.name, storageState, {
|
|
1158
|
+
global: args.global,
|
|
1159
|
+
private: args.private,
|
|
1160
|
+
description: args.description,
|
|
1161
|
+
origins,
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
console.log(`Profile saved: ${args.name}`);
|
|
1165
|
+
console.log(`Path: ${savedPath}`);
|
|
1166
|
+
console.log(`Cookies: ${storageState.cookies.length}`);
|
|
1167
|
+
console.log(`LocalStorage origins: ${storageState.origins.length}`);
|
|
1168
|
+
},
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
const profileDeleteCommand = command({
|
|
1172
|
+
name: "delete",
|
|
1173
|
+
description: "Delete a profile",
|
|
1174
|
+
args: {
|
|
1175
|
+
name: positional({ type: string, displayName: "name" }),
|
|
1176
|
+
},
|
|
1177
|
+
handler: async (args) => {
|
|
1178
|
+
const resolved = resolveProfilePath(args.name);
|
|
1179
|
+
if (!resolved) {
|
|
1180
|
+
console.error(`Profile not found: ${args.name}`);
|
|
1181
|
+
process.exit(1);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const deleted = deleteProfile(args.name);
|
|
1185
|
+
if (deleted) {
|
|
1186
|
+
console.log(`Deleted profile: ${args.name}`);
|
|
1187
|
+
console.log(`Path: ${resolved.path}`);
|
|
1188
|
+
} else {
|
|
1189
|
+
console.error(`Failed to delete profile: ${args.name}`);
|
|
1190
|
+
process.exit(1);
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
const profileImportCommand = command({
|
|
1196
|
+
name: "import",
|
|
1197
|
+
description: "Import a profile from a storage state JSON file",
|
|
1198
|
+
args: {
|
|
1199
|
+
name: positional({ type: string, displayName: "name" }),
|
|
1200
|
+
path: positional({ type: string, displayName: "path" }),
|
|
1201
|
+
global: globalFlag,
|
|
1202
|
+
private: privateFlag,
|
|
1203
|
+
},
|
|
1204
|
+
handler: async (args) => {
|
|
1205
|
+
try {
|
|
1206
|
+
const savedPath = importProfile(args.name, args.path, {
|
|
1207
|
+
global: args.global,
|
|
1208
|
+
private: args.private,
|
|
1209
|
+
});
|
|
1210
|
+
console.log(`Imported profile: ${args.name}`);
|
|
1211
|
+
console.log(`Path: ${savedPath}`);
|
|
1212
|
+
} catch (err) {
|
|
1213
|
+
console.error("Error:", err instanceof Error ? err.message : String(err));
|
|
1214
|
+
process.exit(1);
|
|
1215
|
+
}
|
|
1216
|
+
},
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
const profileCaptureCommand = command({
|
|
1220
|
+
name: "capture",
|
|
1221
|
+
description: "Open browser, interact manually, then save session to profile",
|
|
1222
|
+
args: {
|
|
1223
|
+
name: positional({ type: string, displayName: "name" }),
|
|
1224
|
+
url: option({
|
|
1225
|
+
long: "url",
|
|
1226
|
+
type: string,
|
|
1227
|
+
description: "URL to navigate to",
|
|
1228
|
+
}),
|
|
1229
|
+
headed: headedFlag,
|
|
1230
|
+
headless: headlessFlag,
|
|
1231
|
+
config: configOption,
|
|
1232
|
+
global: globalFlag,
|
|
1233
|
+
private: privateFlag,
|
|
1234
|
+
description: option({
|
|
1235
|
+
long: "description",
|
|
1236
|
+
short: "d",
|
|
1237
|
+
type: optional(string),
|
|
1238
|
+
description: "Profile description",
|
|
1239
|
+
}),
|
|
1240
|
+
},
|
|
1241
|
+
handler: async (args) => {
|
|
1242
|
+
console.log(`Capturing session for profile: ${args.name}`);
|
|
1243
|
+
console.log(`URL: ${args.url}`);
|
|
1244
|
+
console.log();
|
|
1245
|
+
console.log("Browser will open. Log in or do whatever you need.");
|
|
1246
|
+
console.log("Press Enter here when done to save and close.");
|
|
1247
|
+
console.log();
|
|
1248
|
+
|
|
1249
|
+
// Force headed mode for interactive login
|
|
1250
|
+
const browserOptions = await resolveBrowserOptions({
|
|
1251
|
+
...args,
|
|
1252
|
+
configPath: args.config,
|
|
1253
|
+
headed: true,
|
|
1254
|
+
headless: false,
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
// Create a new session for login
|
|
1258
|
+
const client = await ensureDaemonNewSession(browserOptions);
|
|
1259
|
+
const sessionId = client.getSessionId();
|
|
1260
|
+
|
|
1261
|
+
// Navigate to login URL
|
|
1262
|
+
const navResponse = await client.act([{ type: "navigate", url: args.url }]);
|
|
1263
|
+
if (!navResponse.success) {
|
|
1264
|
+
console.error("Error navigating:", navResponse.error);
|
|
1265
|
+
await client.closeSession(sessionId!);
|
|
1266
|
+
process.exit(1);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// Wait for user to press Enter
|
|
1270
|
+
process.stdout.write("Press Enter when login is complete...");
|
|
1271
|
+
await new Promise<void>((resolve) => {
|
|
1272
|
+
process.stdin.setRawMode?.(false);
|
|
1273
|
+
process.stdin.resume();
|
|
1274
|
+
process.stdin.once("data", () => {
|
|
1275
|
+
resolve();
|
|
1276
|
+
});
|
|
1277
|
+
});
|
|
1278
|
+
console.log();
|
|
1279
|
+
|
|
1280
|
+
// Save storage state
|
|
1281
|
+
const saveResponse = await client.command({
|
|
1282
|
+
type: "saveStorageState",
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
if (!saveResponse.success) {
|
|
1286
|
+
console.error("Error saving storage state:", saveResponse.error);
|
|
1287
|
+
await client.closeSession(sessionId!);
|
|
1288
|
+
process.exit(1);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
const storageState = saveResponse.data as StorageState;
|
|
1292
|
+
|
|
1293
|
+
// Extract origins from storage state
|
|
1294
|
+
const origins = [
|
|
1295
|
+
...new Set([
|
|
1296
|
+
...storageState.cookies.map((c) => c.domain),
|
|
1297
|
+
...storageState.origins.map((o) => o.origin),
|
|
1298
|
+
]),
|
|
1299
|
+
].filter(Boolean);
|
|
1300
|
+
|
|
1301
|
+
const savedPath = saveProfile(args.name, storageState, {
|
|
1302
|
+
global: args.global,
|
|
1303
|
+
private: args.private,
|
|
1304
|
+
description: args.description,
|
|
1305
|
+
origins,
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
// Close the session
|
|
1309
|
+
await client.closeSession(sessionId!);
|
|
1310
|
+
|
|
1311
|
+
console.log();
|
|
1312
|
+
console.log(`Profile saved: ${args.name}`);
|
|
1313
|
+
console.log(`Path: ${savedPath}`);
|
|
1314
|
+
console.log(`Cookies: ${storageState.cookies.length}`);
|
|
1315
|
+
console.log(`LocalStorage origins: ${storageState.origins.length}`);
|
|
1316
|
+
},
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
const profileCommand = subcommands({
|
|
1320
|
+
name: "profile",
|
|
1321
|
+
cmds: {
|
|
1322
|
+
list: profileListCommand,
|
|
1323
|
+
show: profileShowCommand,
|
|
1324
|
+
save: profileSaveCommand,
|
|
1325
|
+
delete: profileDeleteCommand,
|
|
1326
|
+
import: profileImportCommand,
|
|
1327
|
+
capture: profileCaptureCommand,
|
|
1328
|
+
},
|
|
1329
|
+
});
|
|
1330
|
+
|
|
909
1331
|
// ============================================================================
|
|
910
1332
|
// Main CLI
|
|
911
1333
|
// ============================================================================
|
|
912
1334
|
|
|
913
1335
|
const cli = subcommands({
|
|
914
1336
|
name: "agent-browser",
|
|
1337
|
+
version: VERSION,
|
|
915
1338
|
cmds: {
|
|
916
1339
|
// Primary CLI commands (daemon-based)
|
|
917
1340
|
open: openCommand,
|
|
@@ -919,10 +1342,14 @@ const cli = subcommands({
|
|
|
919
1342
|
wait: waitCommand,
|
|
920
1343
|
state: stateCommand,
|
|
921
1344
|
screenshot: screenshotCommand,
|
|
1345
|
+
resize: resizeCommand,
|
|
922
1346
|
close: closeCommand,
|
|
923
1347
|
sessions: sessionsCommand,
|
|
924
1348
|
status: statusCommand,
|
|
925
1349
|
|
|
1350
|
+
// Profile management
|
|
1351
|
+
profile: profileCommand,
|
|
1352
|
+
|
|
926
1353
|
// Setup & configuration
|
|
927
1354
|
setup: setupCommand,
|
|
928
1355
|
"install-skill": installSkillCommand,
|
|
@@ -934,10 +1361,11 @@ const cli = subcommands({
|
|
|
934
1361
|
});
|
|
935
1362
|
|
|
936
1363
|
run(cli, process.argv.slice(2)).catch((error) => {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
.
|
|
940
|
-
|
|
941
|
-
|
|
1364
|
+
// Print clean error message for user-facing errors
|
|
1365
|
+
if (error instanceof Error) {
|
|
1366
|
+
console.error(`Error: ${error.message}`);
|
|
1367
|
+
} else {
|
|
1368
|
+
console.error(error);
|
|
1369
|
+
}
|
|
942
1370
|
process.exit(1);
|
|
943
1371
|
});
|
package/src/commands.ts
CHANGED
|
@@ -121,6 +121,12 @@ const screenshotCommandSchema = z.object({
|
|
|
121
121
|
path: z.string().optional(),
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
+
const resizeCommandSchema = z.object({
|
|
125
|
+
type: z.literal("resize"),
|
|
126
|
+
width: z.number().int().positive(),
|
|
127
|
+
height: z.number().int().positive(),
|
|
128
|
+
});
|
|
129
|
+
|
|
124
130
|
const saveStorageStateCommandSchema = z.object({
|
|
125
131
|
type: z.literal("saveStorageState"),
|
|
126
132
|
path: z.string().optional(),
|
|
@@ -178,6 +184,7 @@ export const stepActionSchema = z.discriminatedUnion("type", [
|
|
|
178
184
|
waitForElementCommandSchema,
|
|
179
185
|
screenshotCommandSchema,
|
|
180
186
|
saveStorageStateCommandSchema,
|
|
187
|
+
resizeCommandSchema,
|
|
181
188
|
]);
|
|
182
189
|
|
|
183
190
|
// All commands
|
|
@@ -202,6 +209,7 @@ export const commandSchema = z.discriminatedUnion("type", [
|
|
|
202
209
|
clearNetworkLogsCommandSchema,
|
|
203
210
|
enableNetworkCaptureCommandSchema,
|
|
204
211
|
saveStorageStateCommandSchema,
|
|
212
|
+
resizeCommandSchema,
|
|
205
213
|
closeCommandSchema,
|
|
206
214
|
]);
|
|
207
215
|
|
|
@@ -280,6 +288,9 @@ export async function executeCommand(
|
|
|
280
288
|
case "close":
|
|
281
289
|
await browser.stop();
|
|
282
290
|
return;
|
|
291
|
+
case "resize":
|
|
292
|
+
await browser.resize(command.width, command.height);
|
|
293
|
+
return;
|
|
283
294
|
// Commands that return data
|
|
284
295
|
case "getState":
|
|
285
296
|
return browser.getState(command.options);
|