veil-browser 0.3.0 → 0.4.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/README.md CHANGED
@@ -1,143 +1,195 @@
1
- # 🕶️ veil
1
+ <div align="center">
2
2
 
3
- > Stealth browser CLI for AI agents — bypass bot detection, persist sessions, full web control.
3
+ <img src="https://raw.githubusercontent.com/cuttlelab/veil/main/docs/logo.png" alt="veil" width="80" />
4
4
 
5
- Built for [OpenClaw](https://openclaw.ai) agents but works standalone. Playwright under the hood with stealth anti-detection baked in from day one.
5
+ # veil
6
6
 
7
- ---
7
+ **Stealth browser remote for AI agents.**
8
8
 
9
- ## Features
9
+ [![npm](https://img.shields.io/npm/v/veil-browser?color=000&style=flat-square)](https://www.npmjs.com/package/veil-browser)
10
+ [![license](https://img.shields.io/badge/license-MIT-000?style=flat-square)](LICENSE)
11
+ [![node](https://img.shields.io/badge/node-%3E%3D18-000?style=flat-square)](https://nodejs.org)
10
12
 
11
- - 🥷 **Stealth by default** — powered by `playwright-extra` + `puppeteer-extra-plugin-stealth`
12
- - 🔐 **Persistent sessions** — log in once, use forever (saved to `~/.veil/sessions/`)
13
- - 👁️ **Interactive login mode** — opens a real visible browser for you to authenticate manually
14
- - 🤖 **Agent-friendly commands** — natural language actions, JSON output
15
- - 🌐 **Works everywhere** — X/Twitter, Reddit, LinkedIn, GitHub, Instagram, any site
13
+ </div>
16
14
 
17
15
  ---
18
16
 
19
- ## Install
17
+ veil is a **headless browser CLI** built for AI agents. It runs a real, stealth Chromium browser and exposes every interaction as a simple command that returns clean JSON.
20
18
 
21
- ```bash
22
- cd veil
23
- npm install
24
- npm run build
25
- npm link # makes 'veil' available globally
26
- ```
19
+ You (or your agent) are the brain. veil is the hands.
27
20
 
28
- Or install Playwright browsers:
29
21
  ```bash
22
+ npm install -g veil-browser
30
23
  npx playwright install chromium
24
+ veil login x # save your session once
25
+ veil post "Hello, world from an AI agent."
31
26
  ```
32
27
 
33
28
  ---
34
29
 
35
- ## Usage
30
+ ## Why veil
36
31
 
37
- ### Login to a platform
32
+ Most browser automation tools are built for developers writing scripts. veil is built for **AI agents making decisions in real-time** — where every step is a tool call, every result is parseable JSON, and the agent decides what to do next.
38
33
 
39
- ```bash
40
- veil login twitter
41
- ```
34
+ - **No LLM inside** — veil is pure remote control. Your agent is the intelligence.
35
+ - **Stealth by default** — hides automation signals, realistic user agent, human-like delays
36
+ - **Persistent sessions** — log in once, reuse forever via saved cookies
37
+ - **Every output is JSON** — `{ ok: true, ... }` or `{ ok: false, error: "..." }`
42
38
 
43
- Opens a visible Chromium browser. Log in normally (username, password, 2FA). Veil detects when you're on the home page and saves the session automatically.
39
+ ---
44
40
 
45
- ### Post a tweet
41
+ ## Installation
46
42
 
47
43
  ```bash
48
- veil act "post tweet: Hello from veil 🕶️" --platform twitter
44
+ npm install -g veil-browser
45
+ npx playwright install chromium
49
46
  ```
50
47
 
51
- ### Navigate headlessly
48
+ ---
49
+
50
+ ## Quick Start
52
51
 
53
52
  ```bash
54
- veil navigate https://x.com --platform twitter
53
+ # Log in once (opens visible browser)
54
+ veil login x
55
+
56
+ # Post a tweet
57
+ veil post "Building something new."
58
+
59
+ # Like, reply, repost, quote
60
+ veil like --nth 0
61
+ veil reply "Great point." --nth 0
62
+ veil repost --nth 1
63
+ veil quote "Worth reading." --nth 2
64
+
65
+ # Navigate and read any page
66
+ veil go https://example.com
67
+ veil snapshot # DOM tree → understand the page
68
+ veil read "h1" # Extract specific elements
69
+ veil find "Sign in" # Check if text exists
70
+
71
+ # Click, type, interact
72
+ veil click "[data-testid='button']"
73
+ veil type "input[name='q']" "search query"
74
+ veil press Enter
75
+ veil scroll down
76
+
77
+ # Screenshot
78
+ veil shot page.png
55
79
  ```
56
80
 
57
- ### Extract data
81
+ ---
58
82
 
59
- ```bash
60
- veil extract "tweets" --url https://x.com/home --platform twitter --json
61
- ```
83
+ ## All Commands
62
84
 
63
- ### Take a screenshot
85
+ ### Session
86
+ | Command | Description |
87
+ |---------|-------------|
88
+ | `veil login <platform>` | Open visible browser → log in → save session |
89
+ | `veil open <platform>` | Restore session and navigate to platform home |
90
+ | `veil sessions` | List saved sessions |
91
+ | `veil close` | Close browser |
64
92
 
65
- ```bash
66
- veil screenshot --url https://x.com --platform twitter --output ./x-home.png
67
- ```
93
+ **Supported platforms:** `x`, `linkedin`, `reddit`, `bluesky` (or any URL)
68
94
 
69
- ### List saved sessions
95
+ ### X / Social
96
+ | Command | Description |
97
+ |---------|-------------|
98
+ | `veil post "text"` | Post a tweet |
99
+ | `veil like --nth 0` | Like Nth post in feed |
100
+ | `veil reply "text" --nth 0` | Reply to Nth post |
101
+ | `veil repost --nth 0` | Repost Nth post |
102
+ | `veil quote "text" --nth 0` | Quote Nth post with comment |
70
103
 
71
- ```bash
72
- veil session list
73
- ```
104
+ ### Navigation
105
+ | Command | Description |
106
+ |---------|-------------|
107
+ | `veil go <url>` | Navigate to URL |
108
+ | `veil url` | Get current URL and title |
109
+ | `veil back` | Go back |
74
110
 
75
- ### Remove a session
111
+ ### Reading
112
+ | Command | Description |
113
+ |---------|-------------|
114
+ | `veil snapshot` | Full DOM tree (best for understanding page structure) |
115
+ | `veil read [selector]` | Page text or element text |
116
+ | `veil read <sel> --all` | All matches as array |
117
+ | `veil read <sel> --attr href` | Read attribute value |
118
+ | `veil find "text"` | Check if text exists on page |
119
+ | `veil exists <selector>` | Check if selector exists |
120
+
121
+ ### Interaction
122
+ | Command | Description |
123
+ |---------|-------------|
124
+ | `veil click <selector>` | Click element |
125
+ | `veil click <sel> --force` | Force click (bypass overlays) |
126
+ | `veil click <sel> --nth 2` | Click Nth match |
127
+ | `veil type <selector> "text"` | Type into element |
128
+ | `veil type <sel> "text" --clear` | Clear first, then type |
129
+ | `veil press <key>` | Press key (Enter, Tab, Escape…) |
130
+ | `veil scroll down\|up\|top\|bottom` | Scroll page |
131
+ | `veil wait <ms>` | Wait N milliseconds |
132
+ | `veil wait-for <selector>` | Wait until element appears |
133
+ | `veil eval "script"` | Run JavaScript, returns result |
134
+
135
+ ### Screenshot
136
+ | Command | Description |
137
+ |---------|-------------|
138
+ | `veil shot [file.png]` | Screenshot (full page with `--full`) |
139
+ | `veil shot file.png --selector <sel>` | Screenshot specific element |
76
140
 
77
- ```bash
78
- veil logout twitter
79
- ```
141
+ ---
80
142
 
81
- ### Status
143
+ ## For AI Agents (OpenClaw / MCP)
82
144
 
83
- ```bash
84
- veil status
145
+ veil ships with a `SKILL.md` that teaches OpenClaw exactly how to use it — all commands, all platform selectors, and complete task sequences.
146
+
147
+ The agent pattern:
148
+ ```
149
+ 1. veil open x → restore session
150
+ 2. veil snapshot → understand current page
151
+ 3. veil click / type → act
152
+ 4. veil wait 800 → let UI settle
153
+ 5. veil find / read → verify
85
154
  ```
86
155
 
156
+ Every command is deterministic. Every output is parseable. The agent decides the logic.
157
+
87
158
  ---
88
159
 
89
- ## Command Reference
160
+ ## Platform Support
90
161
 
91
- | Command | Description |
92
- |---------|-------------|
93
- | `veil login <platform>` | Open visible browser for manual login, save session |
94
- | `veil logout <platform>` | Remove saved session |
95
- | `veil navigate <url>` | Navigate to URL (headless stealth) |
96
- | `veil act "<instruction>"` | Perform natural language action |
97
- | `veil extract "<query>"` | Extract data as JSON |
98
- | `veil screenshot` | Take screenshot |
99
- | `veil session list` | List all saved sessions |
100
- | `veil status` | Show veil status |
101
-
102
- ### Common flags
103
-
104
- | Flag | Description |
105
- |------|-------------|
106
- | `-p, --platform <name>` | Use saved session for this platform |
107
- | `-u, --url <url>` | Navigate to URL before action |
108
- | `-H, --headed` | Run in visible browser mode |
109
- | `-o, --output <path>` | Output file path (screenshot) |
110
- | `--json` | Output machine-readable JSON |
162
+ | Platform | Login | Post | Like | Reply | Repost | Quote |
163
+ |----------|-------|------|------|-------|--------|-------|
164
+ | X (Twitter) | | | | | | |
165
+ | LinkedIn | | | | ✅ | — | — |
166
+ | Reddit | | | | | — | — |
167
+ | Any website | | | via selectors | — | — | — |
111
168
 
112
169
  ---
113
170
 
114
- ## Supported `act` instructions
171
+ ## Configuration
172
+
173
+ Config stored at `~/.veil/config.json`. Sessions at `~/.veil/sessions/<platform>.json`.
115
174
 
116
175
  ```bash
117
- veil act "click Login"
118
- veil act "type 'hello world' into search"
119
- veil act "post tweet: your content here"
120
- veil act "like tweet"
121
- veil act "scroll down"
122
- veil act "press enter"
123
- veil act "go to https://example.com"
176
+ veil status # show current config and saved sessions
124
177
  ```
125
178
 
126
179
  ---
127
180
 
128
- ## Session storage
181
+ ## How It Works
129
182
 
130
- Sessions are saved to `~/.veil/sessions/<platform>.json` as Playwright storage state (cookies + localStorage). No encryption currently — keep your machine secure.
183
+ 1. **Stealth Chromium** launches with flags that hide automation signals
184
+ 2. **Session cookies** — saved after manual login, restored on every run
185
+ 3. **Human timing** — random delays between actions (400–1200ms)
186
+ 4. **Clean JSON output** — every command returns `{ ok, ... }` for easy parsing
131
187
 
132
188
  ---
133
189
 
134
- ## OpenClaw integration
135
-
136
- Use `veil` as a drop-in for the OpenClaw Browser Relay. Add it as a skill and call it from any agent:
190
+ ## Built by CUTTLELAB
137
191
 
138
- ```bash
139
- veil act "post tweet: launched something today" --platform twitter --json
140
- ```
192
+ veil is part of the tooling stack we're building for AI agents at [CUTTLELAB](https://cuttle.io).
141
193
 
142
194
  ---
143
195
 
package/dist/browser.js CHANGED
@@ -2,9 +2,28 @@ import { chromium } from 'playwright';
2
2
  import { promises as fs } from 'fs';
3
3
  import { homedir } from 'os';
4
4
  import { join } from 'path';
5
+ import { execSync } from 'child_process';
5
6
  import { loadSession } from './session.js';
6
7
  const VEIL_DIR = join(homedir(), '.veil');
7
8
  const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36';
9
+ // Detect system Chrome installation
10
+ function findSystemChrome() {
11
+ const paths = [
12
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
13
+ '/usr/bin/google-chrome',
14
+ '/usr/bin/chromium',
15
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
16
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
17
+ ];
18
+ for (const path of paths) {
19
+ try {
20
+ execSync(`test -x "${path}"`, { stdio: 'ignore' });
21
+ return path;
22
+ }
23
+ catch { }
24
+ }
25
+ return null;
26
+ }
8
27
  let _browser = null;
9
28
  let _context = null;
10
29
  let _page = null;
@@ -12,9 +31,22 @@ export async function ensureBrowser(opts = {}) {
12
31
  if (_browser?.isConnected() && _page && !_page.isClosed()) {
13
32
  return { browser: _browser, context: _context, page: _page };
14
33
  }
34
+ const executablePath = findSystemChrome();
15
35
  const browser = await chromium.launch({
16
36
  headless: !opts.headed,
17
- args: ['--disable-blink-features=AutomationControlled', '--no-sandbox', '--disable-setuid-sandbox', '--window-size=1280,800'],
37
+ executablePath: executablePath || undefined, // Use system Chrome if found, else fallback
38
+ args: [
39
+ '--disable-blink-features=AutomationControlled',
40
+ '--no-sandbox',
41
+ '--disable-setuid-sandbox',
42
+ '--window-size=1280,800',
43
+ '--disable-dev-shm-usage',
44
+ '--disable-gpu',
45
+ '--no-first-run',
46
+ '--no-default-browser-check',
47
+ '--disable-default-apps',
48
+ '--disable-extensions',
49
+ ],
18
50
  });
19
51
  const context = await browser.newContext({
20
52
  viewport: { width: 1280, height: 800 },
@@ -22,12 +54,27 @@ export async function ensureBrowser(opts = {}) {
22
54
  locale: 'en-US',
23
55
  timezoneId: 'America/New_York',
24
56
  extraHTTPHeaders: { 'Accept-Language': 'en-US,en;q=0.9' },
57
+ ignoreHTTPSErrors: true,
25
58
  });
26
59
  await context.addInitScript(() => {
60
+ // Spoof all automation detection vectors
27
61
  Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
62
+ Object.defineProperty(navigator, 'chromeapp', { get: () => undefined });
28
63
  window.chrome = { runtime: {} };
29
64
  Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });
30
65
  Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
66
+ // Spoof permissions
67
+ window.navigator.permissions = {
68
+ query: () => Promise.resolve({ state: Notification.permission })
69
+ };
70
+ // Override toString on navigator to hide automation
71
+ const originalToString = Function.prototype.toString;
72
+ Function.prototype.toString = function () {
73
+ if (this === window.navigator.permissions.query) {
74
+ return 'function query() { [native code] }';
75
+ }
76
+ return originalToString.call(this);
77
+ };
31
78
  });
32
79
  const session = await loadSession(opts.platform ?? 'default').catch(() => null);
33
80
  if (session?.cookies?.length)
package/dist/index.js CHANGED
@@ -3,31 +3,81 @@ import { Command } from 'commander';
3
3
  import chalk from 'chalk';
4
4
  import { ensureBrowser, closeBrowser, getPage } from './browser.js';
5
5
  import { saveSession } from './session.js';
6
+ import { getPlatform, listPlatforms, searchPlatforms } from './platforms.js';
6
7
  const program = new Command();
7
8
  program
8
9
  .name('veil')
9
10
  .description('🕶️ OpenClaw browser remote — stealth headless browser')
10
- .version('0.3.0');
11
+ .version('0.4.0');
12
+ // ─── Platforms Directory ──────────────────────────────────────────────────────
13
+ program
14
+ .command('platforms')
15
+ .description('Manage platform directory')
16
+ .action(() => {
17
+ console.log(chalk.blue('\n📚 Veil Platform Directory\n'));
18
+ const platforms = listPlatforms();
19
+ const byCategory = platforms.reduce((acc, p) => {
20
+ acc[p.category] = (acc[p.category] || []).concat(p);
21
+ return acc;
22
+ }, {});
23
+ for (const [category, proms] of Object.entries(byCategory)) {
24
+ console.log(chalk.cyan(` ${category.toUpperCase()} (${proms.length})`));
25
+ proms.forEach(p => {
26
+ console.log(chalk.gray(` • ${p.name} — ${p.aliases.join(', ')}`));
27
+ });
28
+ console.log();
29
+ }
30
+ console.log(chalk.gray(`Total: ${platforms.length} platforms\n`));
31
+ });
32
+ program
33
+ .command('platforms:list [category]')
34
+ .description('List platforms by category (ai, social, dev, productivity, finance, shopping, email, other)')
35
+ .action((category) => {
36
+ const platforms = listPlatforms(category);
37
+ console.log(chalk.blue(`\n📊 ${category ? `${category.toUpperCase()} Platforms` : 'All Platforms'} (${platforms.length})\n`));
38
+ platforms.forEach(p => {
39
+ console.log(` ${chalk.cyan(p.name)}`);
40
+ console.log(` Aliases: ${p.aliases.join(', ')}`);
41
+ if (p.notes)
42
+ console.log(` Notes: ${chalk.gray(p.notes)}`);
43
+ console.log();
44
+ });
45
+ });
46
+ program
47
+ .command('platforms:search <query>')
48
+ .description('Search for a platform')
49
+ .action((query) => {
50
+ const results = searchPlatforms(query);
51
+ if (results.length === 0) {
52
+ console.log(chalk.yellow(`\nNo platforms found matching "${query}"\n`));
53
+ }
54
+ else {
55
+ console.log(chalk.blue(`\n🔍 Found ${results.length} platform(s):\n`));
56
+ results.forEach(p => {
57
+ console.log(` ${chalk.cyan(p.name)}`);
58
+ console.log(` Login: ${chalk.gray(p.loginUrl)}`);
59
+ console.log(` Aliases: ${p.aliases.join(', ')}`);
60
+ console.log();
61
+ });
62
+ }
63
+ });
11
64
  // ─── Session ──────────────────────────────────────────────────────────────────
12
65
  program
13
66
  .command('login <platform>')
14
- .description('Open visible browser to log in and save session (x, linkedin, reddit)')
15
- .action(async (platform) => {
16
- const platformUrls = {
17
- x: 'https://x.com/login',
18
- twitter: 'https://x.com/login',
19
- linkedin: 'https://www.linkedin.com/login',
20
- reddit: 'https://www.reddit.com/login',
21
- bluesky: 'https://bsky.app',
22
- };
23
- const url = platformUrls[platform.toLowerCase()] ?? `https://${platform}`;
24
- const { browser, context, page } = await ensureBrowser({ headed: true, platform });
67
+ .description('Open visible browser to log in and save session')
68
+ .action(async (platformQuery) => {
69
+ const platform = getPlatform(platformQuery);
70
+ const url = platformQuery.startsWith('http://') || platformQuery.startsWith('https://')
71
+ ? platformQuery
72
+ : platform?.loginUrl ?? `https://${platformQuery}`;
73
+ const { browser, context, page } = await ensureBrowser({ headed: true, platform: platformQuery });
25
74
  await page.goto(url, { waitUntil: 'domcontentloaded' });
26
- console.log(chalk.cyan(`\n🔐 Log into ${platform} in the browser window.`));
75
+ const displayName = platform?.name || platformQuery;
76
+ console.log(chalk.cyan(`\n🔐 Log into ${displayName} in the browser window.`));
27
77
  console.log(chalk.gray(' Press Enter here when done.\n'));
28
78
  await new Promise(res => process.stdin.once('data', () => res()));
29
- await saveSession(platform, context);
30
- console.log(chalk.green(`✅ Session saved for ${platform}`));
79
+ await saveSession(platformQuery, context);
80
+ console.log(chalk.green(`✅ Session saved for ${displayName}`));
31
81
  await browser.close();
32
82
  });
33
83
  program
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Veil Platform Directory
3
+ * Comprehensive mapping of platforms with login URLs, selectors, and metadata
4
+ */
5
+ export interface PlatformConfig {
6
+ name: string;
7
+ loginUrl: string;
8
+ aliases: string[];
9
+ category: 'social' | 'ai' | 'productivity' | 'email' | 'shopping' | 'dev' | 'finance' | 'other';
10
+ selectors?: {
11
+ emailInput?: string;
12
+ usernameInput?: string;
13
+ passwordInput?: string;
14
+ submitButton?: string;
15
+ nextButton?: string;
16
+ verificationInput?: string;
17
+ };
18
+ postLoginCheck?: string;
19
+ cookies?: string[];
20
+ notes?: string;
21
+ }
22
+ export declare const PLATFORMS: Record<string, PlatformConfig>;
23
+ export declare function getPlatform(query: string): PlatformConfig | null;
24
+ export declare function listPlatforms(category?: PlatformConfig['category']): PlatformConfig[];
25
+ export declare function searchPlatforms(query: string): PlatformConfig[];
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Veil Platform Directory
3
+ * Comprehensive mapping of platforms with login URLs, selectors, and metadata
4
+ */
5
+ export const PLATFORMS = {
6
+ // ─── AI & Image Generation ───
7
+ gemini: {
8
+ name: 'Google Gemini',
9
+ loginUrl: 'https://gemini.google.com/',
10
+ aliases: ['gemini', 'google-gemini', 'bard'],
11
+ category: 'ai',
12
+ postLoginCheck: '[aria-label*="Send a message"]',
13
+ cookies: ['__Secure-GSID', '__Secure-HSID'],
14
+ notes: 'Google account required',
15
+ },
16
+ 'dalle-web': {
17
+ name: 'DALL-E (OpenAI Web)',
18
+ loginUrl: 'https://openai.com/api/auth/callback/auth0',
19
+ aliases: ['dalle', 'dalle-3', 'dall-e', 'openai-images'],
20
+ category: 'ai',
21
+ postLoginCheck: 'button[aria-label*="Generate"]',
22
+ cookies: ['_U'],
23
+ notes: 'OpenAI account required',
24
+ },
25
+ 'claude-web': {
26
+ name: 'Claude (Anthropic Web)',
27
+ loginUrl: 'https://claude.ai/',
28
+ aliases: ['claude', 'anthropic', 'claude-web'],
29
+ category: 'ai',
30
+ postLoginCheck: 'textarea[placeholder*="Send a message"]',
31
+ cookies: ['__Secure-SSID'],
32
+ notes: 'Anthropic/Claude subscription required',
33
+ },
34
+ midjourney: {
35
+ name: 'Midjourney',
36
+ loginUrl: 'https://www.midjourney.com/auth/login',
37
+ aliases: ['midjourney', 'mj'],
38
+ category: 'ai',
39
+ selectors: {
40
+ emailInput: 'input[type="email"]',
41
+ passwordInput: 'input[type="password"]',
42
+ submitButton: 'button[type="submit"]',
43
+ },
44
+ postLoginCheck: 'div[data-testid="sidebar"]',
45
+ notes: 'Discord-based, requires account',
46
+ },
47
+ 'stability-ai': {
48
+ name: 'Stability AI (DreamStudio)',
49
+ loginUrl: 'https://dreamstudio.ai/generate',
50
+ aliases: ['stability', 'dreamstudio', 'stable-diffusion-web'],
51
+ category: 'ai',
52
+ postLoginCheck: 'button:has-text("Generate")',
53
+ notes: 'API key or account required',
54
+ },
55
+ // ─── Social Media ───
56
+ x: {
57
+ name: 'X (Twitter)',
58
+ loginUrl: 'https://x.com/login',
59
+ aliases: ['x', 'twitter', 'x-com'],
60
+ category: 'social',
61
+ selectors: {
62
+ emailInput: 'input[autocomplete="username"]',
63
+ passwordInput: 'input[type="password"]',
64
+ submitButton: 'button[type="submit"]',
65
+ },
66
+ postLoginCheck: 'a[href="/home"]',
67
+ cookies: ['auth_token'],
68
+ },
69
+ bluesky: {
70
+ name: 'Bluesky',
71
+ loginUrl: 'https://bsky.app/login',
72
+ aliases: ['bluesky', 'bsky'],
73
+ category: 'social',
74
+ selectors: {
75
+ usernameInput: 'input[placeholder*="Username"]',
76
+ passwordInput: 'input[type="password"]',
77
+ submitButton: 'button:has-text("Sign in")',
78
+ },
79
+ postLoginCheck: 'div[aria-label="Home timeline"]',
80
+ },
81
+ reddit: {
82
+ name: 'Reddit',
83
+ loginUrl: 'https://www.reddit.com/login',
84
+ aliases: ['reddit'],
85
+ category: 'social',
86
+ selectors: {
87
+ emailInput: 'input[id*="login-username"]',
88
+ passwordInput: 'input[id*="login-password"]',
89
+ submitButton: 'button[type="submit"]',
90
+ },
91
+ postLoginCheck: 'button[aria-label*="Create post"]',
92
+ cookies: ['session_tracker'],
93
+ },
94
+ linkedin: {
95
+ name: 'LinkedIn',
96
+ loginUrl: 'https://www.linkedin.com/login',
97
+ aliases: ['linkedin'],
98
+ category: 'social',
99
+ selectors: {
100
+ emailInput: 'input#username',
101
+ passwordInput: 'input#password',
102
+ submitButton: 'button[type="submit"]',
103
+ },
104
+ postLoginCheck: 'a[data-control-name="feed_home_button"]',
105
+ cookies: ['li_at'],
106
+ },
107
+ github: {
108
+ name: 'GitHub',
109
+ loginUrl: 'https://github.com/login',
110
+ aliases: ['github', 'gh'],
111
+ category: 'dev',
112
+ selectors: {
113
+ emailInput: 'input#login_field',
114
+ passwordInput: 'input#password',
115
+ submitButton: 'input[type="submit"]',
116
+ },
117
+ postLoginCheck: 'div[data-nav-core-github-home-feed]',
118
+ cookies: ['user_session', 'logged_in'],
119
+ },
120
+ // ─── Email & Productivity ───
121
+ gmail: {
122
+ name: 'Gmail',
123
+ loginUrl: 'https://accounts.google.com/ServiceLogin?service=mail',
124
+ aliases: ['gmail', 'google-mail'],
125
+ category: 'email',
126
+ postLoginCheck: 'div[role="button"]:has-text("Compose")',
127
+ cookies: ['__Secure-GSID'],
128
+ notes: 'Google account',
129
+ },
130
+ notion: {
131
+ name: 'Notion',
132
+ loginUrl: 'https://www.notion.so/login',
133
+ aliases: ['notion'],
134
+ category: 'productivity',
135
+ selectors: {
136
+ emailInput: 'input[type="email"]',
137
+ submitButton: 'button:has-text("Continue with email")',
138
+ },
139
+ postLoginCheck: 'div[data-testid="sidebar"]',
140
+ },
141
+ notion_sso: {
142
+ name: 'Notion (Google SSO)',
143
+ loginUrl: 'https://www.notion.so/login?google=true',
144
+ aliases: ['notion-google', 'notion-sso'],
145
+ category: 'productivity',
146
+ postLoginCheck: 'div[data-testid="sidebar"]',
147
+ },
148
+ // ─── Development & APIs ───
149
+ openai_platform: {
150
+ name: 'OpenAI Platform',
151
+ loginUrl: 'https://platform.openai.com/login',
152
+ aliases: ['openai', 'openai-api', 'openai-platform'],
153
+ category: 'dev',
154
+ selectors: {
155
+ emailInput: 'input[type="email"]',
156
+ submitButton: 'button:has-text("Continue")',
157
+ },
158
+ postLoginCheck: 'button[aria-label="Create new secret key"]',
159
+ cookies: ['_auth0sso'],
160
+ },
161
+ anthropic_console: {
162
+ name: 'Anthropic Console',
163
+ loginUrl: 'https://console.anthropic.com/login',
164
+ aliases: ['anthropic', 'anthropic-api', 'anthropic-console'],
165
+ category: 'dev',
166
+ postLoginCheck: 'button:has-text("Create API key")',
167
+ },
168
+ huggingface: {
169
+ name: 'Hugging Face',
170
+ loginUrl: 'https://huggingface.co/login',
171
+ aliases: ['huggingface', 'hf'],
172
+ category: 'dev',
173
+ selectors: {
174
+ usernameInput: 'input[name="username"]',
175
+ passwordInput: 'input[name="password"]',
176
+ submitButton: 'button[type="submit"]',
177
+ },
178
+ postLoginCheck: 'a[href*="/settings"]',
179
+ },
180
+ // ─── Shopping ───
181
+ amazon: {
182
+ name: 'Amazon',
183
+ loginUrl: 'https://www.amazon.com/ap/signin',
184
+ aliases: ['amazon'],
185
+ category: 'shopping',
186
+ selectors: {
187
+ emailInput: 'input#ap_email',
188
+ passwordInput: 'input#ap_password',
189
+ submitButton: 'input#signInSubmit',
190
+ },
191
+ postLoginCheck: 'a[data-nav-ref="nav_your_account"]',
192
+ },
193
+ ebay: {
194
+ name: 'eBay',
195
+ loginUrl: 'https://signin.ebay.com/signin/',
196
+ aliases: ['ebay'],
197
+ category: 'shopping',
198
+ selectors: {
199
+ emailInput: 'input#userid',
200
+ passwordInput: 'input#pass',
201
+ submitButton: 'button#signin-continue',
202
+ },
203
+ postLoginCheck: 'a[href*="myaccount"]',
204
+ },
205
+ // ─── Finance ───
206
+ stripe: {
207
+ name: 'Stripe Dashboard',
208
+ loginUrl: 'https://dashboard.stripe.com/login',
209
+ aliases: ['stripe'],
210
+ category: 'finance',
211
+ selectors: {
212
+ emailInput: 'input[type="email"]',
213
+ passwordInput: 'input[type="password"]',
214
+ submitButton: 'button[type="submit"]',
215
+ },
216
+ postLoginCheck: 'button[aria-label*="Account menu"]',
217
+ },
218
+ // ─── Miscellaneous ───
219
+ google: {
220
+ name: 'Google (Generic)',
221
+ loginUrl: 'https://accounts.google.com/',
222
+ aliases: ['google', 'google-account'],
223
+ category: 'other',
224
+ selectors: {
225
+ emailInput: 'input#identifierId',
226
+ submitButton: 'button#identifierNext',
227
+ },
228
+ },
229
+ microsoft: {
230
+ name: 'Microsoft Account',
231
+ loginUrl: 'https://login.live.com/',
232
+ aliases: ['microsoft', 'outlook', 'hotmail'],
233
+ category: 'other',
234
+ selectors: {
235
+ emailInput: 'input[type="email"]',
236
+ submitButton: 'input[type="submit"]',
237
+ },
238
+ },
239
+ apple: {
240
+ name: 'Apple ID',
241
+ loginUrl: 'https://appleid.apple.com/',
242
+ aliases: ['apple', 'apple-id'],
243
+ category: 'other',
244
+ selectors: {
245
+ emailInput: 'input#user-name',
246
+ passwordInput: 'input#password',
247
+ submitButton: 'button[type="submit"]',
248
+ },
249
+ },
250
+ };
251
+ export function getPlatform(query) {
252
+ const lower = query.toLowerCase();
253
+ // Exact match in key
254
+ if (PLATFORMS[lower]) {
255
+ return PLATFORMS[lower];
256
+ }
257
+ // Check aliases
258
+ for (const [, platform] of Object.entries(PLATFORMS)) {
259
+ if (platform.aliases.includes(lower)) {
260
+ return platform;
261
+ }
262
+ }
263
+ // Fuzzy match on name
264
+ for (const [, platform] of Object.entries(PLATFORMS)) {
265
+ if (platform.name.toLowerCase().includes(lower)) {
266
+ return platform;
267
+ }
268
+ }
269
+ return null;
270
+ }
271
+ export function listPlatforms(category) {
272
+ if (!category) {
273
+ return Object.values(PLATFORMS);
274
+ }
275
+ return Object.values(PLATFORMS).filter(p => p.category === category);
276
+ }
277
+ export function searchPlatforms(query) {
278
+ const lower = query.toLowerCase();
279
+ return Object.values(PLATFORMS).filter(p => p.name.toLowerCase().includes(lower) ||
280
+ p.aliases.some(a => a.includes(lower)));
281
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veil-browser",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Stealth browser CLI for AI agents — bypass bot detection, persist sessions, local CAPTCHA solving, MCP server",
5
5
  "keywords": [
6
6
  "browser",