@askjo/camoufox-browser 1.0.3 → 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.
- package/package.json +4 -2
- package/plugin.ts +322 -200
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askjo/camoufox-browser",
|
|
3
|
-
"version": "1.0.
|
|
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 <
|
|
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",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
"anti-detection",
|
|
23
23
|
"ai-agent",
|
|
24
24
|
"openclaw",
|
|
25
|
+
"clawdbot",
|
|
26
|
+
"moltbot",
|
|
25
27
|
"playwright",
|
|
26
28
|
"firefox"
|
|
27
29
|
],
|
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
|
-
*
|
|
5
|
+
* Server auto-starts when plugin loads (configurable via autoStart: false).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { spawn, ChildProcess } from "child_process";
|
|
@@ -11,16 +11,23 @@ import { join } from "path";
|
|
|
11
11
|
interface PluginConfig {
|
|
12
12
|
url?: string;
|
|
13
13
|
autoStart?: boolean;
|
|
14
|
+
port?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ToolResult {
|
|
18
|
+
content: Array<{ type: string; text: string }>;
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
interface PluginApi {
|
|
17
|
-
registerTool: (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
registerTool: (
|
|
23
|
+
tool: {
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
parameters: object;
|
|
27
|
+
execute: (id: string, params: Record<string, unknown>) => Promise<ToolResult>;
|
|
28
|
+
},
|
|
29
|
+
options?: { optional?: boolean }
|
|
30
|
+
) => void;
|
|
24
31
|
registerCommand: (cmd: {
|
|
25
32
|
name: string;
|
|
26
33
|
description: string;
|
|
@@ -35,6 +42,67 @@ interface PluginApi {
|
|
|
35
42
|
|
|
36
43
|
let serverProcess: ChildProcess | null = null;
|
|
37
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
|
+
|
|
38
106
|
async function fetchApi(
|
|
39
107
|
baseUrl: string,
|
|
40
108
|
path: string,
|
|
@@ -55,218 +123,270 @@ async function fetchApi(
|
|
|
55
123
|
return res.json();
|
|
56
124
|
}
|
|
57
125
|
|
|
126
|
+
function toToolResult(data: unknown): ToolResult {
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
58
132
|
export default function register(api: PluginApi) {
|
|
59
|
-
const
|
|
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;
|
|
60
137
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
+
}
|
|
153
|
+
|
|
154
|
+
api.registerTool(
|
|
155
|
+
{
|
|
156
|
+
name: "camoufox_create_tab",
|
|
157
|
+
description:
|
|
158
|
+
"Create a new browser tab. Returns tabId for subsequent operations.",
|
|
159
|
+
parameters: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
userId: { type: "string", description: "User identifier for session isolation" },
|
|
163
|
+
listItemId: { type: "string", description: "Conversation/task identifier for tab grouping" },
|
|
164
|
+
url: { type: "string", description: "Initial URL to navigate to" },
|
|
165
|
+
},
|
|
166
|
+
required: ["userId", "url"],
|
|
167
|
+
},
|
|
168
|
+
async execute(_id, params) {
|
|
169
|
+
const result = await fetchApi(baseUrl, "/tabs", {
|
|
170
|
+
method: "POST",
|
|
171
|
+
body: JSON.stringify(params),
|
|
172
|
+
});
|
|
173
|
+
return toToolResult(result);
|
|
71
174
|
},
|
|
72
|
-
required: ["userId", "url"],
|
|
73
|
-
},
|
|
74
|
-
optional: true,
|
|
75
|
-
handler: async (params) => {
|
|
76
|
-
return fetchApi(baseUrl, "/tabs", {
|
|
77
|
-
method: "POST",
|
|
78
|
-
body: JSON.stringify(params),
|
|
79
|
-
});
|
|
80
175
|
},
|
|
81
|
-
|
|
176
|
+
{ optional: true }
|
|
177
|
+
);
|
|
82
178
|
|
|
83
|
-
api.registerTool(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
179
|
+
api.registerTool(
|
|
180
|
+
{
|
|
181
|
+
name: "camoufox_snapshot",
|
|
182
|
+
description:
|
|
183
|
+
"Get accessibility snapshot of a page with element refs (e1, e2, etc.) for interaction.",
|
|
184
|
+
parameters: {
|
|
185
|
+
type: "object",
|
|
186
|
+
properties: {
|
|
187
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
188
|
+
userId: { type: "string", description: "User identifier" },
|
|
189
|
+
},
|
|
190
|
+
required: ["tabId", "userId"],
|
|
191
|
+
},
|
|
192
|
+
async execute(_id, params) {
|
|
193
|
+
const { tabId, userId } = params as { tabId: string; userId: string };
|
|
194
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}/snapshot?userId=${userId}`);
|
|
195
|
+
return toToolResult(result);
|
|
92
196
|
},
|
|
93
|
-
required: ["tabId", "userId"],
|
|
94
|
-
},
|
|
95
|
-
optional: true,
|
|
96
|
-
handler: async (params) => {
|
|
97
|
-
const { tabId, userId } = params as { tabId: string; userId: string };
|
|
98
|
-
return fetchApi(baseUrl, `/tabs/${tabId}/snapshot?userId=${userId}`);
|
|
99
197
|
},
|
|
100
|
-
|
|
198
|
+
{ optional: true }
|
|
199
|
+
);
|
|
101
200
|
|
|
102
|
-
api.registerTool(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
201
|
+
api.registerTool(
|
|
202
|
+
{
|
|
203
|
+
name: "camoufox_click",
|
|
204
|
+
description: "Click an element by ref (e.g., e1) or CSS selector.",
|
|
205
|
+
parameters: {
|
|
206
|
+
type: "object",
|
|
207
|
+
properties: {
|
|
208
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
209
|
+
userId: { type: "string", description: "User identifier" },
|
|
210
|
+
ref: { type: "string", description: "Element ref from snapshot (e.g., e1)" },
|
|
211
|
+
selector: { type: "string", description: "CSS selector (alternative to ref)" },
|
|
212
|
+
},
|
|
213
|
+
required: ["tabId", "userId"],
|
|
214
|
+
},
|
|
215
|
+
async execute(_id, params) {
|
|
216
|
+
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
217
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}/click`, {
|
|
218
|
+
method: "POST",
|
|
219
|
+
body: JSON.stringify(body),
|
|
220
|
+
});
|
|
221
|
+
return toToolResult(result);
|
|
112
222
|
},
|
|
113
|
-
required: ["tabId", "userId"],
|
|
114
|
-
},
|
|
115
|
-
optional: true,
|
|
116
|
-
handler: async (params) => {
|
|
117
|
-
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
118
|
-
return fetchApi(baseUrl, `/tabs/${tabId}/click`, {
|
|
119
|
-
method: "POST",
|
|
120
|
-
body: JSON.stringify(body),
|
|
121
|
-
});
|
|
122
223
|
},
|
|
123
|
-
|
|
224
|
+
{ optional: true }
|
|
225
|
+
);
|
|
124
226
|
|
|
125
|
-
api.registerTool(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
227
|
+
api.registerTool(
|
|
228
|
+
{
|
|
229
|
+
name: "camoufox_type",
|
|
230
|
+
description: "Type text into an element.",
|
|
231
|
+
parameters: {
|
|
232
|
+
type: "object",
|
|
233
|
+
properties: {
|
|
234
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
235
|
+
userId: { type: "string", description: "User identifier" },
|
|
236
|
+
ref: { type: "string", description: "Element ref from snapshot (e.g., e2)" },
|
|
237
|
+
selector: { type: "string", description: "CSS selector (alternative to ref)" },
|
|
238
|
+
text: { type: "string", description: "Text to type" },
|
|
239
|
+
pressEnter: { type: "boolean", description: "Press Enter after typing" },
|
|
240
|
+
},
|
|
241
|
+
required: ["tabId", "userId", "text"],
|
|
242
|
+
},
|
|
243
|
+
async execute(_id, params) {
|
|
244
|
+
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
245
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}/type`, {
|
|
246
|
+
method: "POST",
|
|
247
|
+
body: JSON.stringify(body),
|
|
248
|
+
});
|
|
249
|
+
return toToolResult(result);
|
|
137
250
|
},
|
|
138
|
-
required: ["tabId", "userId", "text"],
|
|
139
|
-
},
|
|
140
|
-
optional: true,
|
|
141
|
-
handler: async (params) => {
|
|
142
|
-
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
143
|
-
return fetchApi(baseUrl, `/tabs/${tabId}/type`, {
|
|
144
|
-
method: "POST",
|
|
145
|
-
body: JSON.stringify(body),
|
|
146
|
-
});
|
|
147
251
|
},
|
|
148
|
-
|
|
252
|
+
{ optional: true }
|
|
253
|
+
);
|
|
149
254
|
|
|
150
|
-
api.registerTool(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
255
|
+
api.registerTool(
|
|
256
|
+
{
|
|
257
|
+
name: "camoufox_navigate",
|
|
258
|
+
description:
|
|
259
|
+
"Navigate to a URL or use a search macro (@google_search, @youtube_search, etc.).",
|
|
260
|
+
parameters: {
|
|
261
|
+
type: "object",
|
|
262
|
+
properties: {
|
|
263
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
264
|
+
userId: { type: "string", description: "User identifier" },
|
|
265
|
+
url: { type: "string", description: "URL to navigate to" },
|
|
266
|
+
macro: {
|
|
267
|
+
type: "string",
|
|
268
|
+
description: "Search macro (e.g., @google_search, @youtube_search)",
|
|
269
|
+
enum: [
|
|
270
|
+
"@google_search",
|
|
271
|
+
"@youtube_search",
|
|
272
|
+
"@amazon_search",
|
|
273
|
+
"@reddit_search",
|
|
274
|
+
"@wikipedia_search",
|
|
275
|
+
"@twitter_search",
|
|
276
|
+
"@yelp_search",
|
|
277
|
+
"@spotify_search",
|
|
278
|
+
"@netflix_search",
|
|
279
|
+
"@linkedin_search",
|
|
280
|
+
"@instagram_search",
|
|
281
|
+
"@tiktok_search",
|
|
282
|
+
"@twitch_search",
|
|
283
|
+
],
|
|
284
|
+
},
|
|
285
|
+
query: { type: "string", description: "Search query (when using macro)" },
|
|
178
286
|
},
|
|
179
|
-
|
|
287
|
+
required: ["tabId", "userId"],
|
|
288
|
+
},
|
|
289
|
+
async execute(_id, params) {
|
|
290
|
+
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
291
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}/navigate`, {
|
|
292
|
+
method: "POST",
|
|
293
|
+
body: JSON.stringify(body),
|
|
294
|
+
});
|
|
295
|
+
return toToolResult(result);
|
|
180
296
|
},
|
|
181
|
-
required: ["tabId", "userId"],
|
|
182
|
-
},
|
|
183
|
-
optional: true,
|
|
184
|
-
handler: async (params) => {
|
|
185
|
-
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
186
|
-
return fetchApi(baseUrl, `/tabs/${tabId}/navigate`, {
|
|
187
|
-
method: "POST",
|
|
188
|
-
body: JSON.stringify(body),
|
|
189
|
-
});
|
|
190
297
|
},
|
|
191
|
-
|
|
298
|
+
{ optional: true }
|
|
299
|
+
);
|
|
192
300
|
|
|
193
|
-
api.registerTool(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
301
|
+
api.registerTool(
|
|
302
|
+
{
|
|
303
|
+
name: "camoufox_scroll",
|
|
304
|
+
description: "Scroll the page.",
|
|
305
|
+
parameters: {
|
|
306
|
+
type: "object",
|
|
307
|
+
properties: {
|
|
308
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
309
|
+
userId: { type: "string", description: "User identifier" },
|
|
310
|
+
direction: { type: "string", enum: ["up", "down", "left", "right"] },
|
|
311
|
+
amount: { type: "number", description: "Pixels to scroll" },
|
|
312
|
+
},
|
|
313
|
+
required: ["tabId", "userId", "direction"],
|
|
314
|
+
},
|
|
315
|
+
async execute(_id, params) {
|
|
316
|
+
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
317
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}/scroll`, {
|
|
318
|
+
method: "POST",
|
|
319
|
+
body: JSON.stringify(body),
|
|
320
|
+
});
|
|
321
|
+
return toToolResult(result);
|
|
203
322
|
},
|
|
204
|
-
required: ["tabId", "userId", "direction"],
|
|
205
|
-
},
|
|
206
|
-
optional: true,
|
|
207
|
-
handler: async (params) => {
|
|
208
|
-
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
209
|
-
return fetchApi(baseUrl, `/tabs/${tabId}/scroll`, {
|
|
210
|
-
method: "POST",
|
|
211
|
-
body: JSON.stringify(body),
|
|
212
|
-
});
|
|
213
323
|
},
|
|
214
|
-
|
|
324
|
+
{ optional: true }
|
|
325
|
+
);
|
|
215
326
|
|
|
216
|
-
api.registerTool(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
327
|
+
api.registerTool(
|
|
328
|
+
{
|
|
329
|
+
name: "camoufox_screenshot",
|
|
330
|
+
description: "Take a screenshot of the current page.",
|
|
331
|
+
parameters: {
|
|
332
|
+
type: "object",
|
|
333
|
+
properties: {
|
|
334
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
335
|
+
userId: { type: "string", description: "User identifier" },
|
|
336
|
+
},
|
|
337
|
+
required: ["tabId", "userId"],
|
|
338
|
+
},
|
|
339
|
+
async execute(_id, params) {
|
|
340
|
+
const { tabId, userId } = params as { tabId: string; userId: string };
|
|
341
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}/screenshot?userId=${userId}`);
|
|
342
|
+
return toToolResult(result);
|
|
224
343
|
},
|
|
225
|
-
required: ["tabId", "userId"],
|
|
226
|
-
},
|
|
227
|
-
optional: true,
|
|
228
|
-
handler: async (params) => {
|
|
229
|
-
const { tabId, userId } = params as { tabId: string; userId: string };
|
|
230
|
-
return fetchApi(baseUrl, `/tabs/${tabId}/screenshot?userId=${userId}`);
|
|
231
344
|
},
|
|
232
|
-
|
|
345
|
+
{ optional: true }
|
|
346
|
+
);
|
|
233
347
|
|
|
234
|
-
api.registerTool(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
348
|
+
api.registerTool(
|
|
349
|
+
{
|
|
350
|
+
name: "camoufox_close_tab",
|
|
351
|
+
description: "Close a browser tab.",
|
|
352
|
+
parameters: {
|
|
353
|
+
type: "object",
|
|
354
|
+
properties: {
|
|
355
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
356
|
+
userId: { type: "string", description: "User identifier" },
|
|
357
|
+
},
|
|
358
|
+
required: ["tabId", "userId"],
|
|
359
|
+
},
|
|
360
|
+
async execute(_id, params) {
|
|
361
|
+
const { tabId, userId } = params as { tabId: string; userId: string };
|
|
362
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}?userId=${userId}`, {
|
|
363
|
+
method: "DELETE",
|
|
364
|
+
});
|
|
365
|
+
return toToolResult(result);
|
|
242
366
|
},
|
|
243
|
-
required: ["tabId", "userId"],
|
|
244
|
-
},
|
|
245
|
-
optional: true,
|
|
246
|
-
handler: async (params) => {
|
|
247
|
-
const { tabId, userId } = params as { tabId: string; userId: string };
|
|
248
|
-
return fetchApi(baseUrl, `/tabs/${tabId}?userId=${userId}`, {
|
|
249
|
-
method: "DELETE",
|
|
250
|
-
});
|
|
251
367
|
},
|
|
252
|
-
|
|
368
|
+
{ optional: true }
|
|
369
|
+
);
|
|
253
370
|
|
|
254
|
-
api.registerTool(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
371
|
+
api.registerTool(
|
|
372
|
+
{
|
|
373
|
+
name: "camoufox_list_tabs",
|
|
374
|
+
description: "List all open tabs for a user.",
|
|
375
|
+
parameters: {
|
|
376
|
+
type: "object",
|
|
377
|
+
properties: {
|
|
378
|
+
userId: { type: "string", description: "User identifier" },
|
|
379
|
+
},
|
|
380
|
+
required: ["userId"],
|
|
381
|
+
},
|
|
382
|
+
async execute(_id, params) {
|
|
383
|
+
const { userId } = params as { userId: string };
|
|
384
|
+
const result = await fetchApi(baseUrl, `/tabs?userId=${userId}`);
|
|
385
|
+
return toToolResult(result);
|
|
261
386
|
},
|
|
262
|
-
required: ["userId"],
|
|
263
|
-
},
|
|
264
|
-
optional: true,
|
|
265
|
-
handler: async (params) => {
|
|
266
|
-
const { userId } = params as { userId: string };
|
|
267
|
-
return fetchApi(baseUrl, `/tabs?userId=${userId}`);
|
|
268
387
|
},
|
|
269
|
-
|
|
388
|
+
{ optional: true }
|
|
389
|
+
);
|
|
270
390
|
|
|
271
391
|
api.registerCommand({
|
|
272
392
|
name: "camoufox",
|
|
@@ -278,22 +398,24 @@ export default function register(api: PluginApi) {
|
|
|
278
398
|
try {
|
|
279
399
|
const health = await fetchApi(baseUrl, "/health");
|
|
280
400
|
api.log.info(`Camoufox server at ${baseUrl}: ${JSON.stringify(health)}`);
|
|
281
|
-
} catch
|
|
401
|
+
} catch {
|
|
282
402
|
api.log.error(`Camoufox server at ${baseUrl}: not reachable`);
|
|
283
403
|
}
|
|
284
404
|
break;
|
|
285
405
|
case "start":
|
|
286
406
|
if (serverProcess) {
|
|
287
|
-
api.log.info("Camoufox server already running");
|
|
407
|
+
api.log.info("Camoufox server already running (managed)");
|
|
288
408
|
return;
|
|
289
409
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
410
|
+
if (await checkServerRunning(baseUrl)) {
|
|
411
|
+
api.log.info(`Camoufox server already running at ${baseUrl}`);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
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
|
+
}
|
|
297
419
|
break;
|
|
298
420
|
case "stop":
|
|
299
421
|
if (serverProcess) {
|