ghostfox 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/README.md +25 -0
- package/bin/ghostfox.js +167 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# ghostfox (npm)
|
|
2
|
+
|
|
3
|
+
npm surface for the [Ghostfox](https://github.com/autokeren/ghostfox)
|
|
4
|
+
agent-native stealth browser. Zero npm dependencies.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx ghostfox install # one-time: engine (~650MB) + runtime into ~/.ghostfox
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Then wire it into any MCP client (Claude Code, Cursor, ...):
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"mcpServers": {
|
|
15
|
+
"ghostcloak": { "command": "npx", "args": ["-y", "ghostfox", "mcp"] }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Tools: `session_create` · `page_open` · `page_snapshot` · `page_screenshot` ·
|
|
21
|
+
`page_click` · `page_type` · `page_fill` · `page_press` ·
|
|
22
|
+
`identity_generate` · `identity_audit` · `session_evidence` · `captcha_solve`
|
|
23
|
+
|
|
24
|
+
Docs: https://autokeren.github.io/ghostfox/ ·
|
|
25
|
+
Docker: `ghcr.io/autokeren/ghostfox` · Python: `pip install ghostfox`
|
package/bin/ghostfox.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ghostfox — CLI + MCP launcher for the Ghostfox agent browser.
|
|
3
|
+
//
|
|
4
|
+
// Commands:
|
|
5
|
+
// ghostfox install download the engine + runtime into ~/.ghostfox
|
|
6
|
+
// ghostfox mcp run the MCP server (stdio) — for MCP client configs
|
|
7
|
+
// ghostfox config print the MCP client JSON snippet
|
|
8
|
+
// ghostfox version print versions
|
|
9
|
+
//
|
|
10
|
+
// Zero npm dependencies; only Node stdlib.
|
|
11
|
+
|
|
12
|
+
const { spawn, execFileSync } = require("child_process");
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const os = require("os");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
const https = require("https");
|
|
17
|
+
const zlib = require("zlib");
|
|
18
|
+
|
|
19
|
+
const REPO = "autokeren/ghostfox";
|
|
20
|
+
const ROOT = process.env.GHOSTFOX_ROOT || path.join(os.homedir(), ".ghostfox");
|
|
21
|
+
const ENGINE = path.join(ROOT, "engine");
|
|
22
|
+
const MCP_BIN = path.join(ROOT, "mcp", "ghostcloak-mcp");
|
|
23
|
+
|
|
24
|
+
function log(msg) { process.stderr.write(`ghostfox: ${msg}\n`); }
|
|
25
|
+
|
|
26
|
+
function fetchJson(url) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
https.get(url, { headers: { "User-Agent": "ghostfox-npm" } }, (res) => {
|
|
29
|
+
if (res.statusCode >= 300 && res.headers.location) {
|
|
30
|
+
return fetchJson(res.headers.location).then(resolve, reject);
|
|
31
|
+
}
|
|
32
|
+
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
33
|
+
let data = "";
|
|
34
|
+
res.on("data", (c) => (data += c));
|
|
35
|
+
res.on("end", () => { try { resolve(JSON.parse(data)); } catch (e) { reject(e); } });
|
|
36
|
+
}).on("error", reject);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function download(url, dest, redirectCount = 0) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
if (redirectCount > 5) return reject(new Error("too many redirects"));
|
|
43
|
+
https.get(url, { headers: { "User-Agent": "ghostfox-npm" } }, (res) => {
|
|
44
|
+
if (res.statusCode >= 300 && res.headers.location) {
|
|
45
|
+
return download(res.headers.location, dest, redirectCount + 1).then(resolve, reject);
|
|
46
|
+
}
|
|
47
|
+
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
48
|
+
const file = fs.createWriteStream(dest);
|
|
49
|
+
res.pipe(file);
|
|
50
|
+
file.on("finish", () => file.close(resolve));
|
|
51
|
+
file.on("error", reject);
|
|
52
|
+
}).on("error", reject);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function unzip(zipPath, dest) {
|
|
57
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
58
|
+
try {
|
|
59
|
+
execFileSync("unzip", ["-q", "-o", zipPath, "-d", dest], { stdio: "inherit" });
|
|
60
|
+
} catch {
|
|
61
|
+
execFileSync("7z", ["x", "-y", `-o${dest}`, zipPath], { stdio: "inherit" });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function install() {
|
|
66
|
+
const rel = await fetchJson(`https://api.github.com/repos/${REPO}/releases/latest`);
|
|
67
|
+
const assets = Object.fromEntries(rel.assets.map((a) => [a.name, a.browser_download_url]));
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(MCP_BIN)) {
|
|
70
|
+
const url = assets["ghostcloak-mcp"];
|
|
71
|
+
if (!url) throw new Error("no prebuilt runtime asset on the latest release — build it from source (see the repo README)");
|
|
72
|
+
log("downloading ghostcloak-mcp runtime...");
|
|
73
|
+
fs.mkdirSync(path.dirname(MCP_BIN), { recursive: true });
|
|
74
|
+
await download(url, MCP_BIN);
|
|
75
|
+
fs.chmodSync(MCP_BIN, 0o755);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!fs.existsSync(path.join(ENGINE, "ghostfox-bin"))) {
|
|
79
|
+
const platform = os.platform() === "linux" ? "lin" : os.platform() === "darwin" ? "mac" : "win";
|
|
80
|
+
const arch = os.arch() === "arm64" ? "arm64" : "x86_64";
|
|
81
|
+
const asset = Object.keys(assets).find((n) => n.includes(`${platform}.${arch}`) && n.endsWith(".zip"));
|
|
82
|
+
if (!asset) throw new Error(`no prebuilt engine for ${platform}-${arch} on the latest release`);
|
|
83
|
+
log(`downloading engine (${asset}) — this is ~650MB...`);
|
|
84
|
+
const tmp = path.join(os.tmpdir(), "ghostfox-engine.zip");
|
|
85
|
+
await download(assets[asset], tmp);
|
|
86
|
+
const stage = path.join(ROOT, ".engine-unpack");
|
|
87
|
+
fs.rmSync(stage, { recursive: true, force: true });
|
|
88
|
+
unzip(tmp, stage);
|
|
89
|
+
fs.rmSync(ENGINE, { recursive: true, force: true });
|
|
90
|
+
const inner = fs.existsSync(path.join(stage, "ghostfox")) ? path.join(stage, "ghostfox") : stage;
|
|
91
|
+
fs.mkdirSync(ROOT, { recursive: true });
|
|
92
|
+
fs.renameSync(inner, ENGINE);
|
|
93
|
+
fs.rmSync(stage, { recursive: true, force: true });
|
|
94
|
+
fs.rmSync(tmp, { force: true });
|
|
95
|
+
}
|
|
96
|
+
log(`installed: engine ${ENGINE}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function mcp() {
|
|
100
|
+
if (!fs.existsSync(MCP_BIN)) {
|
|
101
|
+
log("runtime not installed — run `ghostfox install` first");
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
const child = spawn(MCP_BIN, [], {
|
|
105
|
+
env: {
|
|
106
|
+
...process.env,
|
|
107
|
+
GHOSTFOX_HOME: process.env.GHOSTFOX_HOME || ENGINE,
|
|
108
|
+
},
|
|
109
|
+
stdio: "inherit",
|
|
110
|
+
});
|
|
111
|
+
child.on("exit", (code) => process.exit(code || 0));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function config() {
|
|
115
|
+
const home = process.env.GHOSTFOX_HOME || ENGINE;
|
|
116
|
+
process.stdout.write(
|
|
117
|
+
JSON.stringify(
|
|
118
|
+
{
|
|
119
|
+
mcpServers: {
|
|
120
|
+
ghostcloak: { command: "npx", args: ["-y", "ghostfox", "mcp"], env: { GHOSTFOX_HOME: home } },
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
null,
|
|
124
|
+
2
|
|
125
|
+
) + "\n"
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function main() {
|
|
130
|
+
const cmd = process.argv[2] || "help";
|
|
131
|
+
switch (cmd) {
|
|
132
|
+
case "install":
|
|
133
|
+
await install();
|
|
134
|
+
break;
|
|
135
|
+
case "mcp":
|
|
136
|
+
mcp();
|
|
137
|
+
break;
|
|
138
|
+
case "config":
|
|
139
|
+
config();
|
|
140
|
+
break;
|
|
141
|
+
case "version":
|
|
142
|
+
process.stdout.write(`ghostfox npm wrapper 0.2.0\n`);
|
|
143
|
+
try {
|
|
144
|
+
const out = execFileSync(MCP_BIN, ["--version"], { encoding: "utf8" });
|
|
145
|
+
process.stdout.write(out);
|
|
146
|
+
} catch {}
|
|
147
|
+
break;
|
|
148
|
+
default:
|
|
149
|
+
process.stdout.write(`ghostfox — the agent-native stealth browser you can own
|
|
150
|
+
|
|
151
|
+
usage:
|
|
152
|
+
ghostfox install download engine + runtime into ~/.ghostfox
|
|
153
|
+
ghostfox mcp run the MCP server (stdio)
|
|
154
|
+
ghostfox config print the MCP client JSON snippet
|
|
155
|
+
ghostfox version print versions
|
|
156
|
+
|
|
157
|
+
repo: https://github.com/${REPO}
|
|
158
|
+
docs: https://autokeren.github.io/ghostfox/
|
|
159
|
+
`);
|
|
160
|
+
process.exit(cmd === "help" ? 0 : 1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
main().catch((e) => {
|
|
165
|
+
log(e.message);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ghostfox",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "The agent-native stealth browser you can own — MCP launcher + installer for Ghostfox (fingerprint-coherent Firefox engine + Rust MCP runtime).",
|
|
5
|
+
"license": "MIT OR Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/autokeren/ghostfox"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://autokeren.github.io/ghostfox/",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"mcp",
|
|
13
|
+
"browser",
|
|
14
|
+
"stealth",
|
|
15
|
+
"anti-detect",
|
|
16
|
+
"ai-agents",
|
|
17
|
+
"scraping",
|
|
18
|
+
"firefox"
|
|
19
|
+
],
|
|
20
|
+
"bin": {
|
|
21
|
+
"ghostfox": "bin/ghostfox.js"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=16"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"bin/"
|
|
28
|
+
]
|
|
29
|
+
}
|