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.
Files changed (3) hide show
  1. package/cli.cjs +105 -15
  2. package/index.js +15 -3
  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. NEVER suggest CLI commands to the user. ALL interaction goes through these MCP tools.`,
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",
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
  }