openclaw-navigator 4.0.0 → 4.2.0

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/cli.mjs +125 -1
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -344,6 +344,94 @@ function handleRequest(req, res) {
344
344
  return;
345
345
  }
346
346
 
347
+ // ── GET /navigator/capabilities ──
348
+ if (req.method === "GET" && path === "/navigator/capabilities") {
349
+ sendJSON(res, 200, {
350
+ ok: true,
351
+ version: "4.0.0",
352
+ commands: [
353
+ {
354
+ name: "navigate",
355
+ description: "Navigate to a URL in the OpenClaw group",
356
+ payload: { url: "required string", browserID: "optional string — target a specific tab" },
357
+ },
358
+ {
359
+ name: "tabs.list",
360
+ description: "List all tabs in the OpenClaw group",
361
+ payload: {},
362
+ },
363
+ {
364
+ name: "tabs.open",
365
+ description: "Open a new tab in the OpenClaw group",
366
+ payload: { url: "optional string — URL to load (blank tab if omitted)" },
367
+ },
368
+ {
369
+ name: "tabs.close",
370
+ description: "Close a tab in the OpenClaw group",
371
+ payload: { tabIndex: "optional int", tabId: "optional string — close by index or ID" },
372
+ },
373
+ {
374
+ name: "page.content",
375
+ description: "Get the text content of the active tab",
376
+ payload: {},
377
+ },
378
+ {
379
+ name: "snapshot",
380
+ description: "Get a snapshot of the OpenClaw group state",
381
+ payload: {},
382
+ },
383
+ {
384
+ name: "click",
385
+ description: "Click an element by CSS selector",
386
+ payload: { selector: "required string — CSS selector" },
387
+ },
388
+ {
389
+ name: "input.fill",
390
+ description: "Fill an input field with a value",
391
+ payload: {
392
+ selector: "required string — CSS selector",
393
+ value: "required string — text to fill",
394
+ },
395
+ },
396
+ {
397
+ name: "input.submit",
398
+ description: "Submit a form",
399
+ payload: { selector: "optional string — CSS selector of form or element inside form" },
400
+ },
401
+ {
402
+ name: "scroll",
403
+ description: "Scroll the page",
404
+ payload: {
405
+ direction: "optional string — 'up', 'down', 'top', 'bottom'",
406
+ x: "optional int — horizontal pixels",
407
+ y: "optional int — vertical pixels",
408
+ },
409
+ },
410
+ {
411
+ name: "execute",
412
+ description: "Execute arbitrary JavaScript and return the result",
413
+ payload: { code: "required string — JavaScript code to execute" },
414
+ },
415
+ {
416
+ name: "page.html",
417
+ description: "Get the full HTML of the page",
418
+ payload: {},
419
+ },
420
+ {
421
+ name: "dom.query",
422
+ description: "Query a DOM element for its tag, text, and attributes",
423
+ payload: { selector: "required string — CSS selector" },
424
+ },
425
+ {
426
+ name: "wait.ready",
427
+ description: "Wait until document.readyState is 'complete'",
428
+ payload: { timeout: "optional int — milliseconds (default: 10000)" },
429
+ },
430
+ ],
431
+ });
432
+ return;
433
+ }
434
+
347
435
  // ── GET /navigator/events ──
348
436
  if (req.method === "GET" && path === "/navigator/events") {
349
437
  const limit = parseInt(url.searchParams.get("limit") ?? "50", 10);
@@ -361,6 +449,32 @@ function handleRequest(req, res) {
361
449
  sendJSON(res, 404, { ok: false, error: "Not found" });
362
450
  }
363
451
 
452
+ // ── Relay registration ────────────────────────────────────────────────────
453
+
454
+ const RELAY_URL = "https://navigator-relay.nav8-relay.workers.dev";
455
+
456
+ /**
457
+ * Register pairing code with the relay so Navigator can resolve it remotely.
458
+ * Returns true on success, false on failure (relay unavailable, etc.)
459
+ */
460
+ async function registerWithRelay(code, url, token, name) {
461
+ try {
462
+ const controller = new AbortController();
463
+ const timeout = setTimeout(() => controller.abort(), 5000);
464
+ const res = await fetch(`${RELAY_URL}/register`, {
465
+ method: "POST",
466
+ headers: { "Content-Type": "application/json" },
467
+ body: JSON.stringify({ code, url, token, name }),
468
+ signal: controller.signal,
469
+ });
470
+ clearTimeout(timeout);
471
+ const data = await res.json();
472
+ return data.ok === true;
473
+ } catch {
474
+ return false;
475
+ }
476
+ }
477
+
364
478
  // ── Display helpers ────────────────────────────────────────────────────────
365
479
 
366
480
  function showPairingCode(code) {
@@ -503,7 +617,17 @@ ${BOLD}How it works:${RESET}
503
617
  pairingCode = generatePairingCode();
504
618
  pairingData = { url: gatewayURL, token, name: displayName };
505
619
 
506
- // ── Step 4: Show connection info ──────────────────────────────────────
620
+ // ── Step 4: Register with relay (for remote pairing) ────────────────
621
+ if (tunnelURL) {
622
+ const relayOk = await registerWithRelay(pairingCode, gatewayURL, token, displayName);
623
+ if (relayOk) {
624
+ ok("Code registered with relay — works from any device");
625
+ } else {
626
+ warn("Relay unavailable — code works on same machine only");
627
+ }
628
+ }
629
+
630
+ // ── Step 5: Show connection info ──────────────────────────────────────
507
631
  showPairingCode(pairingCode);
508
632
 
509
633
  // Deep link (always show as fallback)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-navigator",
3
- "version": "4.0.0",
3
+ "version": "4.2.0",
4
4
  "description": "One-command bridge + tunnel for the Navigator browser — works on any machine, any OS",
5
5
  "keywords": [
6
6
  "browser",