agent-browser-loop 0.2.1 → 0.3.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/.claude/skills/agent-browser-loop/REFERENCE.md +131 -0
- package/.claude/skills/agent-browser-loop/SKILL.md +36 -4
- package/README.md +41 -17
- package/package.json +1 -1
- package/src/actions.ts +25 -19
- package/src/browser.ts +67 -9
- package/src/cli.ts +461 -9
- package/src/commands.ts +11 -0
- package/src/config.ts +1 -0
- package/src/daemon.ts +57 -26
- package/src/index.ts +18 -0
- package/src/profiles.ts +414 -0
- package/src/ref-store.ts +216 -0
- package/src/server.ts +148 -0
- package/src/state.ts +236 -132
- package/src/types.ts +2 -0
package/src/cli.ts
CHANGED
|
@@ -24,8 +24,17 @@ import {
|
|
|
24
24
|
isDaemonRunning,
|
|
25
25
|
} from "./daemon";
|
|
26
26
|
import { log, withLog } from "./log";
|
|
27
|
+
import {
|
|
28
|
+
deleteProfile,
|
|
29
|
+
importProfile,
|
|
30
|
+
listProfiles,
|
|
31
|
+
loadProfile,
|
|
32
|
+
resolveProfilePath,
|
|
33
|
+
resolveStorageStateOption,
|
|
34
|
+
saveProfile,
|
|
35
|
+
} from "./profiles";
|
|
27
36
|
import { startBrowserServer } from "./server";
|
|
28
|
-
import type { BrowserCliConfig } from "./types";
|
|
37
|
+
import type { BrowserCliConfig, StorageState } from "./types";
|
|
29
38
|
|
|
30
39
|
// ============================================================================
|
|
31
40
|
// Config Loading
|
|
@@ -119,16 +128,62 @@ const jsonFlag = flag({
|
|
|
119
128
|
description: "Output as JSON instead of text",
|
|
120
129
|
});
|
|
121
130
|
|
|
131
|
+
const profileOption = option({
|
|
132
|
+
long: "profile",
|
|
133
|
+
short: "p",
|
|
134
|
+
type: optional(string),
|
|
135
|
+
description:
|
|
136
|
+
"Load profile and save back on close (use --no-save for read-only)",
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const noSaveFlag = flag({
|
|
140
|
+
long: "no-save",
|
|
141
|
+
description: "Don't save profile changes on close (read-only)",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const globalFlag = flag({
|
|
145
|
+
long: "global",
|
|
146
|
+
description: "Save to user-level global profiles (~/.config/agent-browser/)",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const privateFlag = flag({
|
|
150
|
+
long: "private",
|
|
151
|
+
description: "Save to project .private/ (gitignored, for secrets)",
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const widthOption = option({
|
|
155
|
+
long: "width",
|
|
156
|
+
short: "W",
|
|
157
|
+
type: optional(number),
|
|
158
|
+
description: "Viewport width in pixels (default: 1280)",
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const heightOption = option({
|
|
162
|
+
long: "height",
|
|
163
|
+
short: "H",
|
|
164
|
+
type: optional(number),
|
|
165
|
+
description: "Viewport height in pixels (default: 720)",
|
|
166
|
+
});
|
|
167
|
+
|
|
122
168
|
// ============================================================================
|
|
123
169
|
// Browser Options Resolution
|
|
124
170
|
// ============================================================================
|
|
125
171
|
|
|
172
|
+
type SessionBrowserOptions = AgentBrowserOptions & {
|
|
173
|
+
profile?: string;
|
|
174
|
+
noSave?: boolean;
|
|
175
|
+
};
|
|
176
|
+
|
|
126
177
|
async function resolveBrowserOptions(args: {
|
|
127
178
|
configPath?: string;
|
|
128
179
|
headless?: boolean;
|
|
129
180
|
headed?: boolean;
|
|
130
181
|
bundled?: boolean;
|
|
131
|
-
|
|
182
|
+
profile?: string;
|
|
183
|
+
noSave?: boolean;
|
|
184
|
+
width?: number;
|
|
185
|
+
height?: number;
|
|
186
|
+
}): Promise<SessionBrowserOptions> {
|
|
132
187
|
const configPath = await findConfigPath(args.configPath);
|
|
133
188
|
const config = configPath ? await loadConfig(configPath) : undefined;
|
|
134
189
|
|
|
@@ -143,17 +198,30 @@ async function resolveBrowserOptions(args: {
|
|
|
143
198
|
|
|
144
199
|
const useSystemChrome = args.bundled ? false : config?.useSystemChrome;
|
|
145
200
|
|
|
201
|
+
// Resolve storage state from profile or config
|
|
202
|
+
const storageState = resolveStorageStateOption(
|
|
203
|
+
args.profile,
|
|
204
|
+
config?.storageStatePath,
|
|
205
|
+
);
|
|
206
|
+
|
|
146
207
|
return {
|
|
147
208
|
headless,
|
|
148
209
|
executablePath: config?.executablePath,
|
|
149
210
|
useSystemChrome,
|
|
150
|
-
|
|
151
|
-
|
|
211
|
+
allowSystemChromeHeadless: config?.allowSystemChromeHeadless,
|
|
212
|
+
viewportWidth: args.width ?? config?.viewportWidth,
|
|
213
|
+
viewportHeight: args.height ?? config?.viewportHeight,
|
|
152
214
|
userDataDir: config?.userDataDir,
|
|
153
215
|
timeout: config?.timeout,
|
|
154
216
|
captureNetwork: config?.captureNetwork,
|
|
155
217
|
networkLogLimit: config?.networkLogLimit,
|
|
156
|
-
|
|
218
|
+
// Use resolved storage state (object or path)
|
|
219
|
+
storageState: typeof storageState === "object" ? storageState : undefined,
|
|
220
|
+
storageStatePath:
|
|
221
|
+
typeof storageState === "string" ? storageState : undefined,
|
|
222
|
+
// Track profile for save-on-close
|
|
223
|
+
profile: args.profile,
|
|
224
|
+
noSave: args.noSave,
|
|
157
225
|
};
|
|
158
226
|
}
|
|
159
227
|
|
|
@@ -170,6 +238,7 @@ async function resolveBrowserOptions(args: {
|
|
|
170
238
|
* press:Enter
|
|
171
239
|
* scroll:down
|
|
172
240
|
* scroll:down:500
|
|
241
|
+
* resize:1920:1080
|
|
173
242
|
*/
|
|
174
243
|
function parseAction(actionStr: string): StepAction {
|
|
175
244
|
const parts = actionStr.split(":");
|
|
@@ -206,6 +275,15 @@ function parseAction(actionStr: string): StepAction {
|
|
|
206
275
|
return { type: "select", ref, value };
|
|
207
276
|
}
|
|
208
277
|
|
|
278
|
+
case "resize": {
|
|
279
|
+
const width = Number.parseInt(parts[1], 10);
|
|
280
|
+
const height = Number.parseInt(parts[2], 10);
|
|
281
|
+
if (Number.isNaN(width) || Number.isNaN(height)) {
|
|
282
|
+
throw new Error("resize requires width:height (e.g. resize:1920:1080)");
|
|
283
|
+
}
|
|
284
|
+
return { type: "resize", width, height };
|
|
285
|
+
}
|
|
286
|
+
|
|
209
287
|
default:
|
|
210
288
|
throw new Error(`Unknown action type: ${type}`);
|
|
211
289
|
}
|
|
@@ -319,9 +397,16 @@ const openCommand = command({
|
|
|
319
397
|
headed: headedFlag,
|
|
320
398
|
config: configOption,
|
|
321
399
|
json: jsonFlag,
|
|
400
|
+
profile: profileOption,
|
|
401
|
+
noSave: noSaveFlag,
|
|
402
|
+
width: widthOption,
|
|
403
|
+
height: heightOption,
|
|
322
404
|
},
|
|
323
405
|
handler: async (args) => {
|
|
324
|
-
const browserOptions = await resolveBrowserOptions(
|
|
406
|
+
const browserOptions = await resolveBrowserOptions({
|
|
407
|
+
...args,
|
|
408
|
+
configPath: args.config,
|
|
409
|
+
});
|
|
325
410
|
|
|
326
411
|
let client: DaemonClient;
|
|
327
412
|
if (args.new) {
|
|
@@ -347,13 +432,16 @@ const openCommand = command({
|
|
|
347
432
|
if (args.json) {
|
|
348
433
|
const jsonData =
|
|
349
434
|
typeof response.data === "object" && response.data !== null
|
|
350
|
-
? { ...(response.data as object), sessionId }
|
|
351
|
-
: { data: response.data, sessionId };
|
|
435
|
+
? { ...(response.data as object), sessionId, profile: args.profile }
|
|
436
|
+
: { data: response.data, sessionId, profile: args.profile };
|
|
352
437
|
console.log(JSON.stringify(jsonData, null, 2));
|
|
353
438
|
} else {
|
|
354
439
|
if (args.new && sessionId) {
|
|
355
440
|
console.log(`Session: ${sessionId}`);
|
|
356
441
|
}
|
|
442
|
+
if (args.profile) {
|
|
443
|
+
console.log(`Profile: ${args.profile}`);
|
|
444
|
+
}
|
|
357
445
|
console.log(data.text ?? "Navigated successfully");
|
|
358
446
|
}
|
|
359
447
|
},
|
|
@@ -376,6 +464,7 @@ const actCommand = command({
|
|
|
376
464
|
long: "no-state",
|
|
377
465
|
description: "Don't return state after actions",
|
|
378
466
|
}),
|
|
467
|
+
profile: profileOption,
|
|
379
468
|
},
|
|
380
469
|
handler: async (args) => {
|
|
381
470
|
if (args.actions.length === 0) {
|
|
@@ -386,7 +475,10 @@ const actCommand = command({
|
|
|
386
475
|
process.exit(1);
|
|
387
476
|
}
|
|
388
477
|
|
|
389
|
-
const browserOptions = await resolveBrowserOptions(
|
|
478
|
+
const browserOptions = await resolveBrowserOptions({
|
|
479
|
+
...args,
|
|
480
|
+
configPath: args.config,
|
|
481
|
+
});
|
|
390
482
|
|
|
391
483
|
let client: DaemonClient;
|
|
392
484
|
if (args.new) {
|
|
@@ -599,6 +691,44 @@ const screenshotCommand = command({
|
|
|
599
691
|
},
|
|
600
692
|
});
|
|
601
693
|
|
|
694
|
+
// --- resize ---
|
|
695
|
+
const resizeCommand = command({
|
|
696
|
+
name: "resize",
|
|
697
|
+
description: "Resize browser viewport",
|
|
698
|
+
args: {
|
|
699
|
+
width: positional({ type: number, displayName: "width" }),
|
|
700
|
+
height: positional({ type: number, displayName: "height" }),
|
|
701
|
+
session: sessionOption,
|
|
702
|
+
json: jsonFlag,
|
|
703
|
+
},
|
|
704
|
+
handler: async (args) => {
|
|
705
|
+
const client = new DaemonClient(args.session);
|
|
706
|
+
if (!(await client.ping())) {
|
|
707
|
+
console.error(
|
|
708
|
+
"Daemon not running. Use 'agent-browser open <url>' first.",
|
|
709
|
+
);
|
|
710
|
+
process.exit(1);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const response = await client.command({
|
|
714
|
+
type: "resize",
|
|
715
|
+
width: args.width,
|
|
716
|
+
height: args.height,
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
if (!response.success) {
|
|
720
|
+
console.error("Error:", response.error);
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (args.json) {
|
|
725
|
+
console.log(JSON.stringify({ width: args.width, height: args.height }));
|
|
726
|
+
} else {
|
|
727
|
+
console.log(`Viewport resized to ${args.width}x${args.height}`);
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
});
|
|
731
|
+
|
|
602
732
|
// --- close ---
|
|
603
733
|
const closeCommand = command({
|
|
604
734
|
name: "close",
|
|
@@ -833,6 +963,7 @@ const serverCommand = command({
|
|
|
833
963
|
headless,
|
|
834
964
|
executablePath: args.executablePath ?? config?.executablePath,
|
|
835
965
|
useSystemChrome,
|
|
966
|
+
allowSystemChromeHeadless: config?.allowSystemChromeHeadless,
|
|
836
967
|
viewportWidth: args.viewportWidth || config?.viewportWidth,
|
|
837
968
|
viewportHeight: args.viewportHeight || config?.viewportHeight,
|
|
838
969
|
userDataDir: args.userDataDir ?? config?.userDataDir,
|
|
@@ -898,6 +1029,323 @@ const installSkillCommand = command({
|
|
|
898
1029
|
},
|
|
899
1030
|
});
|
|
900
1031
|
|
|
1032
|
+
// ============================================================================
|
|
1033
|
+
// Profile Commands
|
|
1034
|
+
// ============================================================================
|
|
1035
|
+
|
|
1036
|
+
const profileListCommand = command({
|
|
1037
|
+
name: "list",
|
|
1038
|
+
description: "List all available profiles",
|
|
1039
|
+
args: {
|
|
1040
|
+
json: jsonFlag,
|
|
1041
|
+
},
|
|
1042
|
+
handler: async (args) => {
|
|
1043
|
+
const profiles = listProfiles();
|
|
1044
|
+
|
|
1045
|
+
if (args.json) {
|
|
1046
|
+
console.log(JSON.stringify(profiles, null, 2));
|
|
1047
|
+
} else {
|
|
1048
|
+
if (profiles.length === 0) {
|
|
1049
|
+
console.log("No profiles found.");
|
|
1050
|
+
console.log("\nCreate a profile with:");
|
|
1051
|
+
console.log(" agent-browser profile login <name> --url <login-url>");
|
|
1052
|
+
console.log(" agent-browser profile save <name>");
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
console.log(`Profiles (${profiles.length}):\n`);
|
|
1057
|
+
for (const p of profiles) {
|
|
1058
|
+
const scopeLabel =
|
|
1059
|
+
p.scope === "global"
|
|
1060
|
+
? "[global]"
|
|
1061
|
+
: p.scope === "local-private"
|
|
1062
|
+
? "[private]"
|
|
1063
|
+
: "[local]";
|
|
1064
|
+
console.log(` ${p.name} ${scopeLabel}`);
|
|
1065
|
+
if (p.meta?.description) {
|
|
1066
|
+
console.log(` ${p.meta.description}`);
|
|
1067
|
+
}
|
|
1068
|
+
if (p.meta?.origins?.length) {
|
|
1069
|
+
console.log(` Origins: ${p.meta.origins.join(", ")}`);
|
|
1070
|
+
}
|
|
1071
|
+
if (p.meta?.lastUsedAt) {
|
|
1072
|
+
console.log(` Last used: ${p.meta.lastUsedAt}`);
|
|
1073
|
+
}
|
|
1074
|
+
console.log();
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
},
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
const profileShowCommand = command({
|
|
1081
|
+
name: "show",
|
|
1082
|
+
description: "Show profile contents",
|
|
1083
|
+
args: {
|
|
1084
|
+
name: positional({ type: string, displayName: "name" }),
|
|
1085
|
+
json: jsonFlag,
|
|
1086
|
+
},
|
|
1087
|
+
handler: async (args) => {
|
|
1088
|
+
const profile = loadProfile(args.name);
|
|
1089
|
+
if (!profile) {
|
|
1090
|
+
console.error(`Profile not found: ${args.name}`);
|
|
1091
|
+
process.exit(1);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
if (args.json) {
|
|
1095
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
1096
|
+
} else {
|
|
1097
|
+
const resolved = resolveProfilePath(args.name);
|
|
1098
|
+
console.log(`Profile: ${args.name}`);
|
|
1099
|
+
console.log(`Path: ${resolved?.path}`);
|
|
1100
|
+
console.log(`Scope: ${resolved?.scope}`);
|
|
1101
|
+
console.log();
|
|
1102
|
+
if (profile._meta?.description) {
|
|
1103
|
+
console.log(`Description: ${profile._meta.description}`);
|
|
1104
|
+
}
|
|
1105
|
+
if (profile._meta?.origins?.length) {
|
|
1106
|
+
console.log(`Origins: ${profile._meta.origins.join(", ")}`);
|
|
1107
|
+
}
|
|
1108
|
+
if (profile._meta?.createdAt) {
|
|
1109
|
+
console.log(`Created: ${profile._meta.createdAt}`);
|
|
1110
|
+
}
|
|
1111
|
+
if (profile._meta?.lastUsedAt) {
|
|
1112
|
+
console.log(`Last used: ${profile._meta.lastUsedAt}`);
|
|
1113
|
+
}
|
|
1114
|
+
console.log();
|
|
1115
|
+
console.log(`Cookies: ${profile.cookies.length}`);
|
|
1116
|
+
for (const cookie of profile.cookies) {
|
|
1117
|
+
console.log(` - ${cookie.name} (${cookie.domain})`);
|
|
1118
|
+
}
|
|
1119
|
+
console.log();
|
|
1120
|
+
console.log(`LocalStorage origins: ${profile.origins.length}`);
|
|
1121
|
+
for (const origin of profile.origins) {
|
|
1122
|
+
console.log(
|
|
1123
|
+
` - ${origin.origin}: ${origin.localStorage.length} items`,
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
const profileSaveCommand = command({
|
|
1131
|
+
name: "save",
|
|
1132
|
+
description: "Save current session storage to a profile",
|
|
1133
|
+
args: {
|
|
1134
|
+
name: positional({ type: string, displayName: "name" }),
|
|
1135
|
+
session: sessionOption,
|
|
1136
|
+
global: globalFlag,
|
|
1137
|
+
private: privateFlag,
|
|
1138
|
+
description: option({
|
|
1139
|
+
long: "description",
|
|
1140
|
+
short: "d",
|
|
1141
|
+
type: optional(string),
|
|
1142
|
+
description: "Profile description",
|
|
1143
|
+
}),
|
|
1144
|
+
},
|
|
1145
|
+
handler: async (args) => {
|
|
1146
|
+
const client = new DaemonClient(args.session);
|
|
1147
|
+
|
|
1148
|
+
if (!(await client.ping())) {
|
|
1149
|
+
console.error(
|
|
1150
|
+
"Daemon not running. Use 'agent-browser open <url>' first to start a session.",
|
|
1151
|
+
);
|
|
1152
|
+
process.exit(1);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// Get storage state from session via command
|
|
1156
|
+
const response = await client.command({
|
|
1157
|
+
type: "saveStorageState",
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
if (!response.success) {
|
|
1161
|
+
console.error("Error:", response.error);
|
|
1162
|
+
process.exit(1);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
const storageState = response.data as StorageState;
|
|
1166
|
+
|
|
1167
|
+
// Extract origins from storage state
|
|
1168
|
+
const origins = [
|
|
1169
|
+
...new Set([
|
|
1170
|
+
...storageState.cookies.map((c) => c.domain),
|
|
1171
|
+
...storageState.origins.map((o) => o.origin),
|
|
1172
|
+
]),
|
|
1173
|
+
].filter(Boolean);
|
|
1174
|
+
|
|
1175
|
+
const savedPath = saveProfile(args.name, storageState, {
|
|
1176
|
+
global: args.global,
|
|
1177
|
+
private: args.private,
|
|
1178
|
+
description: args.description,
|
|
1179
|
+
origins,
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
console.log(`Profile saved: ${args.name}`);
|
|
1183
|
+
console.log(`Path: ${savedPath}`);
|
|
1184
|
+
console.log(`Cookies: ${storageState.cookies.length}`);
|
|
1185
|
+
console.log(`LocalStorage origins: ${storageState.origins.length}`);
|
|
1186
|
+
},
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
const profileDeleteCommand = command({
|
|
1190
|
+
name: "delete",
|
|
1191
|
+
description: "Delete a profile",
|
|
1192
|
+
args: {
|
|
1193
|
+
name: positional({ type: string, displayName: "name" }),
|
|
1194
|
+
},
|
|
1195
|
+
handler: async (args) => {
|
|
1196
|
+
const resolved = resolveProfilePath(args.name);
|
|
1197
|
+
if (!resolved) {
|
|
1198
|
+
console.error(`Profile not found: ${args.name}`);
|
|
1199
|
+
process.exit(1);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
const deleted = deleteProfile(args.name);
|
|
1203
|
+
if (deleted) {
|
|
1204
|
+
console.log(`Deleted profile: ${args.name}`);
|
|
1205
|
+
console.log(`Path: ${resolved.path}`);
|
|
1206
|
+
} else {
|
|
1207
|
+
console.error(`Failed to delete profile: ${args.name}`);
|
|
1208
|
+
process.exit(1);
|
|
1209
|
+
}
|
|
1210
|
+
},
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
const profileImportCommand = command({
|
|
1214
|
+
name: "import",
|
|
1215
|
+
description: "Import a profile from a storage state JSON file",
|
|
1216
|
+
args: {
|
|
1217
|
+
name: positional({ type: string, displayName: "name" }),
|
|
1218
|
+
path: positional({ type: string, displayName: "path" }),
|
|
1219
|
+
global: globalFlag,
|
|
1220
|
+
private: privateFlag,
|
|
1221
|
+
},
|
|
1222
|
+
handler: async (args) => {
|
|
1223
|
+
try {
|
|
1224
|
+
const savedPath = importProfile(args.name, args.path, {
|
|
1225
|
+
global: args.global,
|
|
1226
|
+
private: args.private,
|
|
1227
|
+
});
|
|
1228
|
+
console.log(`Imported profile: ${args.name}`);
|
|
1229
|
+
console.log(`Path: ${savedPath}`);
|
|
1230
|
+
} catch (err) {
|
|
1231
|
+
console.error("Error:", err instanceof Error ? err.message : String(err));
|
|
1232
|
+
process.exit(1);
|
|
1233
|
+
}
|
|
1234
|
+
},
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
const profileCaptureCommand = command({
|
|
1238
|
+
name: "capture",
|
|
1239
|
+
description: "Open browser, interact manually, then save session to profile",
|
|
1240
|
+
args: {
|
|
1241
|
+
name: positional({ type: string, displayName: "name" }),
|
|
1242
|
+
url: option({
|
|
1243
|
+
long: "url",
|
|
1244
|
+
type: string,
|
|
1245
|
+
description: "URL to navigate to",
|
|
1246
|
+
}),
|
|
1247
|
+
headed: headedFlag,
|
|
1248
|
+
headless: headlessFlag,
|
|
1249
|
+
config: configOption,
|
|
1250
|
+
global: globalFlag,
|
|
1251
|
+
private: privateFlag,
|
|
1252
|
+
description: option({
|
|
1253
|
+
long: "description",
|
|
1254
|
+
short: "d",
|
|
1255
|
+
type: optional(string),
|
|
1256
|
+
description: "Profile description",
|
|
1257
|
+
}),
|
|
1258
|
+
},
|
|
1259
|
+
handler: async (args) => {
|
|
1260
|
+
console.log(`Capturing session for profile: ${args.name}`);
|
|
1261
|
+
console.log(`URL: ${args.url}`);
|
|
1262
|
+
console.log();
|
|
1263
|
+
console.log("Browser will open. Log in or do whatever you need.");
|
|
1264
|
+
console.log("Press Enter here when done to save and close.");
|
|
1265
|
+
console.log();
|
|
1266
|
+
|
|
1267
|
+
// Force headed mode for interactive login
|
|
1268
|
+
const browserOptions = await resolveBrowserOptions({
|
|
1269
|
+
...args,
|
|
1270
|
+
configPath: args.config,
|
|
1271
|
+
headed: true,
|
|
1272
|
+
headless: false,
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
// Create a new session for login
|
|
1276
|
+
const client = await ensureDaemonNewSession(browserOptions);
|
|
1277
|
+
const sessionId = client.getSessionId();
|
|
1278
|
+
|
|
1279
|
+
// Navigate to login URL
|
|
1280
|
+
const navResponse = await client.act([{ type: "navigate", url: args.url }]);
|
|
1281
|
+
if (!navResponse.success) {
|
|
1282
|
+
console.error("Error navigating:", navResponse.error);
|
|
1283
|
+
await client.closeSession(sessionId!);
|
|
1284
|
+
process.exit(1);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// Wait for user to press Enter
|
|
1288
|
+
process.stdout.write("Press Enter when login is complete...");
|
|
1289
|
+
await new Promise<void>((resolve) => {
|
|
1290
|
+
process.stdin.setRawMode?.(false);
|
|
1291
|
+
process.stdin.resume();
|
|
1292
|
+
process.stdin.once("data", () => {
|
|
1293
|
+
resolve();
|
|
1294
|
+
});
|
|
1295
|
+
});
|
|
1296
|
+
console.log();
|
|
1297
|
+
|
|
1298
|
+
// Save storage state
|
|
1299
|
+
const saveResponse = await client.command({
|
|
1300
|
+
type: "saveStorageState",
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
if (!saveResponse.success) {
|
|
1304
|
+
console.error("Error saving storage state:", saveResponse.error);
|
|
1305
|
+
await client.closeSession(sessionId!);
|
|
1306
|
+
process.exit(1);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
const storageState = saveResponse.data as StorageState;
|
|
1310
|
+
|
|
1311
|
+
// Extract origins from storage state
|
|
1312
|
+
const origins = [
|
|
1313
|
+
...new Set([
|
|
1314
|
+
...storageState.cookies.map((c) => c.domain),
|
|
1315
|
+
...storageState.origins.map((o) => o.origin),
|
|
1316
|
+
]),
|
|
1317
|
+
].filter(Boolean);
|
|
1318
|
+
|
|
1319
|
+
const savedPath = saveProfile(args.name, storageState, {
|
|
1320
|
+
global: args.global,
|
|
1321
|
+
private: args.private,
|
|
1322
|
+
description: args.description,
|
|
1323
|
+
origins,
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
// Close the session
|
|
1327
|
+
await client.closeSession(sessionId!);
|
|
1328
|
+
|
|
1329
|
+
console.log();
|
|
1330
|
+
console.log(`Profile saved: ${args.name}`);
|
|
1331
|
+
console.log(`Path: ${savedPath}`);
|
|
1332
|
+
console.log(`Cookies: ${storageState.cookies.length}`);
|
|
1333
|
+
console.log(`LocalStorage origins: ${storageState.origins.length}`);
|
|
1334
|
+
},
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
const profileCommand = subcommands({
|
|
1338
|
+
name: "profile",
|
|
1339
|
+
cmds: {
|
|
1340
|
+
list: profileListCommand,
|
|
1341
|
+
show: profileShowCommand,
|
|
1342
|
+
save: profileSaveCommand,
|
|
1343
|
+
delete: profileDeleteCommand,
|
|
1344
|
+
import: profileImportCommand,
|
|
1345
|
+
capture: profileCaptureCommand,
|
|
1346
|
+
},
|
|
1347
|
+
});
|
|
1348
|
+
|
|
901
1349
|
// ============================================================================
|
|
902
1350
|
// Main CLI
|
|
903
1351
|
// ============================================================================
|
|
@@ -911,10 +1359,14 @@ const cli = subcommands({
|
|
|
911
1359
|
wait: waitCommand,
|
|
912
1360
|
state: stateCommand,
|
|
913
1361
|
screenshot: screenshotCommand,
|
|
1362
|
+
resize: resizeCommand,
|
|
914
1363
|
close: closeCommand,
|
|
915
1364
|
sessions: sessionsCommand,
|
|
916
1365
|
status: statusCommand,
|
|
917
1366
|
|
|
1367
|
+
// Profile management
|
|
1368
|
+
profile: profileCommand,
|
|
1369
|
+
|
|
918
1370
|
// Setup & configuration
|
|
919
1371
|
setup: setupCommand,
|
|
920
1372
|
"install-skill": installSkillCommand,
|
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);
|
package/src/config.ts
CHANGED
|
@@ -32,6 +32,7 @@ export const browserCliConfigSchema = z.looseObject({
|
|
|
32
32
|
headless: z.boolean().optional(),
|
|
33
33
|
executablePath: z.string().optional(),
|
|
34
34
|
useSystemChrome: z.boolean().optional(),
|
|
35
|
+
allowSystemChromeHeadless: z.boolean().optional(),
|
|
35
36
|
viewportWidth: z.number().int().optional(),
|
|
36
37
|
viewportHeight: z.number().int().optional(),
|
|
37
38
|
userDataDir: z.string().optional(),
|