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 +0 -0
- package/README.md +52 -34
- package/bin/cli.js +180 -171
- package/package.json +46 -39
- package/src/compatibility.js +31 -0
- package/src/config.js +35 -0
- package/src/host.js +0 -0
- package/src/paths.js +12 -19
- package/src/profiles.js +0 -0
- package/src/webgl.js +0 -0
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -1,63 +1,81 @@
|
|
|
1
1
|
# camoufox-playwright-cli
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
+
## Profiles
|
|
26
24
|
|
|
27
|
-
```
|
|
25
|
+
```sh
|
|
28
26
|
cpw profile list
|
|
29
27
|
cpw profile create work
|
|
30
|
-
cpw profile
|
|
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
|
-
|
|
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
|
-
|
|
36
|
+
For simultaneous identities, use an explicit config on each `open`:
|
|
37
37
|
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
52
|
+
## Compatibility
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
79
|
+
## Fingerprints
|
|
62
80
|
|
|
63
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
case "
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
function
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
console.log(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
console.log("
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
console.log(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (!
|
|
96
|
-
|
|
97
|
-
const file = await ensureProfile(
|
|
98
|
-
console.log(`
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
case "
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
console.log(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
console.log(`
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
cpw
|
|
159
|
-
|
|
160
|
-
cpw
|
|
161
|
-
cpw
|
|
162
|
-
cpw
|
|
163
|
-
cpw
|
|
164
|
-
cpw
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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.
|
|
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
|
-
},
|
|
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
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
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
|
|
7
|
-
const pkgJsonPath = require.resolve("
|
|
8
|
-
const pkgDir = path.dirname(pkgJsonPath);
|
|
9
|
-
const pkg = require(pkgJsonPath);
|
|
10
|
-
|
|
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
|