@pablovitasso/szkrabok 1.0.10 → 1.0.13

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
@@ -28,9 +28,10 @@ MCP server supplementing [microsoft/playwright-mcp](https://github.com/microsoft
28
28
 
29
29
  | Use Case | Command |
30
30
  | --- | --- |
31
- | **Scaffolding a new project** | `npx @pablovitasso/szkrabok init` |
32
- | **Claude Code** | `claude mcp add szkrabok -- npx -y @pablovitasso/szkrabok` |
31
+ | **Claude Code global (all projects)** | `claude mcp add --scope user szkrabok -- npx -y @pablovitasso/szkrabok` |
32
+ | **Claude Code — this project only** | `claude mcp add szkrabok -- npx -y @pablovitasso/szkrabok` |
33
33
  | **Claude Desktop** | See config snippet below |
34
+ | **Scaffold a new project** | `npx @pablovitasso/szkrabok init` |
34
35
  | **Development (from source)** | `npm ci && claude mcp add szkrabok node /path/to/szkrabok/src/index.js` |
35
36
 
36
37
  Claude Desktop — add to `claude_desktop_config.json`:
@@ -45,8 +46,7 @@ Claude Desktop — add to `claude_desktop_config.json`:
45
46
  }
46
47
  ```
47
48
 
48
- > **Browser not installed?** Run `npx @pablovitasso/szkrabok --setup` once in your terminal, then restart Claude.
49
- > Set `CI=true` or `SZKRABOK_SKIP_BROWSER_INSTALL=1` to suppress the auto-install in CI / Docker.
49
+ > **Browser not found?** Run `szkrabok detect-browser` to find installed browsers, or `szkrabok install-browser` to install Playwright's Chromium. Requires **Node.js ≥ 20**.
50
50
 
51
51
  **2. Configure**
52
52
 
@@ -91,6 +91,9 @@ bebok session inspect <id> # Dump cookie/localStorage counts
91
91
  bebok session delete <id> # Delete a session
92
92
  bebok session cleanup --days 30 # Delete sessions unused for N days
93
93
  bebok endpoint <sessionName> # Print CDP + WS endpoints
94
+
95
+ szkrabok detect-browser # List usable Chrome/Chromium installations
96
+ szkrabok install-browser # Install Playwright's Chromium
94
97
  ```
95
98
 
96
99
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pablovitasso/szkrabok",
3
- "version": "1.0.10",
3
+ "version": "1.0.13",
4
4
  "description": "Production-grade MCP browser automation layer with persistent sessions and stealth capabilities",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -18,11 +18,11 @@
18
18
  "README.md"
19
19
  ],
20
20
  "bin": {
21
- "szkrabok": "./src/index.js",
22
- "bebok": "./src/cli.js"
21
+ "szkrabok": "src/index.js",
22
+ "bebok": "src/cli.js"
23
23
  },
24
24
  "engines": {
25
- "node": ">=18.0.0"
25
+ "node": ">=20.0.0"
26
26
  },
