veil-browser 0.2.1 → 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.d.ts CHANGED
@@ -1,10 +1,4 @@
1
1
  import { Browser, BrowserContext, Page } from 'playwright';
2
- export interface BrowserState {
3
- platform: string;
4
- pid: number;
5
- wsEndpoint: string;
6
- timestamp: number;
7
- }
8
2
  export declare function ensureBrowser(opts?: {
9
3
  headed?: boolean;
10
4
  platform?: string;
package/dist/browser.js CHANGED
@@ -2,87 +2,99 @@ 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
- const STATE_FILE = join(VEIL_DIR, 'browser-state.json');
8
- // In-process singletons
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
+ }
9
27
  let _browser = null;
10
28
  let _context = null;
11
29
  let _page = null;
12
- const STEALTH_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';
13
30
  export async function ensureBrowser(opts = {}) {
14
- const platform = opts.platform ?? 'default';
15
- // Return existing if healthy
16
31
  if (_browser?.isConnected() && _page && !_page.isClosed()) {
17
32
  return { browser: _browser, context: _context, page: _page };
18
33
  }
19
- // Fresh browser launch
34
+ const executablePath = findSystemChrome();
20
35
  const browser = await chromium.launch({
21
36
  headless: !opts.headed,
37
+ executablePath: executablePath || undefined, // Use system Chrome if found, else fallback
22
38
  args: [
23
39
  '--disable-blink-features=AutomationControlled',
24
40
  '--no-sandbox',
25
41
  '--disable-setuid-sandbox',
26
- '--disable-infobars',
42
+ '--window-size=1280,800',
27
43
  '--disable-dev-shm-usage',
28
- '--disable-accelerated-2d-canvas',
44
+ '--disable-gpu',
29
45
  '--no-first-run',
30
- '--window-size=1280,800',
46
+ '--no-default-browser-check',
47
+ '--disable-default-apps',
48
+ '--disable-extensions',
31
49
  ],
32
50
  });
33
51
  const context = await browser.newContext({
34
52
  viewport: { width: 1280, height: 800 },
35
- userAgent: STEALTH_UA,
53
+ userAgent: UA,
36
54
  locale: 'en-US',
37
55
  timezoneId: 'America/New_York',
38
- permissions: ['notifications'],
39
56
  extraHTTPHeaders: { 'Accept-Language': 'en-US,en;q=0.9' },
57
+ ignoreHTTPSErrors: true,
40
58
  });
41
- // Stealth: hide automation signals
42
59
  await context.addInitScript(() => {
60
+ // Spoof all automation detection vectors
43
61
  Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
62
+ Object.defineProperty(navigator, 'chromeapp', { get: () => undefined });
44
63
  window.chrome = { runtime: {} };
45
64
  Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });
46
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
+ };
47
78
  });
48
- // Restore saved session cookies
49
- const session = await loadSession(platform).catch(() => null);
50
- if (session?.cookies?.length) {
79
+ const session = await loadSession(opts.platform ?? 'default').catch(() => null);
80
+ if (session?.cookies?.length)
51
81
  await context.addCookies(session.cookies).catch(() => { });
52
- }
53
82
  const page = await context.newPage();
54
83
  _browser = browser;
55
84
  _context = context;
56
85
  _page = page;
57
- // Persist state for reconnection
58
86
  await fs.mkdir(VEIL_DIR, { recursive: true });
59
- await fs.writeFile(STATE_FILE, JSON.stringify({
60
- platform,
61
- pid: process.pid,
62
- wsEndpoint: '',
63
- timestamp: Date.now(),
64
- }), 'utf-8').catch(() => { });
65
- // Cleanup state on exit
66
- const cleanup = () => {
67
- fs.rm(STATE_FILE).catch(() => { });
68
- _browser?.close().catch(() => { });
69
- };
70
- process.once('exit', cleanup);
71
- process.once('SIGINT', () => { cleanup(); process.exit(0); });
72
- process.once('SIGTERM', () => { cleanup(); process.exit(0); });
87
+ process.once('exit', () => { browser.close().catch(() => { }); });
73
88
  return { browser, context, page };
74
89
  }
75
90
  export async function getPage() {
76
- if (_page && !_page.isClosed())
77
- return _page;
78
- return null;
91
+ return (_page && !_page.isClosed()) ? _page : null;
79
92
  }
80
93
  export async function closeBrowser(_platform) {
81
94
  await _browser?.close().catch(() => { });
82
95
  _browser = null;
83
96
  _context = null;
84
97
  _page = null;
85
- await fs.rm(STATE_FILE).catch(() => { });
86
98
  }
87
99
  export function humanDelay(min = 400, max = 900) {
88
100
  return new Promise(r => setTimeout(r, Math.floor(Math.random() * (max - min) + min)));