c7dbaf2d9f
Two leftovers from the rebrand, done together because they touch the same docs and manifests. **Data directory.** `agent-dashboard` could not simply be renamed in the source: it names a real directory holding the live SQLite database. Startup now moves it once, via `migrateLegacyDataDir()` in `server/lib/claude-home.js` — a same-filesystem rename, so it is instant even at multiple gigabytes and cannot leave two divergent copies. `getDataDir()` stays PURE and resolves to the legacy path until the move happens; the first version of this put the rename inside the getter, which meant `npm run test:server` relocated the developer's real database as a side effect. The call sits behind `require.main === module` and ahead of the router requires, because those pull in db.js, which opens SQLite at module load. A failed rename is silent and the old directory keeps being used: an unfashionable directory name beats a blank database beside an unreachable one. Same treatment for the port-discovery file (`.agent-dashboard.json` → `.agenthub.json`, old one dropped on write). Not renamed on purpose: the Sagi localStorage keys (renaming silently resets everyone's prefs) and the PWA manifest `id` (changing it orphans an installed app). **MCP server.** 4 tool calls in 46 days across 577,132 events, against 4,556 lines of TypeScript, a 2 MB committed build artifact, and a pre-commit guard that had already blocked commits twice. Deleted: `mcp/`, the maintainer skills and `mcp-reviewer` agent, the build check, `plugins/agenthub/.mcp.json`, `docs/MCP.md`, 12 npm scripts, 5 Makefile targets, and every doc section describing it. Kept, because they are unrelated things sharing the name: the Config Explorer's MCP tab and its API, per-lane Playwright MCP, the agenthub-config plugin's MCP audit, and the historical `mcp__plugin_agenthub_*` events that analytics must keep attributing. Verified: server 1432/1432, client 459/459, tsc clean, prettier clean. Both directions of the migration proven — a sandbox-HOME test run leaves the legacy directory untouched, and a real boot moves it with its contents intact.
103 lines
4.2 KiB
JavaScript
103 lines
4.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* @file plugin-open.js
|
|
* @description Builds the dashboard UI bundle for a plugin install and prints
|
|
* the dashboard URL. `scripts/plugin-bootstrap.js` calls `buildClient` eagerly
|
|
* on session start, so client-only routes (e.g. `/run`) work the moment
|
|
* `claude` is started, not just the API. `/agenthub-open` re-runs
|
|
* this standalone — a no-op when the bundle already matches — as a fallback
|
|
* for `--force` rebuilds or a first build that failed during bootstrap, and it
|
|
* re-runs bootstrap first when the live server belongs to a retired plugin root
|
|
* (a mid-session `/plugin` update) so the new bundle is not left calling routes
|
|
* the running server never had.
|
|
*
|
|
* The client source is copied out of the (replaced-on-update) plugin cache into
|
|
* the runtime dir before building, so neither the cache nor the checkout is
|
|
* written to, and the output lands where the server already serves from
|
|
* (`DASHBOARD_CLIENT_DIST`).
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const { spawnSync } = require("child_process");
|
|
|
|
const boot = require("./plugin-bootstrap");
|
|
const { resolveDashboardPort } = require("../server/lib/server-info");
|
|
|
|
const PLUGIN_ROOT = path.resolve(__dirname, "..");
|
|
const NPM = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
|
|
function run(cmd, args, cwd, stdio) {
|
|
const res = spawnSync(cmd, args, { cwd, stdio, shell: process.platform === "win32" });
|
|
return res.status === 0;
|
|
}
|
|
|
|
/**
|
|
* Build the bundle into `<runtime>/client-dist`. Idempotent: an existing bundle
|
|
* is left alone unless `force` is set.
|
|
*
|
|
* `logPath`, when given, redirects npm's output there instead of inheriting the
|
|
* caller's stdio — used when this runs inside the detached bootstrap worker,
|
|
* whose own stdout is discarded, so a build failure is still debuggable.
|
|
*
|
|
* @returns {"ready"|"built"|"failed"}
|
|
*/
|
|
function buildClient({ rt = boot.runtimeDir(), root = PLUGIN_ROOT, force = false, logPath } = {}) {
|
|
const dist = path.join(rt, "client-dist");
|
|
if (!force && fs.existsSync(path.join(dist, "index.html"))) return "ready";
|
|
|
|
const logFd = logPath ? fs.openSync(logPath, "a") : null;
|
|
const stdio = logFd === null ? "inherit" : ["ignore", logFd, logFd];
|
|
try {
|
|
const src = path.join(rt, "client-src");
|
|
fs.rmSync(src, { recursive: true, force: true });
|
|
fs.cpSync(path.join(root, "client"), src, {
|
|
recursive: true,
|
|
filter: (p) => !/[\\/](node_modules|dist)$/.test(p),
|
|
});
|
|
|
|
if (!run(NPM, ["install", "--no-audit", "--no-fund"], src, stdio)) return "failed";
|
|
if (!run(NPM, ["run", "build"], src, stdio)) return "failed";
|
|
|
|
fs.rmSync(dist, { recursive: true, force: true });
|
|
fs.cpSync(path.join(src, "dist"), dist, { recursive: true });
|
|
return "built";
|
|
} finally {
|
|
if (logFd !== null) fs.closeSync(logFd);
|
|
}
|
|
}
|
|
|
|
function dashboardUrl() {
|
|
try {
|
|
return `http://localhost:${resolveDashboardPort()}`;
|
|
} catch {
|
|
return "http://localhost:4820";
|
|
}
|
|
}
|
|
|
|
// Exported before the entry block below, not after it: `bootstrap()` requires
|
|
// this module back for `buildClient`, and when plugin-open is itself the entry
|
|
// that lazy require resolves to whatever is on module.exports at that moment.
|
|
module.exports = { buildClient, dashboardUrl };
|
|
|
|
if (require.main === module) {
|
|
// A plugin update applied mid-session (/plugin + /reload-plugins) fires no
|
|
// SessionStart, so the server keeps executing a cache dir Claude Code has
|
|
// already retired: a bundle rebuilt from the new root then calls routes that
|
|
// old server never had. Hand the version handover back to bootstrap, which
|
|
// owns stop/start and the dependency install the new root may need.
|
|
const stale = (boot.readState() || {}).pluginRoot !== PLUGIN_ROOT || !boot.serverIsLive();
|
|
if (stale) boot.bootstrap();
|
|
|
|
const result = buildClient({ force: process.argv.includes("--force") });
|
|
if (result === "failed") {
|
|
console.error("Client build failed — the API still works.");
|
|
process.exitCode = 1;
|
|
} else {
|
|
if (result === "built") console.log("Dashboard UI built.");
|
|
if (stale) console.log("Server restarted on the current plugin version.");
|
|
console.log(dashboardUrl());
|
|
}
|
|
}
|