27
27
  "workspaces": [
28
28
  "packages/runtime"
@@ -37,10 +37,8 @@
37
37
  "format:check": "prettier --check \"{src,tests}/**/*.{js,json,md}\" \"*.{js,json,md}\"",
38
38
  "codegen:mcp": "node packages/runtime/mcp-client/codegen/generate-mcp-tools.mjs",
39
39
  "prepack": "git describe --exact-match --tags HEAD 2>/dev/null || (echo 'ERROR: HEAD is not tagged. Run npm run release:patch or release:minor first.' && exit 1)",
40
- "pack:runtime": "npm pack --workspace=packages/runtime --pack-destination=dist/",
41
- "pack": "mkdir -p dist && npm run pack:runtime",
42
- "release:patch": "npm version patch --workspaces --include-workspace-root --ignore-scripts && npm run pack && node scripts/release-reminder.js",
43
- "release:minor": "npm version minor --workspaces --include-workspace-root --ignore-scripts && npm run pack && node scripts/release-reminder.js",
40
+ "release:patch": "npm version patch --workspaces --include-workspace-root --ignore-scripts && node scripts/release-reminder.js",
41
+ "release:minor": "npm version minor --workspaces --include-workspace-root --ignore-scripts && node scripts/release-reminder.js",
44
42
  "release:publish": "node scripts/release-publish.js",
45
43
  "test": "npm run test:self && npm run test:auto",
46
44
  "test:node": "node --test tests/node/*.test.js",
@@ -1,6 +1,5 @@
1
- import { homedir } from 'os';
2
1
  import { join, resolve, dirname } from 'path';
3
- import { readdirSync, existsSync, readFileSync } from 'fs';
2
+ import { existsSync, readFileSync } from 'fs';
4
3
  import { fileURLToPath } from 'url';
5
4
  import { parse } from 'smol-toml';
6
5
 
@@ -134,40 +133,38 @@ export const TIMEZONE = defaults.timezone || 'America/New_York';
134
133
 
135
134
  // ── Chromium path resolution ────────────────────────────────────────────────
136
135
 
137
- export const findChromiumPath = () => {
138
- if (tomlDefault.executablePath) {
139
- return tomlDefault.executablePath;
140
- }
141
-
142
- const playwrightCache = join(homedir(), '.cache', 'ms-playwright');
143
-
144
- if (existsSync(playwrightCache)) {
145
- const dirs = readdirSync(playwrightCache)
146
- .filter(d => d.startsWith('chromium-'))
147
- .sort()
148
- .reverse();
149
-
150
- for (const dir of dirs) {
151
- const paths = [
152
- join(playwrightCache, dir, 'chrome-linux', 'chrome'),
153
- join(playwrightCache, dir, 'chrome-linux64', 'chrome'),
154
- ];
155
-
156
- for (const path of paths) {
157
- if (existsSync(path)) return path;
158
- }
136
+ // resolveBrowserPath pure, injectable. Exported for testing.
137
+ // finders: array of async () => string|null, tried in order.
138
+ export const resolveBrowserPath = async (finders) => {
139
+ for (const finder of finders) {
140
+ try {
141
+ const path = await finder();
142
+ if (path) return path;
143
+ } catch {
144
+ // finder unavailable — try next
159
145
  }
160
146
  }
147
+ return null;
148
+ };
161
149
 
162
- const systemChromiums = [
163
- '/usr/bin/chromium',
164
- '/usr/bin/chromium-browser',
165
- '/usr/bin/google-chrome',
166
- ];
167
-
168
- for (const path of systemChromiums) {
169
- if (existsSync(path)) return path;
150
+ export const findChromiumPath = async () => {
151
+ // 1. User-configured path (highest priority)
152
+ if (tomlDefault.executablePath) {
153
+ return tomlDefault.executablePath;
170
154
  }
171
155
 
172
- return null;
156
+ return resolveBrowserPath([
157
+ // 2. System Chrome/Chromium/Brave/Edge via chrome-launcher
158
+ async () => {
159
+ const { Launcher } = await import('chrome-launcher');
160
+ const installs = await Launcher.getInstallations();
161
+ return installs[0] ?? null;
162
+ },
163
+ // 3. Playwright bundled browser
164
+ async () => {
165
+ const { chromium } = await import('playwright');
166
+ const pwPath = chromium.executablePath();
167
+ return pwPath && existsSync(pwPath) ? pwPath : null;
168
+ },
169
+ ]);
173
170
  };
@@ -1,7 +1,7 @@
1
1
  // @szkrabok/runtime — public API
2
2
  // Do NOT expose: pool internals, storage internals, stealth utilities
3
3
 
4
- export { launch, connect } from './launch.js';
4
+ export { launch, connect, checkBrowser } from './launch.js';
5
5
  export { closeSession, getSession, listSessions as listRuntimeSessions, listStoredSessions, updateSessionMeta, deleteStoredSession, updateSessionPage, closeAllSessions } from './sessions.js';
6
6
  export { resolvePreset, PRESETS } from './config.js';
7
7
 
@@ -30,7 +30,7 @@ const cdpPortForId = id => {
30
30
  const _launchPersistentContext = async (userDataDir, options = {}) => {
31
31
  const presetConfig = options.presetConfig ?? {};
32
32
  const pw = options.stealth ? enhanceWithStealth(chromium, presetConfig) : chromium;
33
- const executablePath = findChromiumPath();
33
+ const executablePath = await findChromiumPath();
34
34
 
35
35
  if (executablePath) {
36
36
  log('Using existing Chromium for persistent context', { path: executablePath });
@@ -90,9 +90,25 @@ const _launchPersistentContext = async (userDataDir, options = {}) => {
90
90
  * @param {boolean} [options.reuse] Return existing if profile already open (default: true)
91
91
  * @returns {Promise<{ browser: import('playwright').Browser, context: import('playwright').BrowserContext, cdpEndpoint: string, close(): Promise<void> }>}
92
92
  */
93
+ export const checkBrowser = async () => {
94
+ const found = await findChromiumPath();
95
+ if (!found) {
96
+ throw new Error(
97
+ 'Chromium browser not found.\n\n' +
98
+ 'Run:\n' +
99
+ ' npx playwright install chromium\n\n' +
100
+ 'Or:\n' +
101
+ ' szkrabok install-browser\n'
102
+ );
103
+ }
104
+ return found;
105
+ };
106
+
93
107
  export const launch = async (options = {}) => {
94
108
  const { profile = 'default', preset: presetName, headless, stealth, userAgent, viewport, locale, timezone, reuse = true } = options;
95
109
 
110
+ await checkBrowser();
111
+
96
112
  // Idempotency: return existing handle when reuse=true and profile is open
97
113
  if (reuse && pool.has(profile)) {
98
114
  log(`Reusing existing session: ${profile}`);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@szkrabok/runtime",
3
- "version": "1.0.10",
3
+ "version": "1.0.13",
4
4
  "description": "szkrabok — browser bootstrap, stealth, session pool, MCP client.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -14,8 +14,12 @@
14
14
  "scripts": {
15
15
  "postinstall": "node scripts/patch-playwright.js"
16
16
  },
17
+ "engines": {
18
+ "node": ">=20.0.0"
19
+ },
17
20
  "dependencies": {
18
21
  "@modelcontextprotocol/sdk": ">=1.0.0",
22
+ "chrome-launcher": "*",
19
23
  "playwright": "*",
20
24
  "playwright-core": "*",
21
25
  "playwright-extra": "*",
@@ -1,19 +1,18 @@
1
- import { readFileSync } from 'node:fs';
2
1
  import { execSync } from 'node:child_process';
2
+ import { readFileSync } from 'node:fs';
3
3
  import { resolve, dirname } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
 
6
6
  const pkg = JSON.parse(readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), '../package.json'), 'utf8'));
7
7
  const v = pkg.version;
8
- const tarball = `dist/szkrabok-runtime-${v}.tgz`;
9
8
 
10
9
  try {
11
- execSync(`gh auth status`, { stdio: 'pipe' });
10
+ execSync('npm whoami', { stdio: 'pipe' });
12
11
  } catch {
13
- console.error('ERROR: gh auth failed. Run: gh auth login');
12
+ console.error('ERROR: not logged in to npm. Run: npm login');
14
13
  process.exit(1);
15
14
  }
16
15
 
17
- console.log(`Creating GitHub release v${v} and uploading ${tarball}...`);
18
- execSync(`gh release create v${v} ${tarball} --repo PabloVitasso/szkrabok --title v${v}`, { stdio: 'inherit' });
19
- console.log(`\nDone. Remember to update RUNTIME_RELEASES in src/tools/scaffold.js.`);
16
+ console.log(`Publishing @pablovitasso/szkrabok@${v} to npm...`);
17
+ execSync('npm publish --access public', { stdio: 'inherit' });
18
+ console.log(`\nDone. https://www.npmjs.com/package/@pablovitasso/szkrabok`);
@@ -6,9 +6,8 @@ const pkg = JSON.parse(readFileSync(resolve(dirname(fileURLToPath(import.meta.ur
6
6
  const v = pkg.version;
7
7
 
8
8
  console.log(`
9
- Pack complete: dist/szkrabok-runtime-${v}.tgz
9
+ Version bumped to ${v}.
10
10
 
11
11
  Next steps:
12
- 1. npm run release:publish
13
- 2. Update RUNTIME_RELEASES in src/tools/scaffold.js (add '${v}' entry, bump CURRENT_RUNTIME_VERSION)
12
+ npm run release:publish
14
13
  `);
package/src/cli.js CHANGED
@@ -163,4 +163,34 @@ program
163
163
  }
164
164
  });
165
165
 
166
+ /* ---------- detect-browser command ---------- */
167
+
168
+ program
169
+ .command('detect-browser')
170
+ .description('Detect usable Chrome/Chromium browsers')
171
+ .action(async () => {
172
+ const { findChromiumPath } = await import('#runtime');
173
+ const path = await findChromiumPath();
174
+ if (!path) {
175
+ console.log('No usable Chrome/Chromium browser found.\n');
176
+ console.log(' szkrabok install-browser');
177
+ console.log(' https://www.google.com/chrome/\n');
178
+ process.exit(1);
179
+ }
180
+ console.log(path);
181
+ console.log('\nRecommended configuration:\n\n[default]');
182
+ console.log(`executablePath = "${path}"`);
183
+ });
184
+
185
+ /* ---------- install-browser command ---------- */
186
+
187
+ program
188
+ .command('install-browser')
189
+ .description('Install Chromium browser via Playwright')
190
+ .action(async () => {
191
+ const { spawn } = await import('node:child_process');
192
+ const proc = spawn('npx', ['playwright', 'install', 'chromium'], { stdio: 'inherit' });
193
+ proc.on('close', code => process.exit(code ?? 0));
194
+ });
195
+
166
196
  program.parse();
@@ -2,8 +2,7 @@
2
2
  # Machine-specific overrides. Copy to szkrabok.config.local.toml and edit.
3
3
  # This file is gitignored — never commit credentials or paths here.
4
4
  #
5
- # Run `bash node_modules/@szkrabok/runtime/scripts/detect_browsers.sh`
6
- # to find your Chrome/Chromium binary path.
5
+ # Run `szkrabok detect-browser` to find Chrome/Chromium binary paths.
7
6
 
8
7
  [default]
9
8
  # executablePath = "/path/to/chrome"
@@ -1,147 +0,0 @@
1
- #!/usr/bin/env bash
2
- # detect_browsers.sh — find Chrome/Chromium binaries and echo szkrabok.config.toml lines
3
- #
4
- # Usage:
5
- # bash scripts/detect_browsers.sh
6
- #
7
- # Output:
8
- # For each detected browser: its version + the TOML executablePath line to use.
9
- # If nothing useful is found: installation suggestions.
10
- #
11
- # Copy the executablePath line you want into szkrabok.config.toml [default] section.
12
- # Google Chrome stable is best for stealth (native brands). Ungoogled-chromium is a
13
- # good second choice. The Playwright bundled binary (Chrome for Testing) should be
14
- # avoided in production — it brands itself as automation tooling.
15
-
16
- set -euo pipefail
17
-
18
- FOUND=()
19
-
20
- # ── helpers ───────────────────────────────────────────────────────────────────
21
-
22
- get_version() {
23
- # Run binary with --version, strip leading "Chromium ", "Google Chrome " etc.
24
- timeout 5 "$1" --version 2>/dev/null | head -1 | sed 's/^[^0-9]*//' || true
25
- }
26
-
27
- check_binary() {
28
- local label="$1" path="$2"
29
- if [[ -x "$path" ]]; then
30
- local ver
31
- ver="$(get_version "$path")"
32
- if [[ -n "$ver" ]]; then
33
- echo " FOUND $label — $ver"
34
- echo " executablePath = \"$path\""
35
- echo ""
36
- FOUND+=("$label")
37
- fi
38
- fi
39
- }
40
-
41
- check_flatpak() {
42
- local label="$1" app_id="$2"
43
- if command -v flatpak >/dev/null 2>&1 && flatpak info "$app_id" >/dev/null 2>&1; then
44
- local ver
45
- ver="$(timeout 5 flatpak run "$app_id" --version 2>/dev/null | head -1 | sed 's/^[^0-9]*//' || true)"
46
- if [[ -n "$ver" ]]; then
47
- # Write a small wrapper path check — flatpak run is the executable path
48
- local wrapper
49
- wrapper="$(command -v flatpak)"
50
- echo " FOUND $label — $ver"
51
- echo " # flatpak binary — use a wrapper script or the flatpak run command:"
52
- echo " executablePath = \"$HOME/.local/bin/$(echo "$app_id" | tr '.' '-' | tr '[:upper:]' '[:lower:]')\""
53
- echo " # (create that wrapper: echo '#!/bin/sh' > the path, then:"
54
- echo " # echo 'exec flatpak run $app_id \"\$@\"' >> the path, chmod +x)"
55
- echo ""
56
- FOUND+=("$label")
57
- fi
58
- fi
59
- }
60
-
61
- check_snap() {
62
- local label="$1" snap_name="$2" snap_bin="$3"
63
- if [[ -x "$snap_bin" ]]; then
64
- local ver
65
- ver="$(get_version "$snap_bin")"
66
- if [[ -n "$ver" ]]; then
67
- echo " FOUND $label — $ver"
68
- echo " executablePath = \"$snap_bin\""
69
- echo ""
70
- FOUND+=("$label")
71
- fi
72
- fi
73
- }
74
-
75
- # ── scan ──────────────────────────────────────────────────────────────────────
76
-
77
- echo ""
78
- echo "=== szkrabok browser detection ==="
79
- echo ""
80
- echo "Scanning for usable Chrome/Chromium binaries..."
81
- echo ""
82
-
83
- # Google Chrome stable (best for stealth — native brands)
84
- check_binary "Google Chrome stable" "/usr/bin/google-chrome"
85
- check_binary "Google Chrome stable" "/usr/bin/google-chrome-stable"
86
- check_binary "Google Chrome stable" "/opt/google/chrome/chrome"
87
- check_binary "Google Chrome stable" "/opt/google/chrome/google-chrome"
88
-
89
- # Chromium (distro package)
90
- check_binary "Chromium (distro)" "/usr/bin/chromium"
91
- check_binary "Chromium (distro)" "/usr/bin/chromium-browser"
92
-
93
- # Ungoogled-chromium — common wrapper locations
94
- check_binary "Ungoogled-chromium" "$HOME/.local/bin/ungoogled-chromium"
95
- check_binary "Ungoogled-chromium" "/usr/bin/ungoogled-chromium"
96
-
97
- # Ungoogled-chromium via flatpak — only if no wrapper binary was found above
98
- if [[ ! -x "$HOME/.local/bin/ungoogled-chromium" && ! -x "/usr/bin/ungoogled-chromium" ]]; then
99
- check_flatpak "Ungoogled-chromium (flatpak)" "io.github.ungoogled_software.ungoogled_chromium"
100
- fi
101
-
102
- # Chromium via snap
103
- check_snap "Chromium (snap)" "chromium" "/snap/bin/chromium"
104
-
105
- # Brave
106
- check_binary "Brave" "/usr/bin/brave-browser"
107
- check_binary "Brave" "/usr/bin/brave-browser-stable"
108
- check_binary "Brave" "/opt/brave.com/brave/brave"
109
-
110
- # Microsoft Edge (Chromium-based)
111
- check_binary "Microsoft Edge" "/usr/bin/microsoft-edge"
112
- check_binary "Microsoft Edge" "/usr/bin/microsoft-edge-stable"
113
- check_binary "Microsoft Edge" "/opt/microsoft/msedge/msedge"
114
-
115
- # ── results ───────────────────────────────────────────────────────────────────
116
-
117
- if [[ ${#FOUND[@]} -eq 0 ]]; then
118
- echo " NONE FOUND — no usable Chrome/Chromium binary detected."
119
- echo ""
120
- echo " szkrabok will fall back to the Playwright bundled 'Chrome for Testing'."
121
- echo " This works but is detectable as automation tooling (brands itself as"
122
- echo " 'Chrome for Testing' in navigator.userAgentData)."
123
- echo ""
124
- echo " Recommended installations:"
125
- echo ""
126
- echo " 1. Google Chrome stable (best stealth — native brands):"
127
- echo " https://www.google.com/chrome/"
128
- echo " wget -qO- https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -"
129
- echo " sudo sh -c 'echo \"deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\" > /etc/apt/sources.list.d/google-chrome.list'"
130
- echo " sudo apt-get update && sudo apt-get install google-chrome-stable"
131
- echo ""
132
- echo " 2. Ungoogled-chromium via flatpak (no Google services, good stealth):"
133
- echo " flatpak install flathub io.github.ungoogled_software.ungoogled_chromium"
134
- echo " # then create a wrapper: ~/.local/bin/ungoogled-chromium"
135
- echo " # with: exec flatpak run io.github.ungoogled_software.ungoogled_chromium \"\$@\""
136
- echo ""
137
- else
138
- echo " Copy one executablePath line above into szkrabok.config.toml [default]."
139
- echo ""
140
- echo " Stealth ranking (best first):"
141
- echo " 1. Google Chrome stable — native 'Google Chrome' brands, most trusted"
142
- echo " 2. Ungoogled-chromium — no Google services; needs greasy brands patch"
143
- echo " 3. Chromium (distro) — same as ungoogled; needs greasy brands patch"
144
- echo " 4. Chrome for Testing — avoid in production (automation-branded)"
145
- fi
146
-
147
- echo ""