@askjo/camoufox-browser 1.0.5 → 1.0.7
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/package.json +2 -2
- package/plugin.ts +107 -12
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askjo/camoufox-browser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Headless browser automation server and OpenClaw plugin for AI agents - anti-detection, element refs, and session isolation",
|
|
5
5
|
"main": "server-camoufox.js",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"author": "Jo Inc <
|
|
7
|
+
"author": "Jo Inc <oss@askjo.ai>",
|
|
8
8
|
"homepage": "https://github.com/jo-inc/camoufox-browser#readme",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
package/plugin.ts
CHANGED
|
@@ -2,15 +2,28 @@
|
|
|
2
2
|
* Camoufox Browser - OpenClaw Plugin
|
|
3
3
|
*
|
|
4
4
|
* Provides browser automation tools using the Camoufox anti-detection browser.
|
|
5
|
-
*
|
|
5
|
+
* Server auto-starts when plugin loads (configurable via autoStart: false).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { spawn, ChildProcess } from "child_process";
|
|
9
|
-
import { join } from "path";
|
|
9
|
+
import { join, dirname } from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
|
|
12
|
+
// Get plugin directory - works in both ESM and CJS contexts
|
|
13
|
+
const getPluginDir = (): string => {
|
|
14
|
+
try {
|
|
15
|
+
// ESM context
|
|
16
|
+
return dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
} catch {
|
|
18
|
+
// CJS context
|
|
19
|
+
return __dirname;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
10
22
|
|
|
11
23
|
interface PluginConfig {
|
|
12
24
|
url?: string;
|
|
13
25
|
autoStart?: boolean;
|
|
26
|
+
port?: number;
|
|
14
27
|
}
|
|
15
28
|
|
|
16
29
|
interface ToolResult {
|
|
@@ -41,6 +54,67 @@ interface PluginApi {
|
|
|
41
54
|
|
|
42
55
|
let serverProcess: ChildProcess | null = null;
|
|
43
56
|
|
|
57
|
+
async function startServer(
|
|
58
|
+
pluginDir: string,
|
|
59
|
+
port: number,
|
|
60
|
+
log: PluginApi["log"]
|
|
61
|
+
): Promise<ChildProcess> {
|
|
62
|
+
const serverPath = join(pluginDir, "server-camoufox.js");
|
|
63
|
+
const proc = spawn("node", [serverPath], {
|
|
64
|
+
cwd: pluginDir,
|
|
65
|
+
env: { ...process.env, PORT: String(port) },
|
|
66
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
67
|
+
detached: false,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
proc.stdout?.on("data", (data: Buffer) => {
|
|
71
|
+
const msg = data.toString().trim();
|
|
72
|
+
if (msg) log.info(`[server] ${msg}`);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
proc.stderr?.on("data", (data: Buffer) => {
|
|
76
|
+
const msg = data.toString().trim();
|
|
77
|
+
if (msg) log.error(`[server] ${msg}`);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
proc.on("error", (err) => {
|
|
81
|
+
log.error(`Server process error: ${err.message}`);
|
|
82
|
+
serverProcess = null;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
proc.on("exit", (code) => {
|
|
86
|
+
if (code !== 0 && code !== null) {
|
|
87
|
+
log.error(`Server exited with code ${code}`);
|
|
88
|
+
}
|
|
89
|
+
serverProcess = null;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Wait for server to be ready
|
|
93
|
+
const baseUrl = `http://localhost:${port}`;
|
|
94
|
+
for (let i = 0; i < 30; i++) {
|
|
95
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
96
|
+
try {
|
|
97
|
+
const res = await fetch(`${baseUrl}/health`);
|
|
98
|
+
if (res.ok) {
|
|
99
|
+
log.info(`Camoufox server ready on port ${port}`);
|
|
100
|
+
return proc;
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
// Server not ready yet
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
throw new Error("Server failed to start within 15 seconds");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function checkServerRunning(baseUrl: string): Promise<boolean> {
|
|
110
|
+
try {
|
|
111
|
+
const res = await fetch(`${baseUrl}/health`);
|
|
112
|
+
return res.ok;
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
44
118
|
async function fetchApi(
|
|
45
119
|
baseUrl: string,
|
|
46
120
|
path: string,
|
|
@@ -68,7 +142,26 @@ function toToolResult(data: unknown): ToolResult {
|
|
|
68
142
|
}
|
|
69
143
|
|
|
70
144
|
export default function register(api: PluginApi) {
|
|
71
|
-
const
|
|
145
|
+
const port = api.config.port || 9377;
|
|
146
|
+
const baseUrl = api.config.url || `http://localhost:${port}`;
|
|
147
|
+
const autoStart = api.config.autoStart !== false; // default true
|
|
148
|
+
const pluginDir = getPluginDir();
|
|
149
|
+
|
|
150
|
+
// Auto-start server if configured (default: true)
|
|
151
|
+
if (autoStart) {
|
|
152
|
+
(async () => {
|
|
153
|
+
const alreadyRunning = await checkServerRunning(baseUrl);
|
|
154
|
+
if (alreadyRunning) {
|
|
155
|
+
api.log.info(`Camoufox server already running at ${baseUrl}`);
|
|
156
|
+
} else {
|
|
157
|
+
try {
|
|
158
|
+
serverProcess = await startServer(pluginDir, port, api.log);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
api.log.error(`Failed to auto-start server: ${(err as Error).message}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
})();
|
|
164
|
+
}
|
|
72
165
|
|
|
73
166
|
api.registerTool(
|
|
74
167
|
{
|
|
@@ -317,22 +410,24 @@ export default function register(api: PluginApi) {
|
|
|
317
410
|
try {
|
|
318
411
|
const health = await fetchApi(baseUrl, "/health");
|
|
319
412
|
api.log.info(`Camoufox server at ${baseUrl}: ${JSON.stringify(health)}`);
|
|
320
|
-
} catch
|
|
413
|
+
} catch {
|
|
321
414
|
api.log.error(`Camoufox server at ${baseUrl}: not reachable`);
|
|
322
415
|
}
|
|
323
416
|
break;
|
|
324
417
|
case "start":
|
|
325
418
|
if (serverProcess) {
|
|
326
|
-
api.log.info("Camoufox server already running");
|
|
419
|
+
api.log.info("Camoufox server already running (managed)");
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (await checkServerRunning(baseUrl)) {
|
|
423
|
+
api.log.info(`Camoufox server already running at ${baseUrl}`);
|
|
327
424
|
return;
|
|
328
425
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
});
|
|
335
|
-
api.log.info(`Started camoufox-browser server (pid: ${serverProcess.pid})`);
|
|
426
|
+
try {
|
|
427
|
+
serverProcess = await startServer(pluginDir, port, api.log);
|
|
428
|
+
} catch (err) {
|
|
429
|
+
api.log.error(`Failed to start server: ${(err as Error).message}`);
|
|
430
|
+
}
|
|
336
431
|
break;
|
|
337
432
|
case "stop":
|
|
338
433
|
if (serverProcess) {
|