iframer-cli 1.0.3 → 1.0.4
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/cli.cjs +105 -15
- package/index.js +15 -3
- package/package.json +3 -2
package/cli.cjs
CHANGED
|
@@ -201,20 +201,6 @@ const [,, command, ...args] = process.argv;
|
|
|
201
201
|
|
|
202
202
|
async function main() {
|
|
203
203
|
switch (command) {
|
|
204
|
-
case "install": {
|
|
205
|
-
try {
|
|
206
|
-
execSync("claude mcp add iframer -- npx -y --package=iframer-cli iframer-mcp", { stdio: "inherit" });
|
|
207
|
-
console.log("\n iframer MCP installed in Claude Code!");
|
|
208
|
-
console.log(" Now run: iframer login");
|
|
209
|
-
} catch {
|
|
210
|
-
console.error(" Failed to install. Make sure Claude Code CLI is available.");
|
|
211
|
-
console.error(" You can manually add it with:");
|
|
212
|
-
console.error(" claude mcp add iframer -- npx -y --package=iframer-cli iframer-mcp");
|
|
213
|
-
process.exit(1);
|
|
214
|
-
}
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
204
|
case "login":
|
|
219
205
|
await login();
|
|
220
206
|
break;
|
|
@@ -418,6 +404,111 @@ async function main() {
|
|
|
418
404
|
break;
|
|
419
405
|
}
|
|
420
406
|
|
|
407
|
+
// ─── Proxy tunnel ───────────────────────────────────────────────
|
|
408
|
+
|
|
409
|
+
case "proxy": {
|
|
410
|
+
const token = requireToken();
|
|
411
|
+
const server = getServer();
|
|
412
|
+
const wsUrl = server.replace(/^http/, "ws") + "/proxy/tunnel?token=" + token;
|
|
413
|
+
|
|
414
|
+
let WebSocket;
|
|
415
|
+
try {
|
|
416
|
+
WebSocket = require("ws");
|
|
417
|
+
} catch {
|
|
418
|
+
console.error(" ws package required. Run: npm install -g ws");
|
|
419
|
+
console.error(" Or: bun add ws");
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const net = require("net");
|
|
424
|
+
const ws = new WebSocket(wsUrl);
|
|
425
|
+
const connections = new Map();
|
|
426
|
+
|
|
427
|
+
console.log("\n Connecting to server...");
|
|
428
|
+
|
|
429
|
+
ws.on("open", () => {
|
|
430
|
+
console.log(" Connected! Your browser sessions will route through this machine.");
|
|
431
|
+
console.log(" Keep this running. Press Ctrl+C to stop.\n");
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
ws.on("message", (msg) => {
|
|
435
|
+
if (Buffer.isBuffer(msg) && msg.length > 4) {
|
|
436
|
+
// Binary data frame: first 4 bytes = connId
|
|
437
|
+
const connId = msg.readUInt32BE(0);
|
|
438
|
+
const data = msg.slice(4);
|
|
439
|
+
const socket = connections.get(connId);
|
|
440
|
+
if (socket && !socket.destroyed) {
|
|
441
|
+
socket.write(data);
|
|
442
|
+
}
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
const data = JSON.parse(msg.toString());
|
|
448
|
+
|
|
449
|
+
if (data.type === "ready") {
|
|
450
|
+
console.log(` Proxy tunnel active (server port ${data.port})`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (data.type === "connect") {
|
|
454
|
+
const { id, host, port } = data;
|
|
455
|
+
const socket = net.createConnection({ host, port }, () => {
|
|
456
|
+
ws.send(JSON.stringify({ type: "connected", id }));
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
socket.on("data", (chunk) => {
|
|
460
|
+
const header = Buffer.alloc(4);
|
|
461
|
+
header.writeUInt32BE(id);
|
|
462
|
+
if (ws.readyState === 1) ws.send(Buffer.concat([header, chunk]));
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
socket.on("end", () => {
|
|
466
|
+
ws.send(JSON.stringify({ type: "close", id }));
|
|
467
|
+
connections.delete(id);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
socket.on("error", (err) => {
|
|
471
|
+
ws.send(JSON.stringify({ type: "error", id, message: err.message }));
|
|
472
|
+
connections.delete(id);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
connections.set(id, socket);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (data.type === "close") {
|
|
479
|
+
const socket = connections.get(data.id);
|
|
480
|
+
if (socket) {
|
|
481
|
+
socket.destroy();
|
|
482
|
+
connections.delete(data.id);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
} catch {}
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
ws.on("close", () => {
|
|
489
|
+
console.log("\n Disconnected from server.");
|
|
490
|
+
process.exit(0);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
ws.on("error", (err) => {
|
|
494
|
+
console.error(` Connection error: ${err.message}`);
|
|
495
|
+
process.exit(1);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
process.on("SIGINT", () => {
|
|
499
|
+
console.log("\n Stopping proxy tunnel...");
|
|
500
|
+
for (const [, socket] of connections) {
|
|
501
|
+
try { socket.destroy(); } catch {}
|
|
502
|
+
}
|
|
503
|
+
ws.close();
|
|
504
|
+
process.exit(0);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// Block forever
|
|
508
|
+
await new Promise(() => {});
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
|
|
421
512
|
// ─── Screenshot ────────────────────────────────────────────────
|
|
422
513
|
|
|
423
514
|
case "screenshot": {
|
|
@@ -532,7 +623,6 @@ async function main() {
|
|
|
532
623
|
iframer - CLI for the Agentic Browser API
|
|
533
624
|
|
|
534
625
|
Commands:
|
|
535
|
-
install Install iframer MCP in Claude Code
|
|
536
626
|
login Authenticate with the server
|
|
537
627
|
logout Remove saved credentials
|
|
538
628
|
status Show current auth status
|
package/index.js
CHANGED
|
@@ -75,8 +75,13 @@ WORKFLOW:
|
|
|
75
75
|
6. For logins: check credentials with "status". If credentials are missing, call "store_credentials" to prompt the user — NEVER tell them to use the CLI. Then use "login" tool.
|
|
76
76
|
7. Always call "interactive_stop" when done to save session state.
|
|
77
77
|
|
|
78
|
+
CAPTCHA STRATEGY (escalation order):
|
|
79
|
+
1. First try headless — many sites use invisible reCAPTCHA v3 that passes without a challenge.
|
|
80
|
+
2. If a challenge appears, use recaptcha_solve + recaptcha_answer to solve it visually.
|
|
81
|
+
3. If challenges keep looping (3+ attempts), the server IP is flagged. Tell the user to run "iframer proxy" in a separate terminal to route traffic through their residential IP. This is the ONLY CLI command you may suggest. Check proxy status with the "status" tool.
|
|
82
|
+
|
|
78
83
|
MINIMIZE TOKEN USAGE: Don't screenshot every action. Only look when you need to decide what to do next.
|
|
79
|
-
NEVER use bash/CLI
|
|
84
|
+
NEVER use bash/CLI (except "iframer proxy" when CAPTCHA fails). ALL interaction goes through these MCP tools.`,
|
|
80
85
|
});
|
|
81
86
|
|
|
82
87
|
// ─── Tool 0: status (call this first) ────────────────────────────────
|
|
@@ -122,6 +127,12 @@ server.tool(
|
|
|
122
127
|
if (credData.ok) status.credentials = credData.domains;
|
|
123
128
|
} catch {}
|
|
124
129
|
|
|
130
|
+
// Check proxy tunnel
|
|
131
|
+
try {
|
|
132
|
+
const proxyData = await apiGet("/proxy/status");
|
|
133
|
+
if (proxyData.ok) status.proxyTunnel = proxyData.active;
|
|
134
|
+
} catch {}
|
|
135
|
+
|
|
125
136
|
return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
|
|
126
137
|
} catch (err) {
|
|
127
138
|
return errorResponse(`Error: ${err.message}`);
|
|
@@ -214,7 +225,8 @@ server.tool(
|
|
|
214
225
|
"interactive_act",
|
|
215
226
|
`Send an action to the interactive browser and get a screenshot URL of the result.
|
|
216
227
|
|
|
217
|
-
Actions: click, human-click, human-type, fill, navigate, scroll, wait, evaluate, wait-for-selector, keyboard.
|
|
228
|
+
Actions: click, human-click, human-type, fill, navigate, scroll, wait, evaluate, wait-for-selector, keyboard, type-code.
|
|
229
|
+
type-code: Type a verification code into split digit inputs in one call. Pass the full code as value (e.g. "735948"). Optionally pass selector for the first input (defaults to input[type="tel"]).
|
|
218
230
|
reCAPTCHA: recaptcha-click (click checkbox), recaptcha-select (select tiles by index), recaptcha-verify (click verify), recaptcha-info (get challenge metadata).
|
|
219
231
|
|
|
220
232
|
Screenshots are saved as files and returned as URLs. Use Read tool to view them. For reCAPTCHA, individual tile image URLs are returned.`,
|
|
@@ -222,7 +234,7 @@ Screenshots are saved as files and returned as URLs. Use Read tool to view them.
|
|
|
222
234
|
action: z.enum([
|
|
223
235
|
"click", "human-click", "human-type", "fill",
|
|
224
236
|
"navigate", "scroll", "wait", "evaluate",
|
|
225
|
-
"wait-for-selector", "keyboard",
|
|
237
|
+
"wait-for-selector", "keyboard", "type-code",
|
|
226
238
|
"recaptcha-click", "recaptcha-select", "recaptcha-verify", "recaptcha-info",
|
|
227
239
|
]).describe("Action type to perform"),
|
|
228
240
|
selector: z.string().optional().describe("CSS selector (for click, fill, human-click, human-type, wait-for-selector)"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iframer-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "CLI and MCP server for iframer — a headful/headless browser for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"url": "https://github.com/EduardoFazolo/iframer-agentic-browser"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
29
|
+
"ws": "^8.20.0"
|
|
29
30
|
}
|
|
30
31
|
}
|