@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.
Files changed (2) hide show
  1. package/package.json +2 -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.5",
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 <hello@askjo.ai>",
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
- * Run the server separately: npm start (or ./run-camoufox.sh -p PORT)
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 baseUrl = api.config.url || "http://localhost:9377";
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 (e) {
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
- const pluginDir = __dirname;
330
- serverProcess = spawn("npm", ["start"], {
331
- cwd: pluginDir,
332
- stdio: "inherit",
333
- detached: true,
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) {