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/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.0');
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 { browser, context, page } = await ensureBrowser({ headed: true, platform: platformQuery });
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 browser.close();
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 { browser, context, page } = await ensureBrowser({ headed: opts.headed, platform });
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 { ensureBrowser, humanDelay } = await import('./browser.js');
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 browser.close();
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 { ensureBrowser, humanDelay } = await import('./browser.js');
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 browser.close();
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 { ensureBrowser, humanDelay } = await import('./browser.js');
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 browser.close();
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 { ensureBrowser, humanDelay } = await import('./browser.js');
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 browser.close();
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 { ensureBrowser, humanDelay } = await import('./browser.js');
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 browser.close();
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.3.0 — OpenClaw Browser Remote\n'));
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
- export declare function startMcpServer(port?: number): Promise<void>;
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>;