agent-browser-loop 0.2.2 → 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 +39 -17
- package/package.json +1 -1
- package/src/browser.ts +22 -0
- package/src/cli.ts +451 -7
- package/src/commands.ts +11 -0
- package/src/daemon.ts +53 -8
- package/src/index.ts +15 -0
- package/src/profiles.ts +414 -0
- package/src/server.ts +148 -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,18 +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
211
|
allowSystemChromeHeadless: config?.allowSystemChromeHeadless,
|
|
151
|
-
viewportWidth: config?.viewportWidth,
|
|
152
|
-
viewportHeight: config?.viewportHeight,
|
|
212
|
+
viewportWidth: args.width ?? config?.viewportWidth,
|
|
213
|
+
viewportHeight: args.height ?? config?.viewportHeight,
|
|
153
214
|
userDataDir: config?.userDataDir,
|
|
154
215
|
timeout: config?.timeout,
|
|
155
216
|
captureNetwork: config?.captureNetwork,
|
|
156
217
|
networkLogLimit: config?.networkLogLimit,
|
|
157
|
-
|
|
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,
|
|
158
225
|
};
|
|
159
226
|
}
|
|
160
227
|
|
|
@@ -171,6 +238,7 @@ async function resolveBrowserOptions(args: {
|
|
|
171
238
|
* press:Enter
|
|
172
239
|
* scroll:down
|
|
173
240
|
* scroll:down:500
|
|
241
|
+
* resize:1920:1080
|
|
174
242
|
*/
|
|
175
243
|
function parseAction(actionStr: string): StepAction {
|
|
176
244
|
const parts = actionStr.split(":");
|
|
@@ -207,6 +275,15 @@ function parseAction(actionStr: string): StepAction {
|
|
|
207
275
|
return { type: "select", ref, value };
|
|
208
276
|
}
|
|
209
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
|
+
|
|
210
287
|
default:
|
|
211
288
|
throw new Error(`Unknown action type: ${type}`);
|
|
212
289
|
}
|
|
@@ -320,6 +397,10 @@ const openCommand = command({
|
|
|
320
397
|
headed: headedFlag,
|
|
321
398
|
config: configOption,
|
|
322
399
|
json: jsonFlag,
|
|
400
|
+
profile: profileOption,
|
|
401
|
+
noSave: noSaveFlag,
|
|
402
|
+
width: widthOption,
|
|
403
|
+
height: heightOption,
|
|
323
404
|
},
|
|
324
405
|
handler: async (args) => {
|
|
325
406
|
const browserOptions = await resolveBrowserOptions({
|
|
@@ -351,13 +432,16 @@ const openCommand = command({
|
|
|
351
432
|
if (args.json) {
|
|
352
433
|
const jsonData =
|
|
353
434
|
typeof response.data === "object" && response.data !== null
|
|
354
|
-
? { ...(response.data as object), sessionId }
|
|
355
|
-
: { data: response.data, sessionId };
|
|
435
|
+
? { ...(response.data as object), sessionId, profile: args.profile }
|
|
436
|
+
: { data: response.data, sessionId, profile: args.profile };
|
|
356
437
|
console.log(JSON.stringify(jsonData, null, 2));
|
|
357
438
|
} else {
|
|
358
439
|
if (args.new && sessionId) {
|
|
359
440
|
console.log(`Session: ${sessionId}`);
|
|
360
441
|
}
|
|
442
|
+
if (args.profile) {
|
|
443
|
+
console.log(`Profile: ${args.profile}`);
|
|
444
|
+
}
|
|
361
445
|
console.log(data.text ?? "Navigated successfully");
|
|
362
446
|
}
|
|
363
447
|
},
|
|
@@ -380,6 +464,7 @@ const actCommand = command({
|
|
|
380
464
|
long: "no-state",
|
|
381
465
|
description: "Don't return state after actions",
|
|
382
466
|
}),
|
|
467
|
+
profile: profileOption,
|
|
383
468
|
},
|
|
384
469
|
handler: async (args) => {
|
|
385
470
|
if (args.actions.length === 0) {
|
|
@@ -606,6 +691,44 @@ const screenshotCommand = command({
|
|
|
606
691
|
},
|
|
607
692
|
});
|
|
608
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
|
+
|
|
609
732
|
// --- close ---
|
|
610
733
|
const closeCommand = command({
|
|
611
734
|
name: "close",
|
|
@@ -906,6 +1029,323 @@ const installSkillCommand = command({
|
|
|
906
1029
|
},
|
|
907
1030
|
});
|
|
908
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
|
+
|
|
909
1349
|
// ============================================================================
|
|
910
1350
|
// Main CLI
|
|
911
1351
|
// ============================================================================
|
|
@@ -919,10 +1359,14 @@ const cli = subcommands({
|
|
|
919
1359
|
wait: waitCommand,
|
|
920
1360
|
state: stateCommand,
|
|
921
1361
|
screenshot: screenshotCommand,
|
|
1362
|
+
resize: resizeCommand,
|
|
922
1363
|
close: closeCommand,
|
|
923
1364
|
sessions: sessionsCommand,
|
|
924
1365
|
status: statusCommand,
|
|
925
1366
|
|
|
1367
|
+
// Profile management
|
|
1368
|
+
profile: profileCommand,
|
|
1369
|
+
|
|
926
1370
|
// Setup & configuration
|
|
927
1371
|
setup: setupCommand,
|
|
928
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);
|