hyper-agent-browser 0.1.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/LICENSE +21 -0
- package/README.md +196 -0
- package/package.json +63 -0
- package/src/browser/context.ts +66 -0
- package/src/browser/manager.ts +414 -0
- package/src/browser/sync-chrome-data.ts +53 -0
- package/src/cli.ts +628 -0
- package/src/cli.ts.backup +529 -0
- package/src/commands/actions.ts +232 -0
- package/src/commands/advanced.ts +252 -0
- package/src/commands/config.ts +44 -0
- package/src/commands/getters.ts +110 -0
- package/src/commands/info.ts +195 -0
- package/src/commands/navigation.ts +50 -0
- package/src/commands/session.ts +83 -0
- package/src/daemon/browser-pool.ts +65 -0
- package/src/daemon/client.ts +128 -0
- package/src/daemon/main.ts +200 -0
- package/src/daemon/server.ts +562 -0
- package/src/session/manager.ts +110 -0
- package/src/session/store.ts +172 -0
- package/src/snapshot/accessibility.ts +182 -0
- package/src/snapshot/dom-extractor.ts +220 -0
- package/src/snapshot/formatter.ts +115 -0
- package/src/snapshot/reference-store.ts +97 -0
- package/src/utils/config.ts +183 -0
- package/src/utils/errors.ts +121 -0
- package/src/utils/logger.ts +12 -0
- package/src/utils/selector.ts +23 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import * as configCommands from "./commands/config";
|
|
4
|
+
import { DaemonClient } from "./daemon/client";
|
|
5
|
+
import type { CommandRequest } from "./daemon/server";
|
|
6
|
+
import { getExitCode } from "./utils/errors";
|
|
7
|
+
|
|
8
|
+
const program = new Command();
|
|
9
|
+
const daemonClient = new DaemonClient();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name("hab")
|
|
13
|
+
.description("hyper-agent-browser - Browser automation CLI for AI Agents")
|
|
14
|
+
.version("0.2.0");
|
|
15
|
+
|
|
16
|
+
// Global options
|
|
17
|
+
program
|
|
18
|
+
.option("-s, --session <name>", "Session name", "default")
|
|
19
|
+
.option("-H, --headed", "Show browser window", false)
|
|
20
|
+
.option("-c, --channel <browser>", "Browser channel (chrome/msedge/chromium)", "chrome")
|
|
21
|
+
.option("-t, --timeout <ms>", "Timeout in milliseconds", "30000")
|
|
22
|
+
.option("-v, --verbose", "Verbose output", false);
|
|
23
|
+
|
|
24
|
+
// Helper to execute command via daemon
|
|
25
|
+
async function executeViaDaemon(command: string, args: Record<string, any>, parentCommand: any) {
|
|
26
|
+
const request: CommandRequest = {
|
|
27
|
+
command,
|
|
28
|
+
session: getSessionName(parentCommand),
|
|
29
|
+
args,
|
|
30
|
+
options: {
|
|
31
|
+
headed: parentCommand.opts().headed || false,
|
|
32
|
+
timeout: getTimeout(parentCommand),
|
|
33
|
+
channel: getChannel(parentCommand),
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const response = await daemonClient.execute(request);
|
|
39
|
+
|
|
40
|
+
if (!response.success) {
|
|
41
|
+
console.error("Error:", response.error);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return response.data;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Navigation commands
|
|
53
|
+
program
|
|
54
|
+
.command("open <url>")
|
|
55
|
+
.description("Open a URL")
|
|
56
|
+
.option("--wait-until <state>", "Wait until state (load/domcontentloaded/networkidle)", "load")
|
|
57
|
+
.action(async (url, options, command) => {
|
|
58
|
+
const result = await executeViaDaemon(
|
|
59
|
+
"open",
|
|
60
|
+
{ url, waitUntil: options.waitUntil },
|
|
61
|
+
command.parent,
|
|
62
|
+
);
|
|
63
|
+
console.log(result);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
program
|
|
67
|
+
.command("reload")
|
|
68
|
+
.description("Reload current page")
|
|
69
|
+
.action(async (_options, command) => {
|
|
70
|
+
const result = await executeViaDaemon("reload", {}, command.parent);
|
|
71
|
+
console.log(result);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
program
|
|
75
|
+
.command("back")
|
|
76
|
+
.description("Go back in history")
|
|
77
|
+
.action(async (_options, command) => {
|
|
78
|
+
const result = await executeViaDaemon("back", {}, command.parent);
|
|
79
|
+
console.log(result);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
program
|
|
83
|
+
.command("forward")
|
|
84
|
+
.description("Go forward in history")
|
|
85
|
+
.action(async (_options, command) => {
|
|
86
|
+
const result = await executeViaDaemon("forward", {}, command.parent);
|
|
87
|
+
console.log(result);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Action commands
|
|
91
|
+
program
|
|
92
|
+
.command("click <selector>")
|
|
93
|
+
.description("Click an element")
|
|
94
|
+
.action(async (selector, _options, command) => {
|
|
95
|
+
const result = await executeViaDaemon("click", { selector }, command.parent);
|
|
96
|
+
console.log(result);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
program
|
|
100
|
+
.command("fill <selector> <value>")
|
|
101
|
+
.description("Fill an input field")
|
|
102
|
+
.action(async (selector, value, _options, command) => {
|
|
103
|
+
const result = await executeViaDaemon("fill", { selector, value }, command.parent);
|
|
104
|
+
console.log(result);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
program
|
|
108
|
+
.command("type <selector> <text>")
|
|
109
|
+
.description("Type text into an element")
|
|
110
|
+
.option("--delay <ms>", "Delay between keystrokes", "0")
|
|
111
|
+
.action(async (selector, text, options, command) => {
|
|
112
|
+
const result = await executeViaDaemon(
|
|
113
|
+
"type",
|
|
114
|
+
{ selector, text, delay: Number.parseInt(options.delay) },
|
|
115
|
+
command.parent,
|
|
116
|
+
);
|
|
117
|
+
console.log(result);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
program
|
|
121
|
+
.command("press <key>")
|
|
122
|
+
.description("Press a key")
|
|
123
|
+
.action(async (key, _options, command) => {
|
|
124
|
+
const result = await executeViaDaemon("press", { key }, command.parent);
|
|
125
|
+
console.log(result);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
program
|
|
129
|
+
.command("scroll <direction>")
|
|
130
|
+
.description("Scroll page (up/down/left/right)")
|
|
131
|
+
.option("--amount <pixels>", "Scroll amount in pixels", "500")
|
|
132
|
+
.option("--selector <selector>", "Scroll within element")
|
|
133
|
+
.action(async (direction, options, command) => {
|
|
134
|
+
const result = await executeViaDaemon(
|
|
135
|
+
"scroll",
|
|
136
|
+
{
|
|
137
|
+
direction,
|
|
138
|
+
amount: Number.parseInt(options.amount),
|
|
139
|
+
selector: options.selector,
|
|
140
|
+
},
|
|
141
|
+
command.parent,
|
|
142
|
+
);
|
|
143
|
+
console.log(result);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
program
|
|
147
|
+
.command("hover <selector>")
|
|
148
|
+
.description("Hover over an element")
|
|
149
|
+
.action(async (selector, _options, command) => {
|
|
150
|
+
const result = await executeViaDaemon("hover", { selector }, command.parent);
|
|
151
|
+
console.log(result);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
program
|
|
155
|
+
.command("select <selector> <value>")
|
|
156
|
+
.description("Select option from dropdown")
|
|
157
|
+
.action(async (selector, value, _options, command) => {
|
|
158
|
+
const result = await executeViaDaemon("select", { selector, value }, command.parent);
|
|
159
|
+
console.log(result);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
program
|
|
163
|
+
.command("wait [condition]")
|
|
164
|
+
.description("Wait for condition (ms/selector=/hidden=/navigation)")
|
|
165
|
+
.option("--timeout <ms>", "Timeout in milliseconds")
|
|
166
|
+
.option("--text <text>", "Wait for text to appear")
|
|
167
|
+
.option("--url <pattern>", "Wait for URL pattern")
|
|
168
|
+
.option("--fn <function>", "Wait for JavaScript condition")
|
|
169
|
+
.option("--load <state>", "Wait for load state (load/domcontentloaded/networkidle)")
|
|
170
|
+
.action(async (condition, options, command) => {
|
|
171
|
+
const result = await executeViaDaemon(
|
|
172
|
+
"wait",
|
|
173
|
+
{
|
|
174
|
+
condition: condition || "",
|
|
175
|
+
timeout: options.timeout,
|
|
176
|
+
text: options.text,
|
|
177
|
+
url: options.url,
|
|
178
|
+
fn: options.fn,
|
|
179
|
+
load: options.load,
|
|
180
|
+
},
|
|
181
|
+
command.parent,
|
|
182
|
+
);
|
|
183
|
+
console.log(result);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// 第一批新增 Action 命令
|
|
187
|
+
program
|
|
188
|
+
.command("check <selector>")
|
|
189
|
+
.description("Check checkbox")
|
|
190
|
+
.action(async (selector, _options, command) => {
|
|
191
|
+
const result = await executeViaDaemon("check", { selector }, command.parent);
|
|
192
|
+
console.log(result);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
program
|
|
196
|
+
.command("uncheck <selector>")
|
|
197
|
+
.description("Uncheck checkbox")
|
|
198
|
+
.action(async (selector, _options, command) => {
|
|
199
|
+
const result = await executeViaDaemon("uncheck", { selector }, command.parent);
|
|
200
|
+
console.log(result);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
program
|
|
204
|
+
.command("dblclick <selector>")
|
|
205
|
+
.description("Double-click element")
|
|
206
|
+
.action(async (selector, _options, command) => {
|
|
207
|
+
const result = await executeViaDaemon("dblclick", { selector }, command.parent);
|
|
208
|
+
console.log(result);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
program
|
|
212
|
+
.command("focus <selector>")
|
|
213
|
+
.description("Focus element")
|
|
214
|
+
.action(async (selector, _options, command) => {
|
|
215
|
+
const result = await executeViaDaemon("focus", { selector }, command.parent);
|
|
216
|
+
console.log(result);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
program
|
|
220
|
+
.command("upload <selector> <files...>")
|
|
221
|
+
.description("Upload files")
|
|
222
|
+
.action(async (selector, files, _options, command) => {
|
|
223
|
+
const result = await executeViaDaemon("upload", { selector, files }, command.parent);
|
|
224
|
+
console.log(result);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
program
|
|
228
|
+
.command("scrollintoview <selector>")
|
|
229
|
+
.description("Scroll element into view")
|
|
230
|
+
.action(async (selector, _options, command) => {
|
|
231
|
+
const result = await executeViaDaemon("scrollintoview", { selector }, command.parent);
|
|
232
|
+
console.log(result);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
program
|
|
236
|
+
.command("drag <source> <target>")
|
|
237
|
+
.description("Drag and drop")
|
|
238
|
+
.action(async (source, target, _options, command) => {
|
|
239
|
+
const result = await executeViaDaemon("drag", { source, target }, command.parent);
|
|
240
|
+
console.log(result);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Get 系列命令
|
|
244
|
+
program
|
|
245
|
+
.command("get <subcommand> <selector> [attribute]")
|
|
246
|
+
.description("Get element info (text/value/attr/html/count/box)")
|
|
247
|
+
.action(async (subcommand, selector, attribute, _options, command) => {
|
|
248
|
+
const result = await executeViaDaemon(
|
|
249
|
+
"get",
|
|
250
|
+
{ subcommand, selector, attribute },
|
|
251
|
+
command.parent,
|
|
252
|
+
);
|
|
253
|
+
console.log(result);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Is 系列命令
|
|
257
|
+
program
|
|
258
|
+
.command("is <subcommand> <selector>")
|
|
259
|
+
.description("Check element state (visible/enabled/checked/editable/hidden)")
|
|
260
|
+
.action(async (subcommand, selector, _options, command) => {
|
|
261
|
+
const result = await executeViaDaemon("is", { subcommand, selector }, command.parent);
|
|
262
|
+
console.log(result);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// 第二批新增高级命令
|
|
266
|
+
|
|
267
|
+
// Dialog 命令
|
|
268
|
+
program
|
|
269
|
+
.command("dialog <action> [text]")
|
|
270
|
+
.description("Handle dialogs (accept/dismiss)")
|
|
271
|
+
.action(async (action, text, _options, command) => {
|
|
272
|
+
const result = await executeViaDaemon("dialog", { action, text }, command.parent);
|
|
273
|
+
console.log(result);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Cookie 命令
|
|
277
|
+
program
|
|
278
|
+
.command("cookies [action] [name] [value]")
|
|
279
|
+
.description("Manage cookies (get/set/clear)")
|
|
280
|
+
.action(async (action, name, value, _options, command) => {
|
|
281
|
+
const result = await executeViaDaemon("cookies", { action, name, value }, command.parent);
|
|
282
|
+
if (typeof result === "object") {
|
|
283
|
+
console.log(JSON.stringify(result, null, 2));
|
|
284
|
+
} else {
|
|
285
|
+
console.log(result);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Storage 命令
|
|
290
|
+
program
|
|
291
|
+
.command("storage <type> [action] [key] [value]")
|
|
292
|
+
.description("Manage storage (local/session)")
|
|
293
|
+
.action(async (type, action, key, value, _options, command) => {
|
|
294
|
+
const result = await executeViaDaemon(
|
|
295
|
+
"storage",
|
|
296
|
+
{
|
|
297
|
+
storageType: type,
|
|
298
|
+
action,
|
|
299
|
+
key,
|
|
300
|
+
value,
|
|
301
|
+
},
|
|
302
|
+
command.parent,
|
|
303
|
+
);
|
|
304
|
+
if (typeof result === "object") {
|
|
305
|
+
console.log(JSON.stringify(result, null, 2));
|
|
306
|
+
} else {
|
|
307
|
+
console.log(result);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// PDF 导出
|
|
312
|
+
program
|
|
313
|
+
.command("pdf <path>")
|
|
314
|
+
.description("Save page as PDF")
|
|
315
|
+
.option("--format <format>", "Paper format (A4/Letter/etc)", "A4")
|
|
316
|
+
.option("--landscape", "Landscape orientation")
|
|
317
|
+
.option("--print-background", "Print background graphics", true)
|
|
318
|
+
.action(async (path, options, command) => {
|
|
319
|
+
const result = await executeViaDaemon(
|
|
320
|
+
"pdf",
|
|
321
|
+
{
|
|
322
|
+
path,
|
|
323
|
+
format: options.format,
|
|
324
|
+
landscape: options.landscape,
|
|
325
|
+
printBackground: options.printBackground,
|
|
326
|
+
},
|
|
327
|
+
command.parent,
|
|
328
|
+
);
|
|
329
|
+
console.log(result);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Set 系列命令
|
|
333
|
+
program
|
|
334
|
+
.command("set <subcommand> [args...]")
|
|
335
|
+
.description("Set browser settings (viewport/geo/offline/headers/media)")
|
|
336
|
+
.action(async (subcommand, args, _options, command) => {
|
|
337
|
+
const params: any = { subcommand };
|
|
338
|
+
|
|
339
|
+
switch (subcommand) {
|
|
340
|
+
case "viewport":
|
|
341
|
+
params.width = Number.parseInt(args[0]);
|
|
342
|
+
params.height = Number.parseInt(args[1]);
|
|
343
|
+
break;
|
|
344
|
+
case "geo":
|
|
345
|
+
params.latitude = Number.parseFloat(args[0]);
|
|
346
|
+
params.longitude = Number.parseFloat(args[1]);
|
|
347
|
+
break;
|
|
348
|
+
case "offline":
|
|
349
|
+
params.enabled = args[0] === "on" || args[0] === "true";
|
|
350
|
+
break;
|
|
351
|
+
case "headers":
|
|
352
|
+
params.headers = JSON.parse(args[0]);
|
|
353
|
+
break;
|
|
354
|
+
case "media":
|
|
355
|
+
params.scheme = args[0];
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const result = await executeViaDaemon("set", params, command.parent);
|
|
360
|
+
console.log(result);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Mouse 命令
|
|
364
|
+
program
|
|
365
|
+
.command("mouse <action> [args...]")
|
|
366
|
+
.description("Mouse control (move/down/up/wheel)")
|
|
367
|
+
.action(async (action, args, _options, command) => {
|
|
368
|
+
const params: any = { action };
|
|
369
|
+
|
|
370
|
+
switch (action) {
|
|
371
|
+
case "move":
|
|
372
|
+
params.x = Number.parseInt(args[0]);
|
|
373
|
+
params.y = Number.parseInt(args[1]);
|
|
374
|
+
break;
|
|
375
|
+
case "down":
|
|
376
|
+
case "up":
|
|
377
|
+
params.button = args[0] || "left";
|
|
378
|
+
break;
|
|
379
|
+
case "wheel":
|
|
380
|
+
params.deltaY = Number.parseInt(args[0]);
|
|
381
|
+
params.deltaX = args[1] ? Number.parseInt(args[1]) : 0;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const result = await executeViaDaemon("mouse", params, command.parent);
|
|
386
|
+
console.log(result);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Keyboard 命令
|
|
390
|
+
program
|
|
391
|
+
.command("keydown <key>")
|
|
392
|
+
.description("Press and hold key")
|
|
393
|
+
.action(async (key, _options, command) => {
|
|
394
|
+
const result = await executeViaDaemon("keydown", { key }, command.parent);
|
|
395
|
+
console.log(result);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
program
|
|
399
|
+
.command("keyup <key>")
|
|
400
|
+
.description("Release key")
|
|
401
|
+
.action(async (key, _options, command) => {
|
|
402
|
+
const result = await executeViaDaemon("keyup", { key }, command.parent);
|
|
403
|
+
console.log(result);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Debug 命令
|
|
407
|
+
program
|
|
408
|
+
.command("console [action]")
|
|
409
|
+
.description("View/clear console logs")
|
|
410
|
+
.action(async (action, _options, command) => {
|
|
411
|
+
const result = await executeViaDaemon("console", { action }, command.parent);
|
|
412
|
+
if (Array.isArray(result)) {
|
|
413
|
+
console.log(result.join("\n"));
|
|
414
|
+
} else {
|
|
415
|
+
console.log(result);
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
program
|
|
420
|
+
.command("errors [action]")
|
|
421
|
+
.description("View/clear page errors")
|
|
422
|
+
.action(async (action, _options, command) => {
|
|
423
|
+
const result = await executeViaDaemon("errors", { action }, command.parent);
|
|
424
|
+
if (Array.isArray(result)) {
|
|
425
|
+
console.log(result.join("\n"));
|
|
426
|
+
} else {
|
|
427
|
+
console.log(result);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
program
|
|
432
|
+
.command("highlight <selector>")
|
|
433
|
+
.description("Highlight element on page")
|
|
434
|
+
.action(async (selector, _options, command) => {
|
|
435
|
+
const result = await executeViaDaemon("highlight", { selector }, command.parent);
|
|
436
|
+
console.log(result);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// Info commands
|
|
440
|
+
program
|
|
441
|
+
.command("snapshot")
|
|
442
|
+
.description("Get page snapshot")
|
|
443
|
+
.option("-i, --interactive", "Show only interactive elements")
|
|
444
|
+
.option("-f, --full", "Show all elements")
|
|
445
|
+
.option("-r, --raw", "Output raw JSON")
|
|
446
|
+
.option("-o, --output <file>", "Output to file")
|
|
447
|
+
.action(async (options, command) => {
|
|
448
|
+
const result = await executeViaDaemon(
|
|
449
|
+
"snapshot",
|
|
450
|
+
{
|
|
451
|
+
interactive: options.interactive,
|
|
452
|
+
full: options.full,
|
|
453
|
+
raw: options.raw,
|
|
454
|
+
},
|
|
455
|
+
command.parent,
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
if (options.output) {
|
|
459
|
+
await Bun.write(options.output, result);
|
|
460
|
+
console.log(`Snapshot saved to: ${options.output}`);
|
|
461
|
+
} else {
|
|
462
|
+
console.log(result);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
program
|
|
467
|
+
.command("screenshot")
|
|
468
|
+
.description("Take a screenshot")
|
|
469
|
+
.option("-o, --output <file>", "Output file", "screenshot.png")
|
|
470
|
+
.option("--full-page", "Capture full page")
|
|
471
|
+
.option("--selector <selector>", "Capture specific element")
|
|
472
|
+
.action(async (options, command) => {
|
|
473
|
+
const result = await executeViaDaemon(
|
|
474
|
+
"screenshot",
|
|
475
|
+
{
|
|
476
|
+
output: options.output,
|
|
477
|
+
fullPage: options.fullPage,
|
|
478
|
+
selector: options.selector,
|
|
479
|
+
},
|
|
480
|
+
command.parent,
|
|
481
|
+
);
|
|
482
|
+
console.log(result);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
program
|
|
486
|
+
.command("evaluate <script>")
|
|
487
|
+
.description("Execute JavaScript in page context")
|
|
488
|
+
.action(async (script, _options, command) => {
|
|
489
|
+
const result = await executeViaDaemon("evaluate", { script }, command.parent);
|
|
490
|
+
console.log(result);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
program
|
|
494
|
+
.command("url")
|
|
495
|
+
.description("Get current URL")
|
|
496
|
+
.action(async (_options, command) => {
|
|
497
|
+
const result = await executeViaDaemon("url", {}, command.parent);
|
|
498
|
+
console.log(result);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
program
|
|
502
|
+
.command("title")
|
|
503
|
+
.description("Get page title")
|
|
504
|
+
.action(async (_options, command) => {
|
|
505
|
+
const result = await executeViaDaemon("title", {}, command.parent);
|
|
506
|
+
console.log(result);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// Session commands
|
|
510
|
+
program
|
|
511
|
+
.command("sessions")
|
|
512
|
+
.description("List all sessions")
|
|
513
|
+
.action(async () => {
|
|
514
|
+
try {
|
|
515
|
+
const sessions = await daemonClient.listSessions();
|
|
516
|
+
console.log("Active Sessions:");
|
|
517
|
+
if (sessions.length === 0) {
|
|
518
|
+
console.log(" No active sessions");
|
|
519
|
+
} else {
|
|
520
|
+
for (const session of sessions) {
|
|
521
|
+
console.log(` - ${session.name} (${session.status})`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
} catch (error) {
|
|
525
|
+
handleError(error);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
program
|
|
530
|
+
.command("close")
|
|
531
|
+
.description("Close current session")
|
|
532
|
+
.action(async (_options, command) => {
|
|
533
|
+
try {
|
|
534
|
+
const sessionName = getSessionName(command.parent);
|
|
535
|
+
const closed = await daemonClient.closeSession(sessionName);
|
|
536
|
+
|
|
537
|
+
if (closed) {
|
|
538
|
+
console.log(`Session closed: ${sessionName}`);
|
|
539
|
+
} else {
|
|
540
|
+
console.log(`Session not found: ${sessionName}`);
|
|
541
|
+
}
|
|
542
|
+
} catch (error) {
|
|
543
|
+
handleError(error);
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// Daemon management commands
|
|
548
|
+
program
|
|
549
|
+
.command("daemon <action>")
|
|
550
|
+
.description("Manage daemon (start/stop/status/restart)")
|
|
551
|
+
.action(async (action) => {
|
|
552
|
+
try {
|
|
553
|
+
const { spawnSync } = await import("node:child_process");
|
|
554
|
+
const { join } = await import("node:path");
|
|
555
|
+
|
|
556
|
+
// Get the project root directory
|
|
557
|
+
const projectRoot = join(import.meta.dir, "..");
|
|
558
|
+
const daemonScript = join(projectRoot, "src/daemon/main.ts");
|
|
559
|
+
|
|
560
|
+
const result = spawnSync("bun", [daemonScript, action], {
|
|
561
|
+
stdio: "inherit",
|
|
562
|
+
cwd: projectRoot,
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
process.exit(result.status || 0);
|
|
566
|
+
} catch (error) {
|
|
567
|
+
console.error("Failed to execute daemon command:", error);
|
|
568
|
+
process.exit(1);
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// Config commands
|
|
573
|
+
program
|
|
574
|
+
.command("config <action> [key] [value]")
|
|
575
|
+
.description("Manage configuration (list/get/set)")
|
|
576
|
+
.action(async (action, key, value) => {
|
|
577
|
+
try {
|
|
578
|
+
if (action === "list") {
|
|
579
|
+
const result = await configCommands.listConfig();
|
|
580
|
+
console.log(result);
|
|
581
|
+
} else if (action === "get" && key) {
|
|
582
|
+
const result = await configCommands.getConfig(key);
|
|
583
|
+
console.log(result);
|
|
584
|
+
} else if (action === "set" && key && value) {
|
|
585
|
+
const result = await configCommands.setConfig(key, value);
|
|
586
|
+
console.log(result);
|
|
587
|
+
} else {
|
|
588
|
+
throw new Error(`Unknown config action: ${action}`);
|
|
589
|
+
}
|
|
590
|
+
} catch (error) {
|
|
591
|
+
handleError(error);
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// Version command
|
|
596
|
+
program
|
|
597
|
+
.command("version")
|
|
598
|
+
.description("Show version information")
|
|
599
|
+
.action(() => {
|
|
600
|
+
console.log("hyper-agent-browser v0.2.0 (with daemon architecture)");
|
|
601
|
+
console.log(`Bun v${Bun.version}`);
|
|
602
|
+
console.log("Patchright v1.55.1");
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
// Helper functions
|
|
606
|
+
function getSessionName(parentCommand: any): string {
|
|
607
|
+
return parentCommand.opts().session || "default";
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function getChannel(parentCommand: any): "chrome" | "msedge" | "chromium" {
|
|
611
|
+
return parentCommand.opts().channel || "chrome";
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function getTimeout(parentCommand: any): number {
|
|
615
|
+
return Number.parseInt(parentCommand.opts().timeout || "30000");
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function handleError(error: any) {
|
|
619
|
+
if (error instanceof Error) {
|
|
620
|
+
console.error("Error:", error.message);
|
|
621
|
+
process.exit(getExitCode(error));
|
|
622
|
+
} else {
|
|
623
|
+
console.error("Error:", error);
|
|
624
|
+
process.exit(1);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
program.parse();
|