openclaw-navigator 4.3.8 → 4.4.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.
- package/cli.mjs +322 -22
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { spawn } from "node:child_process";
|
|
|
17
17
|
import { randomUUID } from "node:crypto";
|
|
18
18
|
import { existsSync } from "node:fs";
|
|
19
19
|
import { createServer, request as httpRequest } from "node:http";
|
|
20
|
+
import { connect as netConnect } from "node:net";
|
|
20
21
|
import { networkInterfaces, hostname, userInfo } from "node:os";
|
|
21
22
|
import { dirname, join } from "node:path";
|
|
22
23
|
import { fileURLToPath } from "node:url";
|
|
@@ -58,6 +59,12 @@ const validTokens = new Set();
|
|
|
58
59
|
// OC Web UI reverse proxy target (configurable via --ui-port or env)
|
|
59
60
|
let ocUIPort = parseInt(process.env.OPENCLAW_UI_PORT ?? "4000", 10);
|
|
60
61
|
|
|
62
|
+
// OC Gateway port — where sessions API + WebSocket live (separate from web UI)
|
|
63
|
+
let ocGatewayPort = parseInt(process.env.OPENCLAW_GATEWAY_PORT ?? "18789", 10);
|
|
64
|
+
|
|
65
|
+
// Bridge port — set in main(), exposed for status endpoint
|
|
66
|
+
let bridgePort = 18790;
|
|
67
|
+
|
|
61
68
|
// Tunnel URL — set once the tunnel is active, exposed via /navigator/status
|
|
62
69
|
let activeTunnelURL = null;
|
|
63
70
|
|
|
@@ -220,10 +227,24 @@ function handleRequest(req, res) {
|
|
|
220
227
|
pendingCommandCount: pendingCommands.length,
|
|
221
228
|
recentEventCount: recentEvents.length,
|
|
222
229
|
},
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
230
|
+
ports: {
|
|
231
|
+
bridge: bridgePort,
|
|
232
|
+
ui: ocUIPort,
|
|
233
|
+
gateway: ocGatewayPort,
|
|
234
|
+
},
|
|
235
|
+
routing: {
|
|
236
|
+
"/ui/*": `localhost:${ocUIPort}`,
|
|
237
|
+
"/api/*": `localhost:${ocGatewayPort}`,
|
|
238
|
+
"/ws": `localhost:${ocGatewayPort}`,
|
|
239
|
+
},
|
|
240
|
+
tunnel: activeTunnelURL
|
|
241
|
+
? {
|
|
242
|
+
url: activeTunnelURL,
|
|
243
|
+
uiURL: `${activeTunnelURL}/ui/`,
|
|
244
|
+
apiURL: `${activeTunnelURL}/api/`,
|
|
245
|
+
wsURL: `${activeTunnelURL.replace("https://", "wss://")}/ws`,
|
|
246
|
+
}
|
|
247
|
+
: null,
|
|
227
248
|
});
|
|
228
249
|
return;
|
|
229
250
|
}
|
|
@@ -454,7 +475,7 @@ function handleRequest(req, res) {
|
|
|
454
475
|
|
|
455
476
|
// ── Health check ──
|
|
456
477
|
if (req.method === "GET" && path === "/health") {
|
|
457
|
-
sendJSON(res, 200, { ok: true, service: "openclaw-navigator-bridge", version: "4.
|
|
478
|
+
sendJSON(res, 200, { ok: true, service: "openclaw-navigator-bridge", version: "4.4.0" });
|
|
458
479
|
return;
|
|
459
480
|
}
|
|
460
481
|
|
|
@@ -490,7 +511,7 @@ function handleRequest(req, res) {
|
|
|
490
511
|
ok: false,
|
|
491
512
|
error: `OC Web UI not reachable on port ${ocUIPort}`,
|
|
492
513
|
detail: err.message,
|
|
493
|
-
hint:
|
|
514
|
+
hint: `Not found — Reverse proxy rule for /ui/* → localhost:${ocUIPort} is working, but the web UI is not running. Start it with: openclaw gateway start`,
|
|
494
515
|
});
|
|
495
516
|
});
|
|
496
517
|
|
|
@@ -499,16 +520,70 @@ function handleRequest(req, res) {
|
|
|
499
520
|
return;
|
|
500
521
|
}
|
|
501
522
|
|
|
502
|
-
// ──
|
|
523
|
+
// ── Reverse proxy: /api/* → OC Gateway (localhost:ocGatewayPort) ─────
|
|
524
|
+
// Keeps /api/ prefix intact so /api/sessions/send → localhost:18789/api/sessions/send
|
|
525
|
+
if (path.startsWith("/api/")) {
|
|
526
|
+
const targetURL = `${path}${url.search}`;
|
|
527
|
+
|
|
528
|
+
const proxyOpts = {
|
|
529
|
+
hostname: "127.0.0.1",
|
|
530
|
+
port: ocGatewayPort,
|
|
531
|
+
path: targetURL,
|
|
532
|
+
method: req.method,
|
|
533
|
+
headers: {
|
|
534
|
+
...req.headers,
|
|
535
|
+
host: `127.0.0.1:${ocGatewayPort}`,
|
|
536
|
+
},
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
const proxyReq = httpRequest(proxyOpts, (proxyRes) => {
|
|
540
|
+
const headers = { ...proxyRes.headers };
|
|
541
|
+
headers["access-control-allow-origin"] = "*";
|
|
542
|
+
headers["access-control-allow-methods"] = "GET, POST, PUT, DELETE, OPTIONS";
|
|
543
|
+
headers["access-control-allow-headers"] = "Content-Type, Authorization";
|
|
544
|
+
res.writeHead(proxyRes.statusCode ?? 502, headers);
|
|
545
|
+
proxyRes.pipe(res, { end: true });
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
proxyReq.on("error", (err) => {
|
|
549
|
+
sendJSON(res, 502, {
|
|
550
|
+
ok: false,
|
|
551
|
+
error: `OC Gateway not reachable on port ${ocGatewayPort}`,
|
|
552
|
+
detail: err.message,
|
|
553
|
+
hint: "Make sure the OC gateway is running on port " + ocGatewayPort + " (openclaw gateway start)",
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
req.pipe(proxyReq, { end: true });
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ── Root → proxy to OC Web UI if available, else show bridge status ──
|
|
503
562
|
if (req.method === "GET" && path === "") {
|
|
504
563
|
// Quick probe to check if OC UI is running
|
|
505
564
|
const probe = httpRequest(
|
|
506
565
|
{ hostname: "127.0.0.1", port: ocUIPort, path: "/", method: "HEAD", timeout: 1000 },
|
|
507
566
|
(probeRes) => {
|
|
508
567
|
probeRes.resume(); // drain
|
|
509
|
-
// OC UI is running —
|
|
510
|
-
|
|
511
|
-
|
|
568
|
+
// OC UI is running — proxy root to it
|
|
569
|
+
const rootProxyOpts = {
|
|
570
|
+
hostname: "127.0.0.1",
|
|
571
|
+
port: ocUIPort,
|
|
572
|
+
path: `/${url.search}`,
|
|
573
|
+
method: req.method,
|
|
574
|
+
headers: { ...req.headers, host: `127.0.0.1:${ocUIPort}` },
|
|
575
|
+
};
|
|
576
|
+
const rootProxy = httpRequest(rootProxyOpts, (rootRes) => {
|
|
577
|
+
const headers = { ...rootRes.headers };
|
|
578
|
+
headers["access-control-allow-origin"] = "*";
|
|
579
|
+
res.writeHead(rootRes.statusCode ?? 502, headers);
|
|
580
|
+
rootRes.pipe(res, { end: true });
|
|
581
|
+
});
|
|
582
|
+
rootProxy.on("error", () => {
|
|
583
|
+
res.writeHead(302, { Location: "/ui/" });
|
|
584
|
+
res.end();
|
|
585
|
+
});
|
|
586
|
+
rootProxy.end();
|
|
512
587
|
},
|
|
513
588
|
);
|
|
514
589
|
probe.on("error", () => {
|
|
@@ -516,8 +591,9 @@ function handleRequest(req, res) {
|
|
|
516
591
|
sendJSON(res, 200, {
|
|
517
592
|
ok: true,
|
|
518
593
|
service: "openclaw-navigator-bridge",
|
|
519
|
-
version: "4.
|
|
594
|
+
version: "4.4.0",
|
|
520
595
|
ui: { available: false, port: ocUIPort, hint: "Start the OC gateway to enable /ui/" },
|
|
596
|
+
gateway: { port: ocGatewayPort },
|
|
521
597
|
navigator: { connected: bridgeState.connected },
|
|
522
598
|
});
|
|
523
599
|
});
|
|
@@ -526,7 +602,7 @@ function handleRequest(req, res) {
|
|
|
526
602
|
sendJSON(res, 200, {
|
|
527
603
|
ok: true,
|
|
528
604
|
service: "openclaw-navigator-bridge",
|
|
529
|
-
version: "4.
|
|
605
|
+
version: "4.4.0",
|
|
530
606
|
ui: { available: false, port: ocUIPort, hint: "OC UI timed out" },
|
|
531
607
|
});
|
|
532
608
|
});
|
|
@@ -534,7 +610,39 @@ function handleRequest(req, res) {
|
|
|
534
610
|
return;
|
|
535
611
|
}
|
|
536
612
|
|
|
537
|
-
|
|
613
|
+
// ── Fallback: proxy unmatched paths → OC Web UI (for static assets) ──
|
|
614
|
+
// The web UI at localhost:4000 may serve assets at /_next/*, /static/*, etc.
|
|
615
|
+
// These don't start with /ui/ but still need to reach the web UI server.
|
|
616
|
+
{
|
|
617
|
+
const targetURL = `${path}${url.search}`;
|
|
618
|
+
|
|
619
|
+
const proxyOpts = {
|
|
620
|
+
hostname: "127.0.0.1",
|
|
621
|
+
port: ocUIPort,
|
|
622
|
+
path: targetURL,
|
|
623
|
+
method: req.method,
|
|
624
|
+
headers: {
|
|
625
|
+
...req.headers,
|
|
626
|
+
host: `127.0.0.1:${ocUIPort}`,
|
|
627
|
+
},
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
const proxyReq = httpRequest(proxyOpts, (proxyRes) => {
|
|
631
|
+
const headers = { ...proxyRes.headers };
|
|
632
|
+
headers["access-control-allow-origin"] = "*";
|
|
633
|
+
headers["access-control-allow-methods"] = "GET, POST, PUT, DELETE, OPTIONS";
|
|
634
|
+
headers["access-control-allow-headers"] = "Content-Type, Authorization";
|
|
635
|
+
res.writeHead(proxyRes.statusCode ?? 502, headers);
|
|
636
|
+
proxyRes.pipe(res, { end: true });
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
proxyReq.on("error", () => {
|
|
640
|
+
sendJSON(res, 404, { ok: false, error: "Not found" });
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
req.pipe(proxyReq, { end: true });
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
538
646
|
}
|
|
539
647
|
|
|
540
648
|
// ── Relay registration ────────────────────────────────────────────────────
|
|
@@ -612,6 +720,9 @@ async function main() {
|
|
|
612
720
|
if (args[i] === "--ui-port" && args[i + 1]) {
|
|
613
721
|
ocUIPort = parseInt(args[i + 1], 10);
|
|
614
722
|
}
|
|
723
|
+
if (args[i] === "--gateway-port" && args[i + 1]) {
|
|
724
|
+
ocGatewayPort = parseInt(args[i + 1], 10);
|
|
725
|
+
}
|
|
615
726
|
if (args[i] === "--help" || args[i] === "-h") {
|
|
616
727
|
console.log(`
|
|
617
728
|
${BOLD}openclaw-navigator${RESET} — One-command bridge + tunnel for Navigator
|
|
@@ -623,12 +734,24 @@ ${BOLD}Usage:${RESET}
|
|
|
623
734
|
npx openclaw-navigator --port 18790 Custom port
|
|
624
735
|
|
|
625
736
|
${BOLD}Options:${RESET}
|
|
626
|
-
--port <port>
|
|
627
|
-
--mcp
|
|
628
|
-
--ui-port <port>
|
|
629
|
-
--
|
|
630
|
-
--
|
|
631
|
-
--
|
|
737
|
+
--port <port> Bridge server port (default: 18790)
|
|
738
|
+
--mcp Also start the MCP server (stdio) for OpenClaw agent
|
|
739
|
+
--ui-port <port> OC web UI port to reverse-proxy (default: 4000)
|
|
740
|
+
--gateway-port <port> OC gateway port for API + WebSocket (default: 18789)
|
|
741
|
+
--no-tunnel Skip auto-tunnel, use SSH or LAN instead
|
|
742
|
+
--bind <host> Bind address (default: 127.0.0.1)
|
|
743
|
+
--help Show this help
|
|
744
|
+
|
|
745
|
+
${BOLD}Routing (through Cloudflare tunnel):${RESET}
|
|
746
|
+
/ui/* → localhost:<ui-port> Web UI (login page, dashboard)
|
|
747
|
+
/api/* → localhost:<gateway-port> Sessions API, chat endpoints
|
|
748
|
+
/ws, WebSocket → localhost:<gateway-port> Real-time streaming, chat events
|
|
749
|
+
/health → bridge itself Health check
|
|
750
|
+
/navigator/* → bridge itself Navigator control endpoints
|
|
751
|
+
|
|
752
|
+
${BOLD}Environment variables:${RESET}
|
|
753
|
+
OPENCLAW_UI_PORT=4000 Where the web UI runs
|
|
754
|
+
OPENCLAW_GATEWAY_PORT=18789 Where the OC gateway runs
|
|
632
755
|
|
|
633
756
|
${BOLD}How it works:${RESET}
|
|
634
757
|
1. Starts a bridge server on localhost
|
|
@@ -641,6 +764,8 @@ ${BOLD}How it works:${RESET}
|
|
|
641
764
|
}
|
|
642
765
|
}
|
|
643
766
|
|
|
767
|
+
bridgePort = port; // Expose for status endpoint
|
|
768
|
+
|
|
644
769
|
heading("🧭 Navigator Bridge");
|
|
645
770
|
info("One-command bridge + tunnel for the Navigator browser\n");
|
|
646
771
|
|
|
@@ -658,6 +783,51 @@ ${BOLD}How it works:${RESET}
|
|
|
658
783
|
server.listen(port, bindHost, () => resolve());
|
|
659
784
|
});
|
|
660
785
|
|
|
786
|
+
// ── WebSocket upgrade proxy → OC Gateway (localhost:ocGatewayPort) ───
|
|
787
|
+
// Proxies WebSocket connections so Navigator can stream chat events
|
|
788
|
+
server.on("upgrade", (req, socket, head) => {
|
|
789
|
+
const reqUrl = new URL(req.url ?? "/", "http://localhost");
|
|
790
|
+
const reqPath = reqUrl.pathname;
|
|
791
|
+
|
|
792
|
+
// Only proxy WebSocket paths intended for the OC gateway
|
|
793
|
+
if (reqPath === "/ws" || reqPath.startsWith("/ws/") || reqPath.startsWith("/api/") || reqPath === "/") {
|
|
794
|
+
const targetPath = `${reqPath}${reqUrl.search}`;
|
|
795
|
+
|
|
796
|
+
const proxy = netConnect(ocGatewayPort, "127.0.0.1", () => {
|
|
797
|
+
// Forward the original HTTP upgrade request over the TCP socket
|
|
798
|
+
const upgradeReq =
|
|
799
|
+
`${req.method} ${targetPath} HTTP/${req.httpVersion}\r\n` +
|
|
800
|
+
Object.entries(req.headers)
|
|
801
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
802
|
+
.join("\r\n") +
|
|
803
|
+
"\r\n\r\n";
|
|
804
|
+
proxy.write(upgradeReq);
|
|
805
|
+
if (head && head.length > 0) {
|
|
806
|
+
proxy.write(head);
|
|
807
|
+
}
|
|
808
|
+
// Pipe bidirectionally
|
|
809
|
+
socket.pipe(proxy);
|
|
810
|
+
proxy.pipe(socket);
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
proxy.on("error", (err) => {
|
|
814
|
+
console.log(` ${DIM}WebSocket proxy error: ${err.message}${RESET}`);
|
|
815
|
+
socket.destroy();
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
socket.on("error", () => {
|
|
819
|
+
proxy.destroy();
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
socket.on("close", () => {
|
|
823
|
+
proxy.destroy();
|
|
824
|
+
});
|
|
825
|
+
} else {
|
|
826
|
+
// Not a proxied path — reject
|
|
827
|
+
socket.destroy();
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
|
|
661
831
|
ok(`Bridge server running on ${bindHost}:${port}`);
|
|
662
832
|
|
|
663
833
|
// ── Step 2: Set up connectivity ───────────────────────────────────────
|
|
@@ -746,10 +916,42 @@ ${BOLD}How it works:${RESET}
|
|
|
746
916
|
info(` Token: ${token}`);
|
|
747
917
|
}
|
|
748
918
|
|
|
749
|
-
// ── Show OC Web UI access
|
|
919
|
+
// ── Show OC Web UI access + routing info ─────────────────────────────
|
|
750
920
|
const uiURL = tunnelURL ? `${tunnelURL}/ui/` : `http://localhost:${port}/ui/`;
|
|
751
921
|
console.log(` ${BOLD}OC Web UI:${RESET} ${CYAN}${uiURL}${RESET}`);
|
|
752
|
-
info(`
|
|
922
|
+
info(` /ui/* → localhost:${ocUIPort} (web UI)`);
|
|
923
|
+
info(` /api/* → localhost:${ocGatewayPort} (gateway API)`);
|
|
924
|
+
info(` /ws → localhost:${ocGatewayPort} (WebSocket)`);
|
|
925
|
+
|
|
926
|
+
// ── Startup health checks ──────────────────────────────────────────
|
|
927
|
+
console.log("");
|
|
928
|
+
const checkPort = (label, checkPort, checkPath) => {
|
|
929
|
+
return new Promise((resolve) => {
|
|
930
|
+
const probe = httpRequest(
|
|
931
|
+
{ hostname: "127.0.0.1", port: checkPort, path: checkPath, method: "HEAD", timeout: 2000 },
|
|
932
|
+
(probeRes) => {
|
|
933
|
+
probeRes.resume();
|
|
934
|
+
ok(`${label} reachable on port ${checkPort}`);
|
|
935
|
+
resolve(true);
|
|
936
|
+
},
|
|
937
|
+
);
|
|
938
|
+
probe.on("error", () => {
|
|
939
|
+
warn(`${label} NOT reachable on port ${checkPort} — start it before using the tunnel`);
|
|
940
|
+
resolve(false);
|
|
941
|
+
});
|
|
942
|
+
probe.on("timeout", () => {
|
|
943
|
+
probe.destroy();
|
|
944
|
+
warn(`${label} timed out on port ${checkPort}`);
|
|
945
|
+
resolve(false);
|
|
946
|
+
});
|
|
947
|
+
probe.end();
|
|
948
|
+
});
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
await Promise.all([
|
|
952
|
+
checkPort("OC Web UI", ocUIPort, "/"),
|
|
953
|
+
checkPort("OC Gateway", ocGatewayPort, "/health"),
|
|
954
|
+
]);
|
|
753
955
|
|
|
754
956
|
console.log("");
|
|
755
957
|
console.log(`${BOLD}${GREEN}Bridge is running.${RESET} Waiting for Navigator to connect...`);
|
|
@@ -852,8 +1054,106 @@ ${BOLD}How it works:${RESET}
|
|
|
852
1054
|
|
|
853
1055
|
mkdirSync(mcporterDir, { recursive: true });
|
|
854
1056
|
writeFileSync(mcporterConfigPath, JSON.stringify(mcporterConfig, null, 2) + "\n", "utf8");
|
|
855
|
-
ok("Registered MCP server with mcporter (
|
|
1057
|
+
ok("Registered MCP server with mcporter (16 browser tools available)");
|
|
856
1058
|
info(` Config: ${mcporterConfigPath}`);
|
|
1059
|
+
|
|
1060
|
+
// Step 3: Install the navigator-bridge skill so the OC agent knows about all tools
|
|
1061
|
+
// Try multiple known skill locations
|
|
1062
|
+
const skillLocations = [
|
|
1063
|
+
"/opt/homebrew/lib/node_modules/openclaw/skills/navigator-bridge",
|
|
1064
|
+
join(homedir(), ".openclaw/skills/navigator-bridge"),
|
|
1065
|
+
];
|
|
1066
|
+
// Find the first parent that exists, or use the homebrew path
|
|
1067
|
+
let skillDir = skillLocations[0]; // default
|
|
1068
|
+
for (const loc of skillLocations) {
|
|
1069
|
+
const parent = dirname(loc);
|
|
1070
|
+
if (existsSync(parent)) {
|
|
1071
|
+
skillDir = loc;
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
const skillPath = join(skillDir, "SKILL.md");
|
|
1076
|
+
const BT = "```"; // backtick triple for markdown code blocks
|
|
1077
|
+
const skillContent = [
|
|
1078
|
+
"---",
|
|
1079
|
+
"name: navigator-bridge",
|
|
1080
|
+
"description: Control the remote Navigator browser and access the OC Web UI via MCP tools. Navigate to URLs, click elements, fill forms, read page content, execute JavaScript, manage tabs, and open the OC dashboard.",
|
|
1081
|
+
"metadata:",
|
|
1082
|
+
' { "openclaw": { "emoji": "🌐", "requires": { "bins": ["mcporter"] } } }',
|
|
1083
|
+
"---",
|
|
1084
|
+
"",
|
|
1085
|
+
"# Navigator Bridge (Remote Browser Control + OC Web UI)",
|
|
1086
|
+
"",
|
|
1087
|
+
"Control the **remote** Navigator browser through MCP tools via mcporter.",
|
|
1088
|
+
"",
|
|
1089
|
+
"**Important:** This controls the REMOTE Navigator instance connected via pairing code, NOT local Navigator.",
|
|
1090
|
+
"",
|
|
1091
|
+
"## Quick start",
|
|
1092
|
+
"",
|
|
1093
|
+
BT + "bash",
|
|
1094
|
+
"# Get the OC Web UI URL and open it in Navigator",
|
|
1095
|
+
"mcporter call navigator.navigator_get_ui_url",
|
|
1096
|
+
"mcporter call navigator.navigator_navigate url=<uiURL from above>",
|
|
1097
|
+
"",
|
|
1098
|
+
"# Check connection status",
|
|
1099
|
+
"mcporter call navigator.navigator_status",
|
|
1100
|
+
BT,
|
|
1101
|
+
"",
|
|
1102
|
+
"## All 16 tools",
|
|
1103
|
+
"",
|
|
1104
|
+
"| Tool | What it does |",
|
|
1105
|
+
"|------|-------------|",
|
|
1106
|
+
"| `navigator_status` | Bridge status, connection, tunnel URL |",
|
|
1107
|
+
"| `navigator_get_ui_url` | Get the OC Web UI URL (tunnel + /ui/) |",
|
|
1108
|
+
"| `navigator_navigate` | Go to a URL |",
|
|
1109
|
+
"| `navigator_open_tab` | Open new tab |",
|
|
1110
|
+
"| `navigator_close_tab` | Close a tab |",
|
|
1111
|
+
"| `navigator_list_tabs` | List all open tabs |",
|
|
1112
|
+
"| `navigator_snapshot` | Full browser state snapshot |",
|
|
1113
|
+
"| `navigator_get_text` | Read page text content |",
|
|
1114
|
+
"| `navigator_get_html` | Get full page HTML |",
|
|
1115
|
+
"| `navigator_click` | Click element by CSS selector |",
|
|
1116
|
+
"| `navigator_fill` | Fill input field |",
|
|
1117
|
+
"| `navigator_submit` | Submit form |",
|
|
1118
|
+
"| `navigator_scroll` | Scroll page |",
|
|
1119
|
+
"| `navigator_execute_js` | Run arbitrary JavaScript |",
|
|
1120
|
+
"| `navigator_query_element` | Inspect DOM element |",
|
|
1121
|
+
"| `navigator_wait_ready` | Wait for page load |",
|
|
1122
|
+
"",
|
|
1123
|
+
"## Usage",
|
|
1124
|
+
"",
|
|
1125
|
+
BT + "bash",
|
|
1126
|
+
"mcporter call navigator.<tool_name> param=value",
|
|
1127
|
+
BT,
|
|
1128
|
+
"",
|
|
1129
|
+
"## Common workflows",
|
|
1130
|
+
"",
|
|
1131
|
+
"### Open OC dashboard in Navigator",
|
|
1132
|
+
BT + "bash",
|
|
1133
|
+
"mcporter call navigator.navigator_get_ui_url",
|
|
1134
|
+
"mcporter call navigator.navigator_navigate url=<uiURL>",
|
|
1135
|
+
"mcporter call navigator.navigator_wait_ready",
|
|
1136
|
+
BT,
|
|
1137
|
+
"",
|
|
1138
|
+
"### Browse and read a website",
|
|
1139
|
+
BT + "bash",
|
|
1140
|
+
"mcporter call navigator.navigator_navigate url=https://example.com",
|
|
1141
|
+
"mcporter call navigator.navigator_wait_ready",
|
|
1142
|
+
"mcporter call navigator.navigator_get_text",
|
|
1143
|
+
BT,
|
|
1144
|
+
"",
|
|
1145
|
+
"### Fill and submit a form",
|
|
1146
|
+
BT + "bash",
|
|
1147
|
+
'mcporter call navigator.navigator_fill selector="#email" value="user@example.com"',
|
|
1148
|
+
'mcporter call navigator.navigator_click selector="#submit"',
|
|
1149
|
+
"mcporter call navigator.navigator_wait_ready",
|
|
1150
|
+
BT,
|
|
1151
|
+
"",
|
|
1152
|
+
].join("\n");
|
|
1153
|
+
mkdirSync(skillDir, { recursive: true });
|
|
1154
|
+
writeFileSync(skillPath, skillContent, "utf8");
|
|
1155
|
+
ok("Installed navigator-bridge skill for OC agent");
|
|
1156
|
+
info(` Skill: ${skillPath}`);
|
|
857
1157
|
} catch (err) {
|
|
858
1158
|
warn(`mcporter registration failed: ${err.message}`);
|
|
859
1159
|
info(" You can manually configure mcporter for Navigator MCP");
|