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.
Files changed (3) hide show
  1. package/cli.mjs +64 -36
  2. package/mcp.mjs +25 -1
  3. 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 (if available) ─────────
782
+ // ── Auto-register MCP server with mcporter ────────────────────────
775
783
  // Writes directly to ~/.mcporter/mcporter.json for reliability.
776
784
  //
777
- // KEY FIX: We resolve stable paths instead of using process.execPath
778
- // which may point to a temp npx cache (/Users/x/.npm/_npx/<hash>/node)
779
- // that disappears after npx exits. We find the REAL node binary and
780
- // the globally-installed mcp.mjs (or realpath of the current one).
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, realpathSync } = await import("node:fs");
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
- // Resolve stable node binary (follow symlinks out of npx temp dir)
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
- stableMcpPath = realpathSync(mcpPath);
797
- } catch {
798
- stableMcpPath = mcpPath;
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
- // If paths are still in npx temp cache, try to find the real install
802
- if (stableMcpPath.includes("_npx") || stableMcpPath.includes(".npm/_npx")) {
803
- // Check if openclaw-navigator is installed globally
804
- const nodeDir = dirname(stableNode);
805
- const globalMcp = join(dirname(nodeDir), "lib/node_modules/openclaw-navigator/mcp.mjs");
806
- if (existsSync(globalMcp)) {
807
- stableMcpPath = globalMcp;
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
- // If still temp, that's OK — mcporter will use the path we give it
810
- // and the bridge running alongside keeps the MCP server alive anyway
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 { writeFileSync: writePlist, existsSync: plistExists, mkdirSync: mkPlist } = await import("node:fs");
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
- // Resolve the stable node path (same one we found above for mcporter)
858
- const { realpathSync: rp } = await import("node:fs");
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 { nodeForPlist = rp(process.execPath); } catch { nodeForPlist = process.execPath; }
861
-
862
- // Find npx next to the stable node
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 { ex(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" }); } catch { /* not loaded */ }
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 { /* launchctl not available */ }
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 }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-navigator",
3
- "version": "4.3.6",
3
+ "version": "4.3.8",
4
4
  "description": "One-command bridge + tunnel for the Navigator browser — works on any machine, any OS",
5
5
  "keywords": [
6
6
  "browser",