veil-browser 0.4.0 ā 0.4.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/README.md +70 -0
- package/dist/browser.d.ts +21 -3
- package/dist/browser.js +314 -54
- package/dist/index.js +102 -41
- package/dist/mcp.d.ts +13 -1
- package/dist/mcp.js +537 -0
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -1,14 +1,48 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import { ensureBrowser, closeBrowser, getPage } from './browser.js';
|
|
4
|
+
import { ensureBrowser, closeBrowser, getPage, humanDelay } from './browser.js';
|
|
5
5
|
import { saveSession } from './session.js';
|
|
6
6
|
import { getPlatform, listPlatforms, searchPlatforms } from './platforms.js';
|
|
7
|
+
import { startMcpServer } from './mcp.js';
|
|
7
8
|
const program = new Command();
|
|
8
9
|
program
|
|
9
10
|
.name('veil')
|
|
10
11
|
.description('š¶ļø OpenClaw browser remote ā stealth headless browser')
|
|
11
|
-
.version('0.4.
|
|
12
|
+
.version('0.4.1');
|
|
13
|
+
/**
|
|
14
|
+
* Add shared browser-selection options to a command that can create a browser.
|
|
15
|
+
*/
|
|
16
|
+
function addBrowserOptions(command) {
|
|
17
|
+
return command
|
|
18
|
+
.option('--browser <browser>', 'Browser backend: playwright, chrome, or dia')
|
|
19
|
+
.option('--browser-path <path>', 'Executable path for a Chromium-compatible browser')
|
|
20
|
+
.option('--cdp-url <url>', 'Attach to a running Chromium browser via CDP')
|
|
21
|
+
.option('--user-data-dir <path>', 'Launch with a persistent browser profile directory')
|
|
22
|
+
.option('--timeout-ms <ms>', 'Browser launch/connect timeout in milliseconds');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Extract shared browser-launch options from Commander command options.
|
|
26
|
+
*/
|
|
27
|
+
function pickBrowserOptions(opts) {
|
|
28
|
+
return {
|
|
29
|
+
browser: opts.browser,
|
|
30
|
+
browserPath: opts.browserPath,
|
|
31
|
+
cdpUrl: opts.cdpUrl,
|
|
32
|
+
userDataDir: opts.userDataDir,
|
|
33
|
+
timeoutMs: opts.timeoutMs,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Parse an integer CLI option and fail fast on invalid input.
|
|
38
|
+
*/
|
|
39
|
+
function parseIntegerOption(value, label) {
|
|
40
|
+
const parsed = Number.parseInt(value, 10);
|
|
41
|
+
if (!Number.isFinite(parsed)) {
|
|
42
|
+
throw new Error(`Invalid ${label}: ${value}`);
|
|
43
|
+
}
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
12
46
|
// āāā Platforms Directory āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
13
47
|
program
|
|
14
48
|
.command('platforms')
|
|
@@ -62,24 +96,28 @@ program
|
|
|
62
96
|
}
|
|
63
97
|
});
|
|
64
98
|
// āāā Session āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
65
|
-
program
|
|
99
|
+
addBrowserOptions(program
|
|
66
100
|
.command('login <platform>')
|
|
67
101
|
.description('Open visible browser to log in and save session')
|
|
68
|
-
.action(async (platformQuery) => {
|
|
102
|
+
.action(async (platformQuery, opts) => {
|
|
69
103
|
const platform = getPlatform(platformQuery);
|
|
70
104
|
const url = platformQuery.startsWith('http://') || platformQuery.startsWith('https://')
|
|
71
105
|
? platformQuery
|
|
72
106
|
: platform?.loginUrl ?? `https://${platformQuery}`;
|
|
73
|
-
const {
|
|
107
|
+
const { context, page } = await ensureBrowser({
|
|
108
|
+
...pickBrowserOptions(opts),
|
|
109
|
+
headed: true,
|
|
110
|
+
platform: platformQuery,
|
|
111
|
+
});
|
|
74
112
|
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
75
113
|
const displayName = platform?.name || platformQuery;
|
|
76
114
|
console.log(chalk.cyan(`\nš Log into ${displayName} in the browser window.`));
|
|
77
115
|
console.log(chalk.gray(' Press Enter here when done.\n'));
|
|
78
116
|
await new Promise(res => process.stdin.once('data', () => res()));
|
|
79
|
-
await saveSession(platformQuery, context);
|
|
117
|
+
await saveSession(platformQuery, await context.storageState());
|
|
80
118
|
console.log(chalk.green(`ā
Session saved for ${displayName}`));
|
|
81
|
-
await
|
|
82
|
-
});
|
|
119
|
+
await closeBrowser();
|
|
120
|
+
}));
|
|
83
121
|
program
|
|
84
122
|
.command('sessions')
|
|
85
123
|
.description('List saved sessions')
|
|
@@ -94,14 +132,14 @@ program
|
|
|
94
132
|
}
|
|
95
133
|
});
|
|
96
134
|
// āāā Navigation āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
97
|
-
program
|
|
135
|
+
addBrowserOptions(program
|
|
98
136
|
.command('go <url>')
|
|
99
137
|
.description('Navigate to a URL')
|
|
100
138
|
.option('--platform <platform>', 'Platform for session restore', 'default')
|
|
101
139
|
.option('--wait <event>', 'Wait event: load|domcontentloaded|networkidle', 'domcontentloaded')
|
|
102
140
|
.option('--timeout <ms>', 'Timeout in ms', '30000')
|
|
103
141
|
.action(async (url, opts) => {
|
|
104
|
-
const { page } = await ensureBrowser({ platform: opts.platform });
|
|
142
|
+
const { page } = await ensureBrowser({ ...pickBrowserOptions(opts), platform: opts.platform });
|
|
105
143
|
try {
|
|
106
144
|
await page.goto(url, { waitUntil: opts.wait, timeout: parseInt(opts.timeout) });
|
|
107
145
|
const title = await page.title();
|
|
@@ -112,7 +150,7 @@ program
|
|
|
112
150
|
console.log(JSON.stringify({ ok: false, error: err.message }));
|
|
113
151
|
process.exit(1);
|
|
114
152
|
}
|
|
115
|
-
});
|
|
153
|
+
}));
|
|
116
154
|
program
|
|
117
155
|
.command('url')
|
|
118
156
|
.description('Get current URL')
|
|
@@ -383,12 +421,16 @@ program
|
|
|
383
421
|
}
|
|
384
422
|
});
|
|
385
423
|
// āāā Session management āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
386
|
-
program
|
|
424
|
+
addBrowserOptions(program
|
|
387
425
|
.command('open <platform>')
|
|
388
426
|
.description('Open a browser session using saved login cookies for a platform')
|
|
389
427
|
.option('--headed', 'Show browser window', false)
|
|
390
428
|
.action(async (platform, opts) => {
|
|
391
|
-
const {
|
|
429
|
+
const { page } = await ensureBrowser({
|
|
430
|
+
...pickBrowserOptions(opts),
|
|
431
|
+
headed: opts.headed,
|
|
432
|
+
platform,
|
|
433
|
+
});
|
|
392
434
|
const platformUrls = {
|
|
393
435
|
x: 'https://x.com/home',
|
|
394
436
|
twitter: 'https://x.com/home',
|
|
@@ -400,16 +442,15 @@ program
|
|
|
400
442
|
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
401
443
|
const title = await page.title();
|
|
402
444
|
console.log(JSON.stringify({ ok: true, platform, url: page.url(), title }));
|
|
403
|
-
});
|
|
445
|
+
}));
|
|
404
446
|
// āāā X / Social Interactions āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
405
|
-
program
|
|
447
|
+
addBrowserOptions(program
|
|
406
448
|
.command('like')
|
|
407
449
|
.description('Like the Nth post on the current page')
|
|
408
450
|
.option('--nth <n>', 'Which post (0-indexed)', '0')
|
|
409
451
|
.option('--platform <platform>', 'Platform for session', 'x')
|
|
410
452
|
.action(async (opts) => {
|
|
411
|
-
const {
|
|
412
|
-
const { browser, page } = await ensureBrowser({ platform: opts.platform });
|
|
453
|
+
const { page } = await ensureBrowser({ ...pickBrowserOptions(opts), platform: opts.platform });
|
|
413
454
|
try {
|
|
414
455
|
await page.goto('https://x.com/home', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
415
456
|
await page.waitForSelector("article[data-testid='tweet']", { timeout: 20000 });
|
|
@@ -424,17 +465,16 @@ program
|
|
|
424
465
|
process.exit(1);
|
|
425
466
|
}
|
|
426
467
|
finally {
|
|
427
|
-
await
|
|
468
|
+
await closeBrowser(opts.platform);
|
|
428
469
|
}
|
|
429
|
-
});
|
|
430
|
-
program
|
|
470
|
+
}));
|
|
471
|
+
addBrowserOptions(program
|
|
431
472
|
.command('reply <text>')
|
|
432
473
|
.description('Reply to the Nth post on the current X feed')
|
|
433
474
|
.option('--nth <n>', 'Which post (0-indexed)', '0')
|
|
434
475
|
.option('--platform <platform>', 'Platform for session', 'x')
|
|
435
476
|
.action(async (text, opts) => {
|
|
436
|
-
const {
|
|
437
|
-
const { browser, page } = await ensureBrowser({ platform: opts.platform });
|
|
477
|
+
const { page } = await ensureBrowser({ ...pickBrowserOptions(opts), platform: opts.platform });
|
|
438
478
|
try {
|
|
439
479
|
await page.goto('https://x.com/home', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
440
480
|
await page.waitForSelector("article[data-testid='tweet']", { timeout: 20000 });
|
|
@@ -455,17 +495,16 @@ program
|
|
|
455
495
|
process.exit(1);
|
|
456
496
|
}
|
|
457
497
|
finally {
|
|
458
|
-
await
|
|
498
|
+
await closeBrowser(opts.platform);
|
|
459
499
|
}
|
|
460
|
-
});
|
|
461
|
-
program
|
|
500
|
+
}));
|
|
501
|
+
addBrowserOptions(program
|
|
462
502
|
.command('repost')
|
|
463
503
|
.description('Repost (retweet) the Nth post on the current X feed')
|
|
464
504
|
.option('--nth <n>', 'Which post (0-indexed)', '0')
|
|
465
505
|
.option('--platform <platform>', 'Platform for session', 'x')
|
|
466
506
|
.action(async (opts) => {
|
|
467
|
-
const {
|
|
468
|
-
const { browser, page } = await ensureBrowser({ platform: opts.platform });
|
|
507
|
+
const { page } = await ensureBrowser({ ...pickBrowserOptions(opts), platform: opts.platform });
|
|
469
508
|
try {
|
|
470
509
|
await page.goto('https://x.com/home', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
471
510
|
await page.waitForSelector("article[data-testid='tweet']", { timeout: 20000 });
|
|
@@ -482,17 +521,16 @@ program
|
|
|
482
521
|
process.exit(1);
|
|
483
522
|
}
|
|
484
523
|
finally {
|
|
485
|
-
await
|
|
524
|
+
await closeBrowser(opts.platform);
|
|
486
525
|
}
|
|
487
|
-
});
|
|
488
|
-
program
|
|
526
|
+
}));
|
|
527
|
+
addBrowserOptions(program
|
|
489
528
|
.command('quote <text>')
|
|
490
529
|
.description('Quote the Nth post on the current X feed with your comment')
|
|
491
530
|
.option('--nth <n>', 'Which post (0-indexed)', '0')
|
|
492
531
|
.option('--platform <platform>', 'Platform for session', 'x')
|
|
493
532
|
.action(async (text, opts) => {
|
|
494
|
-
const {
|
|
495
|
-
const { browser, page } = await ensureBrowser({ platform: opts.platform });
|
|
533
|
+
const { page } = await ensureBrowser({ ...pickBrowserOptions(opts), platform: opts.platform });
|
|
496
534
|
try {
|
|
497
535
|
await page.goto('https://x.com/home', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
498
536
|
await page.waitForSelector("article[data-testid='tweet']", { timeout: 20000 });
|
|
@@ -516,16 +554,15 @@ program
|
|
|
516
554
|
process.exit(1);
|
|
517
555
|
}
|
|
518
556
|
finally {
|
|
519
|
-
await
|
|
557
|
+
await closeBrowser(opts.platform);
|
|
520
558
|
}
|
|
521
|
-
});
|
|
522
|
-
program
|
|
559
|
+
}));
|
|
560
|
+
addBrowserOptions(program
|
|
523
561
|
.command('post <text>')
|
|
524
562
|
.description('Post a tweet on X')
|
|
525
563
|
.option('--platform <platform>', 'Platform for session', 'x')
|
|
526
564
|
.action(async (text, opts) => {
|
|
527
|
-
const {
|
|
528
|
-
const { browser, page } = await ensureBrowser({ platform: opts.platform });
|
|
565
|
+
const { page } = await ensureBrowser({ ...pickBrowserOptions(opts), platform: opts.platform });
|
|
529
566
|
try {
|
|
530
567
|
await page.goto('https://x.com/home', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
531
568
|
await page.waitForSelector("[data-testid='primaryColumn']", { timeout: 20000 });
|
|
@@ -543,9 +580,32 @@ program
|
|
|
543
580
|
process.exit(1);
|
|
544
581
|
}
|
|
545
582
|
finally {
|
|
546
|
-
await
|
|
547
|
-
}
|
|
548
|
-
});
|
|
583
|
+
await closeBrowser(opts.platform);
|
|
584
|
+
}
|
|
585
|
+
}));
|
|
586
|
+
addBrowserOptions(program
|
|
587
|
+
.command('serve')
|
|
588
|
+
.description('Start a Streamable HTTP MCP server for Claude or other MCP clients')
|
|
589
|
+
.option('--host <host>', 'Host interface to bind', '127.0.0.1')
|
|
590
|
+
.option('--port <port>', 'TCP port to bind', '3456')
|
|
591
|
+
.option('--allowed-hosts <hosts>', 'Comma-separated allowed Host header values')
|
|
592
|
+
.option('--https-cert <path>', 'TLS certificate path for direct HTTPS')
|
|
593
|
+
.option('--https-key <path>', 'TLS private key path for direct HTTPS')
|
|
594
|
+
.action(async (opts) => {
|
|
595
|
+
await startMcpServer({
|
|
596
|
+
host: opts.host,
|
|
597
|
+
port: parseIntegerOption(opts.port, 'port'),
|
|
598
|
+
allowedHosts: opts.allowedHosts
|
|
599
|
+
? String(opts.allowedHosts)
|
|
600
|
+
.split(',')
|
|
601
|
+
.map((entry) => entry.trim())
|
|
602
|
+
.filter(Boolean)
|
|
603
|
+
: undefined,
|
|
604
|
+
httpsCert: opts.httpsCert,
|
|
605
|
+
httpsKey: opts.httpsKey,
|
|
606
|
+
browserDefaults: pickBrowserOptions(opts),
|
|
607
|
+
});
|
|
608
|
+
}));
|
|
549
609
|
program
|
|
550
610
|
.command('close')
|
|
551
611
|
.description('Close the current browser session')
|
|
@@ -563,7 +623,7 @@ program
|
|
|
563
623
|
const { isFlareSolverrUp } = await import('./local-captcha.js');
|
|
564
624
|
const sessions = await listSessions();
|
|
565
625
|
const flare = await isFlareSolverrUp();
|
|
566
|
-
console.log(chalk.cyan('\nš¶ļø veil v0.
|
|
626
|
+
console.log(chalk.cyan('\nš¶ļø veil v0.4.1 ā OpenClaw Browser Remote\n'));
|
|
567
627
|
console.log(` Sessions: ${sessions.length > 0 ? chalk.green(sessions.join(', ')) : chalk.gray('none')}`);
|
|
568
628
|
console.log(` FlareSolverr: ${flare ? chalk.green('running') : chalk.gray('not running (auto-starts on use)')}`);
|
|
569
629
|
console.log('');
|
|
@@ -576,6 +636,7 @@ program
|
|
|
576
636
|
console.log(chalk.gray(' veil type <sel> <text>'));
|
|
577
637
|
console.log(chalk.gray(' veil read [sel] # extract text'));
|
|
578
638
|
console.log(chalk.gray(' veil shot # screenshot'));
|
|
639
|
+
console.log(chalk.gray(' veil serve # start MCP server'));
|
|
579
640
|
console.log('');
|
|
580
641
|
});
|
|
581
642
|
program.parse();
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import { type BrowserLaunchOptions } from './browser.js';
|
|
2
|
+
export interface StartMcpServerOptions {
|
|
3
|
+
host?: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
allowedHosts?: string[];
|
|
6
|
+
httpsCert?: string;
|
|
7
|
+
httpsKey?: string;
|
|
8
|
+
browserDefaults?: Pick<BrowserLaunchOptions, 'browser' | 'browserPath' | 'cdpUrl' | 'userDataDir' | 'timeoutMs'>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Start a Claude-compatible Streamable HTTP MCP server with optional direct TLS.
|
|
12
|
+
*/
|
|
13
|
+
export declare function startMcpServer(options?: StartMcpServerOptions): Promise<void>;
|