openclaw-navigator 4.3.6 → 4.3.8
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 +64 -36
- package/mcp.mjs +25 -1
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -58,6 +58,9 @@ const validTokens = new Set();
|
|
|
58
58
|
// OC Web UI reverse proxy target (configurable via --ui-port or env)
|
|
59
59
|
let ocUIPort = parseInt(process.env.OPENCLAW_UI_PORT ?? "4000", 10);
|
|
60
60
|
|
|
61
|
+
// Tunnel URL — set once the tunnel is active, exposed via /navigator/status
|
|
62
|
+
let activeTunnelURL = null;
|
|
63
|
+
|
|
61
64
|
// Pairing code state
|
|
62
65
|
let pairingCode = null;
|
|
63
66
|
let pairingData = null;
|
|
@@ -217,6 +220,10 @@ function handleRequest(req, res) {
|
|
|
217
220
|
pendingCommandCount: pendingCommands.length,
|
|
218
221
|
recentEventCount: recentEvents.length,
|
|
219
222
|
},
|
|
223
|
+
tunnel: activeTunnelURL ? {
|
|
224
|
+
url: activeTunnelURL,
|
|
225
|
+
uiURL: `${activeTunnelURL}/ui/`,
|
|
226
|
+
} : null,
|
|
220
227
|
});
|
|
221
228
|
return;
|
|
222
229
|
}
|
|
@@ -670,6 +677,7 @@ ${BOLD}How it works:${RESET}
|
|
|
670
677
|
|
|
671
678
|
if (result) {
|
|
672
679
|
tunnelURL = result.url;
|
|
680
|
+
activeTunnelURL = tunnelURL; // Expose via /navigator/status
|
|
673
681
|
tunnelProcess = result.process;
|
|
674
682
|
gatewayURL = tunnelURL; // Use tunnel URL as the gateway URL
|
|
675
683
|
|
|
@@ -771,49 +779,57 @@ ${BOLD}How it works:${RESET}
|
|
|
771
779
|
|
|
772
780
|
ok(`MCP server started (PID ${mcpProcess.pid}) — bridge: http://localhost:${port}`);
|
|
773
781
|
|
|
774
|
-
// ── Auto-register MCP server with mcporter
|
|
782
|
+
// ── Auto-register MCP server with mcporter ────────────────────────
|
|
775
783
|
// Writes directly to ~/.mcporter/mcporter.json for reliability.
|
|
776
784
|
//
|
|
777
|
-
//
|
|
778
|
-
//
|
|
779
|
-
//
|
|
780
|
-
// the
|
|
785
|
+
// npx runs from a temp cache dir (.npm/_npx/<hash>/) that disappears.
|
|
786
|
+
// To get stable paths we:
|
|
787
|
+
// 1. Install openclaw-navigator globally (npm i -g)
|
|
788
|
+
// 2. Use the global install path for mcporter config
|
|
789
|
+
// 3. If global install fails, find node's real path via `which node`
|
|
781
790
|
try {
|
|
782
|
-
const { readFileSync, writeFileSync, mkdirSync
|
|
791
|
+
const { readFileSync, writeFileSync, mkdirSync } = await import("node:fs");
|
|
792
|
+
const { execSync } = await import("node:child_process");
|
|
783
793
|
const { homedir } = await import("node:os");
|
|
784
794
|
|
|
785
|
-
//
|
|
795
|
+
// Step 1: Install globally to get a stable path
|
|
786
796
|
let stableNode;
|
|
787
|
-
try {
|
|
788
|
-
stableNode = realpathSync(process.execPath);
|
|
789
|
-
} catch {
|
|
790
|
-
stableNode = process.execPath;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
// Resolve stable mcp.mjs path
|
|
794
797
|
let stableMcpPath;
|
|
798
|
+
|
|
795
799
|
try {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
+
info(" Installing openclaw-navigator globally for stable paths...");
|
|
801
|
+
execSync("npm install -g openclaw-navigator@latest", {
|
|
802
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
803
|
+
timeout: 30000,
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// Find the global node and mcp.mjs
|
|
807
|
+
const globalPrefix = execSync("npm prefix -g", { encoding: "utf8" }).trim();
|
|
808
|
+
stableMcpPath = join(globalPrefix, "lib/node_modules/openclaw-navigator/mcp.mjs");
|
|
809
|
+
// Find node via `which` — always gives the real PATH-resolved binary
|
|
810
|
+
stableNode = execSync("which node", { encoding: "utf8" }).trim();
|
|
811
|
+
|
|
812
|
+
if (!existsSync(stableMcpPath)) {
|
|
813
|
+
throw new Error(`Global mcp.mjs not found at ${stableMcpPath}`);
|
|
814
|
+
}
|
|
800
815
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
//
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
816
|
+
ok(`Global install: ${stableMcpPath}`);
|
|
817
|
+
} catch (installErr) {
|
|
818
|
+
// Global install failed (permissions?) — fallback to which node + current mcpPath
|
|
819
|
+
warn(`Global install failed: ${installErr.message}`);
|
|
820
|
+
try {
|
|
821
|
+
stableNode = execSync("which node", { encoding: "utf8" }).trim();
|
|
822
|
+
} catch {
|
|
823
|
+
stableNode = process.execPath;
|
|
808
824
|
}
|
|
809
|
-
|
|
810
|
-
|
|
825
|
+
stableMcpPath = mcpPath;
|
|
826
|
+
info(` Falling back to: ${stableNode} ${stableMcpPath}`);
|
|
811
827
|
}
|
|
812
828
|
|
|
813
829
|
info(` mcporter: node=${stableNode}`);
|
|
814
830
|
info(` mcporter: mcp=${stableMcpPath}`);
|
|
815
831
|
|
|
816
|
-
// Write directly to ~/.mcporter/mcporter.json
|
|
832
|
+
// Step 2: Write directly to ~/.mcporter/mcporter.json
|
|
817
833
|
const mcporterDir = join(homedir(), ".mcporter");
|
|
818
834
|
const mcporterConfigPath = join(mcporterDir, "mcporter.json");
|
|
819
835
|
|
|
@@ -848,18 +864,24 @@ ${BOLD}How it works:${RESET}
|
|
|
848
864
|
// so the bridge + MCP server starts automatically on login.
|
|
849
865
|
if (process.platform === "darwin") {
|
|
850
866
|
try {
|
|
851
|
-
const {
|
|
867
|
+
const {
|
|
868
|
+
writeFileSync: writePlist,
|
|
869
|
+
existsSync: plistExists,
|
|
870
|
+
mkdirSync: mkPlist,
|
|
871
|
+
} = await import("node:fs");
|
|
852
872
|
const { homedir: homeDir } = await import("node:os");
|
|
853
873
|
const home = homeDir();
|
|
854
874
|
const launchAgentsDir = join(home, "Library/LaunchAgents");
|
|
855
875
|
const plistPath = join(launchAgentsDir, "com.openclaw.navigator-bridge.plist");
|
|
856
876
|
|
|
857
|
-
//
|
|
858
|
-
const {
|
|
877
|
+
// Find stable node/npx paths via `which` (not temp npx cache)
|
|
878
|
+
const { execSync: whichExec } = await import("node:child_process");
|
|
859
879
|
let nodeForPlist;
|
|
860
|
-
try {
|
|
861
|
-
|
|
862
|
-
|
|
880
|
+
try {
|
|
881
|
+
nodeForPlist = whichExec("which node", { encoding: "utf8" }).trim();
|
|
882
|
+
} catch {
|
|
883
|
+
nodeForPlist = process.execPath;
|
|
884
|
+
}
|
|
863
885
|
const npxForPlist = join(dirname(nodeForPlist), "npx");
|
|
864
886
|
|
|
865
887
|
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -901,9 +923,15 @@ ${BOLD}How it works:${RESET}
|
|
|
901
923
|
// Load the service (unload first if already loaded)
|
|
902
924
|
try {
|
|
903
925
|
const { execSync: ex } = await import("node:child_process");
|
|
904
|
-
try {
|
|
926
|
+
try {
|
|
927
|
+
ex(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
|
|
928
|
+
} catch {
|
|
929
|
+
/* not loaded */
|
|
930
|
+
}
|
|
905
931
|
// Don't load now — we're already running. It'll start on next login.
|
|
906
|
-
} catch {
|
|
932
|
+
} catch {
|
|
933
|
+
/* launchctl not available */
|
|
934
|
+
}
|
|
907
935
|
|
|
908
936
|
ok("Auto-start installed — bridge will start on login");
|
|
909
937
|
info(` Plist: ${plistPath}`);
|
package/mcp.mjs
CHANGED
|
@@ -156,7 +156,13 @@ const TOOLS = [
|
|
|
156
156
|
{
|
|
157
157
|
name: "navigator_status",
|
|
158
158
|
description:
|
|
159
|
-
"Get the Navigator bridge status — connection state, active tabs, current URL, uptime, pending commands.",
|
|
159
|
+
"Get the Navigator bridge status — connection state, active tabs, current URL, uptime, pending commands. Also returns the tunnel URL and OC Web UI URL.",
|
|
160
|
+
inputSchema: { type: "object", properties: {} },
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "navigator_get_ui_url",
|
|
164
|
+
description:
|
|
165
|
+
"Get the OC Web UI URL. Returns the Cloudflare tunnel URL with /ui/ path that serves the OpenClaw dashboard. Use this to find the URL before navigating to the UI.",
|
|
160
166
|
inputSchema: { type: "object", properties: {} },
|
|
161
167
|
},
|
|
162
168
|
|
|
@@ -356,6 +362,24 @@ async function handleTool(name, args) {
|
|
|
356
362
|
case "navigator_status":
|
|
357
363
|
return jsonResult(await bridgeGet("/navigator/status"));
|
|
358
364
|
|
|
365
|
+
case "navigator_get_ui_url": {
|
|
366
|
+
const status = await bridgeGet("/navigator/status");
|
|
367
|
+
if (status.tunnel?.uiURL) {
|
|
368
|
+
return jsonResult({
|
|
369
|
+
ok: true,
|
|
370
|
+
uiURL: status.tunnel.uiURL,
|
|
371
|
+
tunnelURL: status.tunnel.url,
|
|
372
|
+
hint: "Navigate to uiURL to access the OC Web UI dashboard",
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
return jsonResult({
|
|
376
|
+
ok: true,
|
|
377
|
+
uiURL: `${BRIDGE_URL}/ui/`,
|
|
378
|
+
tunnelURL: null,
|
|
379
|
+
hint: "No tunnel active — UI accessible locally only. Start with --mcp to enable tunnel.",
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
359
383
|
// ── Fire-and-forget ──
|
|
360
384
|
case "navigator_navigate":
|
|
361
385
|
return jsonResult(await sendCommand("navigate", { url: args.url }));
|