stealth-cli 0.5.1 → 0.6.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.
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Shared browser bootstrap utilities
3
+ * Eliminates duplication across browser.js, daemon.js, serve.js, mcp-server.js
4
+ */
5
+
6
+ import os from 'os';
7
+ import { launchOptions } from 'camoufox-js';
8
+ import { firefox } from 'playwright-core';
9
+
10
+ /**
11
+ * Detect host OS for Camoufox fingerprint matching
12
+ */
13
+ export function getHostOS() {
14
+ const platform = os.platform();
15
+ if (platform === 'darwin') return 'macos';
16
+ if (platform === 'win32') return 'windows';
17
+ return 'linux';
18
+ }
19
+
20
+ /**
21
+ * Launch a Camoufox browser instance with standard settings
22
+ *
23
+ * @param {object} opts
24
+ * @param {boolean} [opts.headless=true]
25
+ * @param {string} [opts.os] - Override OS (default: auto-detect)
26
+ * @param {object} [opts.proxy] - { server, username?, password? }
27
+ * @returns {Promise<import('playwright-core').Browser>}
28
+ */
29
+ export async function createBrowser(opts = {}) {
30
+ const {
31
+ headless = true,
32
+ os: targetOS,
33
+ proxy,
34
+ } = opts;
35
+
36
+ const options = await launchOptions({
37
+ headless,
38
+ os: targetOS || getHostOS(),
39
+ // Camoufox's `humanize` controls low-level fingerprint randomization (canvas noise, etc.)
40
+ // This is NOT the same as stealth-cli's --humanize flag (which controls mouse/scroll/type simulation).
41
+ // Always enabled for anti-detection effectiveness.
42
+ humanize: true,
43
+ enable_cache: true,
44
+ proxy: proxy || undefined,
45
+ geoip: !!proxy,
46
+ });
47
+
48
+ return firefox.launch(options);
49
+ }
50
+
51
+ /**
52
+ * Create a standard browser context
53
+ *
54
+ * @param {import('playwright-core').Browser} browser
55
+ * @param {object} opts
56
+ * @returns {Promise<import('playwright-core').BrowserContext>}
57
+ */
58
+ export async function createContext(browser, opts = {}) {
59
+ const {
60
+ locale = 'en-US',
61
+ timezone = 'America/Los_Angeles',
62
+ viewport = { width: 1280, height: 720 },
63
+ geo = { latitude: 37.7749, longitude: -122.4194 },
64
+ } = opts;
65
+
66
+ return browser.newContext({
67
+ viewport,
68
+ locale,
69
+ timezoneId: timezone,
70
+ permissions: ['geolocation'],
71
+ geolocation: geo,
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Extract visible text from a page.
77
+ * Pass directly to page.evaluate() — must not reference Node.js scope.
78
+ * Using a function (not a string) avoids eval-injection risks.
79
+ */
80
+ export function extractPageText() {
81
+ const body = document.body;
82
+ if (!body) return '';
83
+ const clone = body.cloneNode(true);
84
+ clone.querySelectorAll('script, style, noscript').forEach(el => el.remove());
85
+ return clone.innerText || clone.textContent || '';
86
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Merge global config with CLI options.
3
+ * Priority: CLI explicit opts > global config > defaults
4
+ *
5
+ * Special handling for Commander's --no-xxx pattern:
6
+ * Commander sets `headless: true` by default even when user didn't pass --headless.
7
+ * We can't tell "default true" from "explicit --headless". So when the CLI value
8
+ * matches Commander's auto-default AND global config has a different value,
9
+ * we let global config win.
10
+ */
11
+
12
+ import { loadConfig, DEFAULTS } from '../config.js';
13
+
14
+ // Commander auto-defaults for --no-xxx flags.
15
+ // When CLI value === auto-default, it may be Commander's default, not user intent.
16
+ const COMMANDER_AUTO_DEFAULTS = {
17
+ headless: true,
18
+ };
19
+
20
+ // Keys from config.js DEFAULTS that map to CLI option names
21
+ const CONFIG_TO_CLI = {
22
+ headless: 'headless',
23
+ locale: 'locale',
24
+ timezone: 'timezone',
25
+ timeout: 'timeout',
26
+ retries: 'retries',
27
+ humanize: 'humanize',
28
+ delay: 'delay',
29
+ format: 'format',
30
+ proxy: 'proxy',
31
+ viewportWidth: 'viewportWidth',
32
+ viewportHeight: 'viewportHeight',
33
+ };
34
+
35
+ // Numeric fields that Commander passes as strings
36
+ const NUMERIC_KEYS = [
37
+ 'retries', 'timeout', 'delay', 'viewportWidth', 'viewportHeight',
38
+ 'wait', 'depth', 'limit', 'interval', 'count', 'port', 'num', 'width', 'height',
39
+ ];
40
+
41
+ /**
42
+ * Resolve final options by merging:
43
+ * 1. Built-in defaults (from config.js DEFAULTS)
44
+ * 2. User's global config (~/.stealth/config.json)
45
+ * 3. CLI arguments (highest priority)
46
+ *
47
+ * @param {object} cliOpts - Options from Commander action
48
+ * @returns {object} Merged options
49
+ */
50
+ export function resolveOpts(cliOpts = {}) {
51
+ const globalConfig = loadConfig(); // merges DEFAULTS + user config file
52
+
53
+ // Start with global config values
54
+ const merged = {};
55
+ for (const [configKey, cliKey] of Object.entries(CONFIG_TO_CLI)) {
56
+ merged[cliKey] = globalConfig[configKey];
57
+ }
58
+
59
+ // Overlay CLI options (only if explicitly provided)
60
+ for (const [key, value] of Object.entries(cliOpts)) {
61
+ if (value === undefined) continue;
62
+
63
+ // Handle Commander auto-defaults: if CLI value matches the auto-default
64
+ // and user has set a different value in global config, let config win.
65
+ if (key in COMMANDER_AUTO_DEFAULTS && value === COMMANDER_AUTO_DEFAULTS[key]) {
66
+ if (merged[key] !== undefined && merged[key] !== DEFAULTS[key]) {
67
+ continue; // Global config has a user-set value — don't overwrite with auto-default
68
+ }
69
+ }
70
+
71
+ merged[key] = value;
72
+ }
73
+
74
+ // Coerce numeric fields (Commander passes CLI values as strings)
75
+ for (const key of NUMERIC_KEYS) {
76
+ if (typeof merged[key] === 'string') {
77
+ const n = parseInt(merged[key], 10);
78
+ if (!isNaN(n)) merged[key] = n;
79
+ }
80
+ }
81
+
82
+ return merged;
83
+ }