Files
AgentHub/server/lib/dashboard-url.js
T
nntrivi2001 4a665da79e fix: a token-gated or LAN-bound dashboard works end to end (WSL mirrored)
- /ws-pty carries the dashboard token like /ws (the terminal never attached
  with DASHBOARD_TOKEN set), and so does everything that cannot send the
  header: images, downloads, exports, drops (withToken/tokenHeader). A
  refused upgrade answers 401/403 and is logged and counted by reason.
- The browser asks for a missing or wrong token (401 EUNAUTHORIZED) and takes
  ?token= from the link agenthub start prints, dropping it from the URL.
- The CLI sends the token (DASHBOARD_TOKEN, else the one the running server
  publishes 0600 in <data dir>/runtime/dashboard-token); MCP and statusline
  read the same file.
- DASHBOARD_HOST takes a list, and 127.0.0.1 is always listened on too, so a
  LAN-only bind keeps hooks and the CLI working.
- agenthub start --host/--allowed-hosts/--token are saved in
  <data dir>/network.json for every later start; a wide bind gets a token;
  every reachable URL is printed.
- tmux < 3.7 is an amber, once-per-version notice that says only the pane
  scrollbar is missing, with a ~/.local build instead of sudo make install.
- INSTALL: PATH belongs in ~/.profile for sudo -iu/ssh; WSL mirrored notes.
2026-11-06 14:59:48 +07:00

51 lines
2.0 KiB
JavaScript

/**
* @file Where the running dashboard answers, for a process that is NOT the
* dashboard: the `agenthub` CLI, the MCP server (`mcp/server.js`) and the
* statusline (`statusline/statusline.js`). An explicit CLAUDE_DASHBOARD_PORT /
* DASHBOARD_PORT wins (the hook handler's contract), then the live-server
* discovery file `~/.claude/.agenthub.json` (`server-info.js`, PID-checked),
* then the default port. `dashboardToken()` is the token such a process sends
* when the dashboard requires one: `DASHBOARD_TOKEN`, else the one the running
* server published (`<data dir>/runtime/dashboard-token`, 0600 — see
* `security.publishDashboardToken`), so a shell that never exported it still
* reaches a token-gated dashboard. Never throws.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const path = require("node:path");
/** Base URL of the live dashboard, e.g. `http://127.0.0.1:4820`. */
function dashboardUrl() {
const envPort = process.env.CLAUDE_DASHBOARD_PORT || process.env.DASHBOARD_PORT;
if (envPort) return `http://127.0.0.1:${envPort}`;
try {
const { resolveDashboardPort } = require(path.join(__dirname, "server-info.js"));
const port = resolveDashboardPort();
if (port) return `http://127.0.0.1:${port}`;
} catch {
/* discovery unavailable — fall through to the default */
}
return "http://127.0.0.1:4820";
}
/** The dashboard token to send, or null when none is configured or readable. */
function dashboardToken() {
const env = process.env.DASHBOARD_TOKEN;
if (typeof env === "string" && env) return env;
try {
const { tokenFilePath } = require(path.join(__dirname, "security.js"));
const t = require("node:fs").readFileSync(tokenFilePath(), "utf8").trim();
return t || null;
} catch {
return null;
}
}
/** The headers carrying `dashboardToken()` (both spellings the server takes); {} with none. */
function tokenHeaders() {
const t = dashboardToken();
return t ? { "x-dashboard-token": t } : {};
}
module.exports = { dashboardUrl, dashboardToken, tokenHeaders };