@askjo/camoufox-browser 1.0.5 → 1.0.6

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 +94 -11
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.6",
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,7 +2,7 @@
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";
@@ -11,6 +11,7 @@ import { join } from "path";
11
11
  interface PluginConfig {
12
12
  url?: string;
13
13
  autoStart?: boolean;
14
+ port?: number;
14
15
  }
15
16
 
16
17
  interface ToolResult {
@@ -41,6 +42,67 @@ interface PluginApi {
41
42
 
42
43
  let serverProcess: ChildProcess | null = null;
43
44
 
45
+ async function startServer(
46
+ pluginDir: string,
47
+ port: number,
48
+ log: PluginApi["log"]
49
+ ): Promise<ChildProcess> {
50
+ const serverPath = join(pluginDir, "server-camoufox.js");
51
+ const proc = spawn("node", [serverPath], {
52
+ cwd: pluginDir,
53
+ env: { ...process.env, PORT: String(port) },
54
+ stdio: ["ignore", "pipe", "pipe"],
55
+ detached: false,
56
+ });
57
+
58
+ proc.stdout?.on("data", (data: Buffer) => {
59
+ const msg = data.toString().trim();
60
+ if (msg) log.info(`[server] ${msg}`);
61
+ });
62
+
63
+ proc.stderr?.on("data", (data: Buffer) => {
64
+ const msg = data.toString().trim();
65
+ if (msg) log.error(`[server] ${msg}`);
66
+ });
67
+
68
+ proc.on("error", (err) => {
69
+ log.error(`Server process error: ${err.message}`);
70
+ serverProcess = null;
71
+ });
72
+
73
+ proc.on("exit", (code) => {
74
+ if (code !== 0 && code !== null) {
75
+ log.error(`Server exited with code ${code}`);
76
+ }
77
+ serverProcess = null;
78
+ });
79
+
80
+ // Wait for server to be ready
81
+ const baseUrl = `http://localhost:${port}`;
82
+ for (let i = 0; i < 30; i++) {
83
+ await new Promise((r) => setTimeout(r, 500));
84
+ try {
85
+ const res = await fetch(`${baseUrl}/health`);
86
+ if (res.ok) {
87
+ log.info(`Camoufox server ready on port ${port}`);
88
+ return proc;
89
+ }
90
+ } catch {
91
+ // Server not ready yet
92
+ }
93
+ }
94
+ throw new Error("Server failed to start within 15 seconds");
95
+ }
96
+
97
+ async function checkServerRunning(baseUrl: string): Promise<boolean> {
98
+ try {
99
+ const res = await fetch(`${baseUrl}/health`);
100
+ return res.ok;
101
+ } catch {
102
+ return false;
103
+ }
104
+ }
105
+
44
106
  async function fetchApi(
45
107
  baseUrl: string,
46
108
  path: string,
@@ -68,7 +130,26 @@ function toToolResult(data: unknown): ToolResult {
68
130
  }
69
131
 
70
132
  export default function register(api: PluginApi) {
71
- const baseUrl = api.config.url || "http://localhost:9377";
133
+ const port = api.config.port || 9377;
134
+ const baseUrl = api.config.url || `http://localhost:${port}`;
135
+ const autoStart = api.config.autoStart !== false; // default true
136
+ const pluginDir = __dirname;
137
+
138
+ // Auto-start server if configured (default: true)
139
+ if (autoStart) {
140
+ (async () => {
141
+ const alreadyRunning = await checkServerRunning(baseUrl);
142
+ if (alreadyRunning) {
143
+ api.log.info(`Camoufox server already running at ${baseUrl}`);
144
+ } else {
145
+ try {
146
+ serverProcess = await startServer(pluginDir, port, api.log);
147
+ } catch (err) {
148
+ api.log.error(`Failed to auto-start server: ${(err as Error).message}`);
149
+ }
150
+ }
151
+ })();
152
+ }
72
153
 
73
154
  api.registerTool(
74
155
  {
@@ -317,22 +398,24 @@ export default function register(api: PluginApi) {
317
398
  try {
318
399
  const health = await fetchApi(baseUrl, "/health");
319
400
  api.log.info(`Camoufox server at ${baseUrl}: ${JSON.stringify(health)}`);
320
- } catch (e) {
401
+ } catch {
321
402
  api.log.error(`Camoufox server at ${baseUrl}: not reachable`);
322
403
  }
323
404
  break;
324
405
  case "start":
325
406
  if (serverProcess) {
326
- api.log.info("Camoufox server already running");
407
+ api.log.info("Camoufox server already running (managed)");
408
+ return;
409
+ }
410
+ if (await checkServerRunning(baseUrl)) {
411
+ api.log.info(`Camoufox server already running at ${baseUrl}`);
327
412
  return;
328
413
  }
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})`);
414
+ try {
415
+ serverProcess = await startServer(pluginDir, port, api.log);
416
+ } catch (err) {
417
+ api.log.error(`Failed to start server: ${(err as Error).message}`);
418
+ }
336
419
  break;
337
420
  case "stop":
338
421
  if (serverProcess) {