camoufox-playwright-cli 0.1.0 → 0.2.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/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -1,63 +1,81 @@
1
1
  # camoufox-playwright-cli
2
2
 
3
- Official [`@playwright/cli`](https://github.com/microsoft/playwright-cli) (Microsoft's Playwright CLI — open, click, fill, snapshot, tabs, network, tracing, video, ...), driven by [Camoufox](https://camoufox.com) instead of stock Chromium/Firefox, so the browser doesn't look automated.
3
+ Camoufox configuration and fingerprint management for the official [Playwright CLI](https://github.com/microsoft/playwright-cli). No fork, protocol patches, command rewriting, or replacement `playwright-cli` executable.
4
4
 
5
- No fork of playwright-cli, no monkey-patching. Camoufox's stealth is just three standard Playwright launch options — `executablePath`, `firefoxUserPrefs`, and a `CAMOU_CONFIG_*` env blob — and playwright-cli already passes those through from a config file. This package generates that config for you and manages it as named **profiles**.
6
-
7
- Short alias: `cpw` (same binary as `camoufox-playwright-cli`).
5
+ Requires Node.js 22 or newer.
8
6
 
9
7
  ## Install
10
8
 
11
- ```bash
9
+ ```sh
10
+ npm install -g @playwright/cli@0.1.12
12
11
  npm install -g camoufox-playwright-cli
13
12
  cpw install
13
+ cpw doctor
14
+ playwright-cli open https://example.com
14
15
  ```
15
16
 
16
- `install` downloads the Camoufox browser binary (shared cache, only happens once even across other Camoufox-based tools) and generates a `default` profile.
17
-
18
- ## Profiles
17
+ If you already have the official CLI, check its version before installing. `cpw` never installs or overwrites another CLI automatically. Only the validated version is accepted for setup and session cleanup; other versions remain usable directly at your own discretion.
19
18
 
20
- A profile is a fingerprint identity + its own isolated browser data (cookies, localStorage, login sessions) think of it as "one persona". The `default` profile is created automatically and its fingerprint is generated from **your actual machine** where possible:
19
+ `cpw install` downloads Camoufox, creates the default fingerprint, and sets it as the official CLI's global default in `~/.playwright/cli.config.json`. Existing unrelated settings are preserved. Browser launch settings are replaced together, including the executable, Firefox preferences and fingerprint environment. Remote browser and persistent-directory defaults are cleared to enable local Camoufox launches. This affects new CLI browser sessions, not already running browsers or ordinary Playwright libraries.
21
20
 
22
- - Real GPU vendor (via `Get-CimInstance Win32_VideoController` on Windows) — matched to the closest vendor bucket in Camoufox's curated WebGL database. Camoufox only ships a fixed, vetted set of older reference GPU models per vendor (no RTX 20/30/40-series entries at all, for example), so the *vendor* will match your real hardware but the exact renderer string won't be your literal card model. That vendor-match is what fingerprinting scripts actually cross-check; matching the exact model isn't possible through Camoufox's public API without maintaining a separate spoofing database, which is out of scope here.
23
- - Real screen resolution, locale, and timezone (via `Intl` + WMI).
21
+ Project configuration, environment variables and explicit CLI flags can override the global default. `cpw doctor` reports project configuration and environment override names without printing environment values.
24
22
 
25
- If any of these can't be detected (non-Windows, sandboxed environment, etc.) that field silently falls back to Camoufox's own randomized default — it never errors out.
23
+ ## Profiles
26
24
 
27
- ```bash
25
+ ```sh
28
26
  cpw profile list
29
27
  cpw profile create work
30
- cpw profile rotate work # wipes cookies + regenerates fingerprint
28
+ cpw profile use work
29
+ cpw profile path work
30
+ cpw profile rotate work
31
31
  cpw profile delete work
32
32
  ```
33
33
 
34
- Rotating or deleting a profile always wipes its browser session data first keeping old cookies with a new fingerprint is itself an inconsistency signal, so we don't allow that combination.
34
+ `use` changes the global default for new sessions. Close existing sessions before switching identities. An active global profile cannot be deleted; switch to another profile first. Rotating the active profile updates its global launch settings too.
35
35
 
36
- ## Usage
36
+ For simultaneous identities, use an explicit config on each `open`:
37
37
 
38
- Everything besides `install` and `profile` is forwarded verbatim to the real `playwright-cli`, with `--profile` mapped to its `-s=` session flag and (for `open`) `--config=` pointed at that profile's generated config:
38
+ ```sh
39
+ playwright-cli -s=work open https://example.com --config=/absolute/path/from-profile-path
40
+ playwright-cli -s=work snapshot
41
+ playwright-cli -s=work close
42
+ ```
39
43
 
40
- ```bash
41
- cpw open https://example.com
42
- cpw snapshot
43
- cpw click e3
44
- cpw fill e5 "hello"
45
- cpw screenshot --filename out.png
46
- cpw close
44
+ Fingerprint names and CLI session names are independent. `rotate` and `delete` only clear the same-named CLI session in the current workspace. Before using them, close and clear any other sessions that use the fingerprint, including other workspaces and custom persistent data directories. The tool cannot infer every use of an exported config.
47
45
 
48
- cpw --profile=work open https://example.com # separate identity + separate cookies
49
- ```
46
+ ## Migration from 0.1
47
+
48
+ Browser commands now go directly to `playwright-cli`; `cpw` only manages Camoufox. Existing fingerprint files remain usable. Run `cpw profile use default` to activate an existing profile without fetching a browser again.
50
49
 
51
- See the [playwright-cli command reference](https://github.com/microsoft/playwright-cli) for the full list tabs, network routing, storage state, tracing, video recording, etc. all work unmodified.
50
+ `--profile` has its official meaning: a persistent browser data directory. It no longer selects a Camoufox fingerprint. Use `cpw profile use` or `--config` instead. Official help, errors, skills and command prefixes remain unchanged.
52
51
 
53
- ## How it works
52
+ ## Compatibility
54
53
 
55
- - [`camoufox-js`](https://github.com/apify/camoufox-js)'s `launchOptions()` computes `{executablePath, env, firefoxUserPrefs}` for a given fingerprint.
56
- - We write that into `~/.camoufox-playwright-cli/profiles/<name>/cli.config.json`, in the shape `@playwright/cli` expects (`browser.launchOptions`).
57
- - `cpw <command>` is a thin wrapper: it ensures that config exists, then spawns the real `playwright-cli` binary with `-s=<profile>` (and `--config=` on `open`) injected.
54
+ Validated locally on Windows with Node.js 24.12.0:
55
+
56
+ | Component | Version |
57
+ | --- | --- |
58
+ | Official CLI | 0.1.12 |
59
+ | CLI's Playwright driver | 1.60.0-alpha-1778101408000 |
60
+ | camoufox-js | 0.12.0 |
61
+ | Camoufox browser | 152.0.4-beta.28 |
62
+ | better-sqlite3 | 13.0.3 |
63
+
64
+ CLI 0.1.19 was tested and rejected: its 1.63 alpha driver sends `viewport.isMobile`, which this Camoufox binary rejects when creating a page. `camoufox-js@0.12.0` declares `playwright-core <1.61.0`. CLI 0.1.12 is the newest published CLI whose declared driver is in the 1.60 line. This is a deliberate compatibility downgrade from the previous 0.1.18, not an upgrade to the latest CLI.
65
+
66
+ Also validated on WSL Linux with Node.js 24.13.0, the same npm dependency versions, and Camoufox `152.0.4-beta.30`: configuration tests, browser interactions/context isolation, and official CLI persistent-cookie restart tests all passed. Install dependencies natively in WSL; do not reuse Windows `node_modules` or Windows executable paths.
67
+
68
+ No transitive driver overrides or browser patches are used. Future CLI or browser updates require retesting; a pinned npm package does not pin the separately downloaded browser binary. The official CLI is a development dependency here for reproducible tests, and a separate installation for normal use.
69
+
70
+ ```sh
71
+ npm ci
72
+ npm test
73
+ npm run test:browser
74
+ npm run test:cli
75
+ ```
58
76
 
59
- Dependency versions (`@playwright/cli`, `camoufox-js`) are pinned exactly, not range-matched both are young/prerelease packages and an unpinned `npm install` could silently pick up a breaking protocol change between Playwright's internal Firefox/Juggler driver and Camoufox's patched binary. Upgrades are deliberate, one version bump at a time, tested against the checks in this README before being released.
77
+ Browser tests use generated HTML and a fresh browser context, not personal content. They check launch, input, click, accessibility text and context isolation. The CLI test additionally checks global configuration, leading session flags, persistent cookies across restart and upstream error help. These checks do not establish compatibility for every CLI feature or operating system.
60
78
 
61
- ## Security note
79
+ ## Fingerprints
62
80
 
63
- The `env` passed to the Camoufox process is an explicit allowlist (`PATH`, temp dirs, etc.) never your full shell environment. Don't widen it without checking what secrets might be sitting in `process.env`.
81
+ Profiles live in `~/.camoufox-playwright-cli/profiles/<name>/cli.config.json`. Host OS, locale, screen and a vendor-matched WebGL identity feed Camoufox's fingerprint generator. Browser launch environment variables use an explicit allowlist; the full shell environment is never serialized into profile files.
package/bin/cli.js CHANGED
@@ -1,172 +1,181 @@
1
1
  #!/usr/bin/env node
2
- import { spawnSync } from "node:child_process";
3
- import {
4
- DEFAULT_PROFILE,
5
- ensureProfile,
6
- profileConfigPath,
7
- profileExists,
8
- listProfiles,
9
- deleteProfileFiles,
10
- } from "../src/profiles.js";
11
- import { playwrightCliBin, camoufoxJsBin } from "../src/paths.js";
12
-
13
- function extractProfileFlag(argv) {
14
- let profile = DEFAULT_PROFILE;
15
- const rest = [];
16
- for (const arg of argv) {
17
- if (arg.startsWith("--profile=")) {
18
- profile = arg.slice("--profile=".length);
19
- } else {
20
- rest.push(arg);
21
- }
22
- }
23
- return { profile, rest };
24
- }
25
-
26
- async function main() {
27
- const { profile, rest } = extractProfileFlag(process.argv.slice(2));
28
- const [command, ...args] = rest;
29
-
30
- switch (command) {
31
- case "install":
32
- return runInstall();
33
- case "profile":
34
- return runProfile(args);
35
- case undefined:
36
- case "-h":
37
- case "--help":
38
- return printHelp();
39
- default:
40
- return runPlaywrightCli(profile, rest);
41
- }
42
- }
43
-
44
- function runCamoufoxFetch() {
45
- const result = spawnSync(process.execPath, [camoufoxJsBin(), "fetch"], { stdio: "inherit" });
46
- if (result.status !== 0) process.exit(result.status ?? 1);
47
- }
48
-
49
- // Stops the daemon (if running) and wipes its browser profile dir (cookies,
50
- // storage). Used before rotate/delete so a new fingerprint never keeps the
51
- // old identity's login state — a fingerprint change with stale cookies is
52
- // itself an inconsistency signal.
53
- function resetSessionData(profileName) {
54
- spawnSync(process.execPath, [playwrightCliBin(), `-s=${profileName}`, "close"], { stdio: "ignore" });
55
- spawnSync(process.execPath, [playwrightCliBin(), `-s=${profileName}`, "delete-data"], { stdio: "ignore" });
56
- }
57
-
58
- async function runInstall() {
59
- console.log("Downloading Camoufox browser binary (this only needs to run once)...");
60
- runCamoufoxFetch();
61
-
62
- console.log("\nGenerating default profile from your machine's real parameters...");
63
- const file = await ensureProfile(DEFAULT_PROFILE);
64
- console.log(`Profile written to ${file}`);
65
- console.log("\nDone. Try: camoufox-playwright-cli open https://example.com");
66
- }
67
-
68
- async function runProfile(args) {
69
- const [sub, name] = args;
70
- switch (sub) {
71
- case "list": {
72
- const profiles = await listProfiles();
73
- if (profiles.length === 0) {
74
- console.log("No profiles yet. Run `camoufox-playwright-cli install` first.");
75
- return;
76
- }
77
- for (const p of profiles) {
78
- console.log(`${p.name}${p.name === DEFAULT_PROFILE ? " (default)" : ""}`);
79
- console.log(` created: ${p.createdAt}`);
80
- console.log(` os: ${p.os} locale: ${p.locale} timezone: ${p.timezone}`);
81
- console.log(` screen: ${JSON.stringify(p.screen)}`);
82
- console.log(` gpu: ${JSON.stringify(p.gpu)}`);
83
- }
84
- return;
85
- }
86
- case "create": {
87
- if (!name) return console.error("Usage: camoufox-playwright-cli profile create <name>");
88
- if (profileExists(name)) return console.error(`Profile "${name}" already exists. Use \`profile rotate ${name}\` to regenerate it.`);
89
- const file = await ensureProfile(name);
90
- console.log(`Created profile "${name}" -> ${file}`);
91
- return;
92
- }
93
- case "rotate": {
94
- const target = name || DEFAULT_PROFILE;
95
- if (!profileExists(target)) return console.error(`Profile "${target}" does not exist. Use \`profile create ${target}\` first.`);
96
- resetSessionData(target);
97
- const file = await ensureProfile(target, { rotate: true });
98
- console.log(`Rotated profile "${target}" (browser data wiped, new fingerprint generated) -> ${file}`);
99
- return;
100
- }
101
- case "delete": {
102
- if (!name) return console.error("Usage: camoufox-playwright-cli profile delete <name>");
103
- if (name === DEFAULT_PROFILE) return console.error(`Refusing to delete the "${DEFAULT_PROFILE}" profile. Use \`profile rotate\` instead.`);
104
- resetSessionData(name);
105
- const existed = await deleteProfileFiles(name);
106
- console.log(existed ? `Deleted profile "${name}"` : `Profile "${name}" did not exist`);
107
- return;
108
- }
109
- default:
110
- console.log(`Usage:
111
- camoufox-playwright-cli profile list
112
- camoufox-playwright-cli profile create <name>
113
- camoufox-playwright-cli profile rotate [name] (defaults to "default")
114
- camoufox-playwright-cli profile delete <name>`);
115
- }
116
- }
117
-
118
- async function runPlaywrightCli(profile, argv) {
119
- if (!profileExists(profile)) {
120
- if (profile === DEFAULT_PROFILE) {
121
- console.log("No default profile yet - running setup first...");
122
- runCamoufoxFetch();
123
- await ensureProfile(DEFAULT_PROFILE);
124
- } else {
125
- console.error(`Profile "${profile}" does not exist. Create it with: camoufox-playwright-cli profile create ${profile}`);
126
- process.exit(1);
127
- }
128
- }
129
-
130
- const configFile = profileConfigPath(profile);
131
- const finalArgs = [...argv];
132
-
133
- if (!finalArgs.some((a) => a === "-s" || a.startsWith("-s="))) {
134
- finalArgs.push(`-s=${profile}`);
135
- }
136
- if (finalArgs[0] === "open" && !finalArgs.some((a) => a.startsWith("--config"))) {
137
- finalArgs.push(`--config=${configFile}`);
138
- }
139
-
140
- const result = spawnSync(process.execPath, [playwrightCliBin(), ...finalArgs], { stdio: "inherit" });
141
- process.exit(result.status ?? 0);
142
- }
143
-
144
- function printHelp() {
145
- console.log(`camoufox-playwright-cli (alias: cpw) - Playwright CLI (@playwright/cli), driven by Camoufox (stealth Firefox)
146
-
147
- Setup (run once):
148
- cpw install Download Camoufox + generate the "default" profile
149
-
150
- Profiles (fingerprint + isolated browser data, one per identity):
151
- cpw profile list
152
- cpw profile create <name>
153
- cpw profile rotate [name] Wipe cookies + regenerate fingerprint (defaults to "default")
154
- cpw profile delete <name>
155
-
156
- Everything else is forwarded to the real playwright-cli, pointed at Camoufox.
157
- Use --profile=<name> to pick which identity to drive (defaults to "default"):
158
- cpw open <url>
159
- cpw --profile=work open <url>
160
- cpw snapshot
161
- cpw click <ref>
162
- cpw fill <ref> <text>
163
- cpw screenshot
164
- cpw close
165
-
166
- ("camoufox-playwright-cli" also works everywhere "cpw" does same binary, two names.)
167
-
168
- See https://github.com/microsoft/playwright-cli for the full command reference.
169
- `);
170
- }
171
-
172
- main();
2
+ import { spawnSync } from "node:child_process";
3
+ import {
4
+ DEFAULT_PROFILE,
5
+ ensureProfile,
6
+ profileConfigPath,
7
+ profileExists,
8
+ listProfiles,
9
+ deleteProfileFiles,
10
+ readProfileConfig,
11
+ } from "../src/profiles.js";
12
+ import { camoufoxJsBin } from "../src/paths.js";
13
+ import { activateProfile, readJson, globalConfigPath } from "../src/config.js";
14
+ import { installedCLI, requireCompatibleCLI, RECOMMENDED_CLI } from "../src/compatibility.js";
15
+ import { isDeepStrictEqual } from "node:util";
16
+ import { existsSync } from "node:fs";
17
+ import path from "node:path";
18
+
19
+ async function main() {
20
+ const [command, ...args] = process.argv.slice(2);
21
+
22
+ switch (command) {
23
+ case "install":
24
+ return runInstall();
25
+ case "doctor":
26
+ return runDoctor();
27
+ case "profile":
28
+ return runProfile(args);
29
+ case undefined:
30
+ case "-h":
31
+ case "--help":
32
+ return printHelp();
33
+ default:
34
+ throw new Error("Browser commands now use official playwright-cli. Run playwright-cli --help.");
35
+ }
36
+ }
37
+
38
+ function runCamoufoxFetch() {
39
+ const result = spawnSync(process.execPath, [camoufoxJsBin(), "fetch"], { stdio: "inherit" });
40
+ if (result.status !== 0) process.exit(result.status ?? 1);
41
+ }
42
+
43
+ // Only the same-named session in the current workspace is managed here.
44
+ function resetSessionData(profileName) {
45
+ const { bin } = requireCompatibleCLI();
46
+ for (const command of ["close", "delete-data"]) {
47
+ const result = spawnSync(process.execPath, [bin, `-s=${profileName}`, command], { stdio: "inherit" });
48
+ if (result.error || result.status !== 0) throw new Error(`Session cleanup failed: ${command}`);
49
+ }
50
+ }
51
+
52
+ async function runInstall() {
53
+ requireCompatibleCLI();
54
+ console.log("Downloading Camoufox browser binary (this only needs to run once)...");
55
+ runCamoufoxFetch();
56
+
57
+ console.log("\nGenerating default profile from your machine's real parameters...");
58
+ const file = await ensureProfile(DEFAULT_PROFILE);
59
+ console.log(`Profile written to ${file}`);
60
+ await activateProfile(await readProfileConfig(DEFAULT_PROFILE));
61
+ console.log("\nDone. Try: playwright-cli open https://example.com");
62
+ }
63
+
64
+ async function runProfile(args) {
65
+ const [sub, name] = args;
66
+ switch (sub) {
67
+ case "list": {
68
+ const profiles = await listProfiles();
69
+ if (profiles.length === 0) {
70
+ console.log("No profiles yet. Run `camoufox-playwright-cli install` first.");
71
+ return;
72
+ }
73
+ for (const p of profiles) {
74
+ console.log(`${p.name}${await isActive(p.name) ? " (active global default)" : ""}`);
75
+ console.log(` created: ${p.createdAt}`);
76
+ console.log(` os: ${p.os} locale: ${p.locale} timezone: ${p.timezone}`);
77
+ console.log(` screen: ${JSON.stringify(p.screen)}`);
78
+ console.log(` gpu: ${JSON.stringify(p.gpu)}`);
79
+ }
80
+ return;
81
+ }
82
+ case "use": {
83
+ if (!name || !profileExists(name)) throw new Error("Usage: cpw profile use <existing-name>");
84
+ requireCompatibleCLI();
85
+ console.log(`Global Camoufox config: ${await activateProfile(await readProfileConfig(name))}`);
86
+ console.log("Applies to new sessions. Close existing sessions before switching identities.");
87
+ return;
88
+ }
89
+ case "path": {
90
+ if (!name || !profileExists(name)) throw new Error("Usage: cpw profile path <existing-name>");
91
+ console.log(profileConfigPath(name));
92
+ return;
93
+ }
94
+ case "create": {
95
+ if (!name) throw new Error("Usage: camoufox-playwright-cli profile create <name>");
96
+ if (profileExists(name)) throw new Error(`Profile "${name}" already exists. Use \`profile rotate ${name}\` to regenerate it.`);
97
+ const file = await ensureProfile(name);
98
+ console.log(`Created profile "${name}" -> ${file}`);
99
+ return;
100
+ }
101
+ case "rotate": {
102
+ const target = name || DEFAULT_PROFILE;
103
+ if (!profileExists(target)) throw new Error(`Profile "${target}" does not exist. Use \`profile create ${target}\` first.`);
104
+ const active = await isActive(target);
105
+ resetSessionData(target);
106
+ const file = await ensureProfile(target, { rotate: true });
107
+ if (active) await activateProfile(await readProfileConfig(target));
108
+ console.log(`Rotated profile "${target}" (same-named workspace session cleared, new fingerprint generated) -> ${file}`);
109
+ return;
110
+ }
111
+ case "delete": {
112
+ if (!name) throw new Error("Usage: camoufox-playwright-cli profile delete <name>");
113
+ if (name === DEFAULT_PROFILE) throw new Error(`Refusing to delete the "${DEFAULT_PROFILE}" profile. Use \`profile rotate\` instead.`);
114
+ if (await isActive(name)) throw new Error("Switch the global default before deleting this profile.");
115
+ resetSessionData(name);
116
+ const existed = await deleteProfileFiles(name);
117
+ console.log(existed ? `Deleted profile "${name}"` : `Profile "${name}" did not exist`);
118
+ return;
119
+ }
120
+ default:
121
+ console.log(`Usage:
122
+ camoufox-playwright-cli profile list
123
+ camoufox-playwright-cli profile use <name>
124
+ camoufox-playwright-cli profile path <name>
125
+ camoufox-playwright-cli profile create <name>
126
+ camoufox-playwright-cli profile rotate [name] (defaults to "default")
127
+ camoufox-playwright-cli profile delete <name>`);
128
+ }
129
+ }
130
+
131
+ async function isActive(name) {
132
+ const current = await readJson(globalConfigPath());
133
+ const profile = await readProfileConfig(name);
134
+ return !!profile && isDeepStrictEqual(current?.browser?.launchOptions, profile.browser.launchOptions);
135
+ }
136
+
137
+ async function runDoctor() {
138
+ const cli = installedCLI();
139
+ console.log(`Official CLI: ${cli ? `${cli.version} (driver ${cli.driver}) at ${cli.command}` : 'missing'}`);
140
+ console.log(`Validated CLI: ${RECOMMENDED_CLI}`);
141
+ const config = await readJson(globalConfigPath());
142
+ const executable = config?.browser?.launchOptions?.executablePath;
143
+ console.log(`Global config: ${globalConfigPath()}`);
144
+ console.log(`Browser: ${config?.browser?.browserName ?? 'not configured'}`);
145
+ console.log(`Executable exists: ${!!executable && existsSync(executable)}`);
146
+ const local = path.resolve('.playwright', 'cli.config.json');
147
+ if (existsSync(local)) console.log(`Project override: ${local}`);
148
+ for (const key of Object.keys(process.env).filter(key => key.startsWith('PLAYWRIGHT_MCP_') || key === 'PWTEST_CLI_GLOBAL_CONFIG')) {
149
+ console.log(`Environment override present: ${key}`);
150
+ }
151
+ requireCompatibleCLI();
152
+ if (config?.browser?.browserName !== 'firefox' || !executable || !existsSync(executable)) {
153
+ throw new Error('Camoufox default is not configured. Run cpw install or cpw profile use <name>.');
154
+ }
155
+ }
156
+
157
+ function printHelp() {
158
+ console.log(`cpw - Camoufox configuration for the official Playwright CLI
159
+
160
+ cpw install Fetch Camoufox, create and activate default profile
161
+ cpw doctor Check official CLI and browser configuration
162
+ cpw profile list
163
+ cpw profile create <name>
164
+ cpw profile use <name> Set the global default for new sessions
165
+ cpw profile path <name> Print config path for --config
166
+ cpw profile rotate [name] Reset same-named session in this workspace
167
+ cpw profile delete <name> Reset same-named session and delete fingerprint
168
+
169
+ Browser operations use official commands:
170
+ playwright-cli open <url>
171
+ playwright-cli -s=work open <url> --config=<path-from-cpw-profile-path>
172
+ playwright-cli click <ref>
173
+ playwright-cli close
174
+
175
+ --profile belongs to the official CLI and means a persistent data directory.
176
+ Before rotating/deleting, close and clear any other sessions using that fingerprint,
177
+ including sessions in other projects or custom persistent directories.
178
+ `);
179
+ }
180
+
181
+ main().catch(error => { console.error(error.message); process.exitCode = 1; });
package/package.json CHANGED
@@ -1,39 +1,46 @@
1
- {
2
- "name": "camoufox-playwright-cli",
3
- "version": "0.1.0",
4
- "description": "Playwright CLI (@playwright/cli), driven by Camoufox for stealth browsing. Profile-based: auto-generated default fingerprint on install, create/rotate/delete named profiles.",
5
- "type": "module",
6
- "bin": {
7
- "camoufox-playwright-cli": "bin/cli.js",
8
- "cpw": "bin/cli.js"
9
- },
10
- "files": [
11
- "bin",
12
- "src",
13
- "README.md",
14
- "LICENSE"
15
- ],
16
- "engines": {
17
- "node": ">=18"
18
- },
19
- "license": "MIT",
20
- "author": "Shun",
21
- "repository": {
22
- "type": "git",
23
- "url": "git+https://github.com/ShunL12324/camoufox-playwright-cli.git"
24
- },
25
- "keywords": [
26
- "camoufox",
27
- "playwright",
28
- "stealth",
29
- "anti-detect",
30
- "browser-automation",
31
- "cli",
32
- "profiles"
33
- ],
34
- "dependencies": {
35
- "@playwright/cli": "0.1.18",
36
- "better-sqlite3": "13.0.1",
37
- "camoufox-js": "0.12.0"
38
- }
39
- }
1
+ {
2
+ "name": "camoufox-playwright-cli",
3
+ "version": "0.2.0",
4
+ "description": "Playwright CLI (@playwright/cli), driven by Camoufox for stealth browsing. Profile-based: auto-generated default fingerprint on install, create/rotate/delete named profiles.",
5
+ "type": "module",
6
+ "bin": {
7
+ "camoufox-playwright-cli": "bin/cli.js",
8
+ "cpw": "bin/cli.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "src",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "node": ">=22"
18
+ },
19
+ "license": "MIT",
20
+ "author": "Shun",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/ShunL12324/camoufox-playwright-cli.git"
24
+ },
25
+ "keywords": [
26
+ "camoufox",
27
+ "playwright",
28
+ "stealth",
29
+ "anti-detect",
30
+ "browser-automation",
31
+ "cli",
32
+ "profiles"
33
+ ],
34
+ "dependencies": {
35
+ "better-sqlite3": "13.0.3",
36
+ "camoufox-js": "0.12.0"
37
+ },
38
+ "scripts": {
39
+ "test": "node --test test/config.test.mjs",
40
+ "test:browser": "node test/browser-smoke.mjs",
41
+ "test:cli": "node test/cli-smoke.mjs"
42
+ },
43
+ "devDependencies": {
44
+ "@playwright/cli": "0.1.12"
45
+ }
46
+ }
@@ -0,0 +1,31 @@
1
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import path from 'node:path';
4
+
5
+ export const RECOMMENDED_CLI = '0.1.12';
6
+
7
+ // Resolve the executable actually exposed on PATH; never shadow it with our bin.
8
+ export function installedCLI() {
9
+ for (const directory of (process.env.PATH ?? '').split(path.delimiter)) {
10
+ if (!directory) continue;
11
+ const command = path.join(directory, process.platform === 'win32' ? 'playwright-cli.cmd' : 'playwright-cli');
12
+ if (!existsSync(command)) continue;
13
+ const bin = process.platform === 'win32'
14
+ ? path.join(directory, 'node_modules', '@playwright', 'cli', 'playwright-cli.js')
15
+ : realpathSync(command);
16
+ if (!existsSync(bin)) throw new Error(`Unrecognized playwright-cli installation: ${command}`);
17
+ const pkg = JSON.parse(readFileSync(path.join(path.dirname(bin), 'package.json'), 'utf8'));
18
+ if (pkg.name !== '@playwright/cli') throw new Error(`Unexpected package behind ${command}`);
19
+ const require = createRequire(bin);
20
+ return { bin, command, version: pkg.version, driver: require('playwright-core/package.json').version };
21
+ }
22
+ return null;
23
+ }
24
+
25
+ export function requireCompatibleCLI() {
26
+ const cli = installedCLI();
27
+ if (!cli || cli.version !== RECOMMENDED_CLI) {
28
+ throw new Error(`${cli ? `Installed CLI ${cli.version} is not validated.` : 'Official playwright-cli is not installed on PATH.'} Recommended: npm install -g @playwright/cli@${RECOMMENDED_CLI}. Existing installations are never overwritten automatically.`);
29
+ }
30
+ return cli;
31
+ }
package/src/config.js ADDED
@@ -0,0 +1,35 @@
1
+ import { mkdir, readFile, writeFile, rename, unlink } from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { randomUUID } from 'node:crypto';
5
+
6
+ export const globalConfigPath = () => path.join(os.homedir(), '.playwright', 'cli.config.json');
7
+
8
+ export async function readJson(file) {
9
+ try { return JSON.parse(await readFile(file, 'utf8')); }
10
+ catch (error) { if (error.code === 'ENOENT') return null; throw error; }
11
+ }
12
+
13
+ // Preserve unrelated settings, but replace launch settings as one coherent set.
14
+ export function mergeCamoufoxConfig(existing, profile) {
15
+ const browser = { ...existing?.browser, ...profile.browser };
16
+ for (const key of ['remoteEndpoint', 'cdpEndpoint', 'userDataDir', 'isolated']) delete browser[key];
17
+ return { ...existing, extension: false, browser };
18
+ }
19
+
20
+ export async function writeJson(file, value) {
21
+ await mkdir(path.dirname(file), { recursive: true });
22
+ const pending = `${file}.${randomUUID()}.pending`;
23
+ try {
24
+ await writeFile(pending, JSON.stringify(value, null, 2) + '\n', { mode: 0o600 });
25
+ await rename(pending, file);
26
+ } finally {
27
+ await unlink(pending).catch(error => { if (error.code !== 'ENOENT') throw error; });
28
+ }
29
+ }
30
+
31
+ export async function activateProfile(profile, file = globalConfigPath()) {
32
+ const existing = await readJson(file);
33
+ await writeJson(file, mergeCamoufoxConfig(existing, profile));
34
+ return file;
35
+ }
package/src/host.js CHANGED
File without changes
package/src/paths.js CHANGED
@@ -1,19 +1,12 @@
1
- import { createRequire } from "node:module";
2
- import path from "node:path";
3
-
4
- const require = createRequire(import.meta.url);
5
-
6
- export function playwrightCliBin() {
7
- const pkgJsonPath = require.resolve("@playwright/cli/package.json");
8
- const pkgDir = path.dirname(pkgJsonPath);
9
- const pkg = require(pkgJsonPath);
10
- return path.join(pkgDir, pkg.bin["playwright-cli"]);
11
- }
12
-
13
- export function camoufoxJsBin() {
14
- const pkgJsonPath = require.resolve("camoufox-js/package.json");
15
- const pkgDir = path.dirname(pkgJsonPath);
16
- const pkg = require(pkgJsonPath);
17
- const bin = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
18
- return path.join(pkgDir, bin);
19
- }
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+
4
+ const require = createRequire(import.meta.url);
5
+
6
+ export function camoufoxJsBin() {
7
+ const pkgJsonPath = require.resolve("camoufox-js/package.json");
8
+ const pkgDir = path.dirname(pkgJsonPath);
9
+ const pkg = require(pkgJsonPath);
10
+ const bin = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
11
+ return path.join(pkgDir, bin);
12
+ }
package/src/profiles.js CHANGED
File without changes
package/src/webgl.js CHANGED
File without changes