8a82895c65
Adds a root `ccam` plugin (`.claude-plugin/plugin.json`, `"source": "./"`) so
`/plugin marketplace add` + `/plugin install ccam@...` is enough on a machine
with nothing but Claude Code: no clone, no npm run setup, no manual npm start.
- scripts/plugin-bootstrap.js: SessionStart hook. Fast-path exit, Node >=22.5
gate (node:sqlite), mkdir lock with stale reclaim, deps installed into
~/.claude/agent-dashboard/runtime/ (never the plugin cache), legacy
checkout-hook cleanup (backed up), ~/.local/bin/ccam launcher, eager UI
build so client routes like /run work immediately, detached server spawn.
- scripts/plugin-open.js, scripts/plugin-doctor.js: /ccam-open, /ccam-doctor.
- server/index.js: DASHBOARD_CLIENT_DIST override (plugin cache is read-only).
- mcp/build/ is committed (plugin MCP servers start before any bootstrap could
build them) and kept honest by scripts/check-mcp-build.js (content hash,
not mtime), enforced by pre-commit when mcp/src changes.
- plugins/ccam-dashboard/.mcp.json moved under plugins/ccam/ with a working
${CLAUDE_PLUGIN_ROOT} path (the old relative path never resolved from a
marketplace-cached subdir).
- Docs: README, INSTALL, SETUP, ARCHITECTURE, CLAUDE.md, docs/PLUGINS.md,
docs/MCP.md, docs/CLI.md, docs/HOOKS.md.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
128 lines
7.6 KiB
JavaScript
128 lines
7.6 KiB
JavaScript
/**
|
|
* @file banner.ts
|
|
* @description Console startup UI for the MCP server's non-stdio transports (HTTP and REPL):
|
|
* the ASCII-art wordmark, a boxed server-info panel (version, transport, dashboard URL, port,
|
|
* tool count, mutation/destructive policy state), a "ready" line, and a shutdown message. The
|
|
* stdio transport never calls any of these, since stdout there is the MCP JSON-RPC channel.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
/* =============================================================================
|
|
* MODULE_GUIDE — extended in-file reference (comments only; safe to read, never executed)
|
|
* =============================================================================
|
|
* **Purpose:** Dashboard module consumed by the React client, MCP tools, or desktop shell depending on deployment mode.
|
|
*
|
|
* ## Design constraints
|
|
* - Local-first: no telemetry leaves the machine unless the user configures webhooks.
|
|
* - Fail-safe hooks path on the server must never block Claude Code; UI mirrors that
|
|
* philosophy by degrading gracefully (empty states, stale badges, reconnect loops).
|
|
* - Destructive flows stay behind explicit confirmation modals and server-side gates.
|
|
* - Internationalization: user-visible strings belong in i18n JSON, not literals here.
|
|
*
|
|
* ## Remote data & SSH
|
|
* Remote Data Sources let operators aggregate multiple machines. SSH entries describe
|
|
* how to reach a peer dashboard; the global data scope (`dataScope.ts`) narrows every
|
|
* scoped GET via `?sources=`. Health checks and import history surface in Settings.
|
|
*
|
|
* ## Observability
|
|
* Prometheus scrapes `GET /api/metrics` (see `monitoring/`). Grafana ships four
|
|
* provisioned boards (overview, sessions, tools, alerts). Native npm scripts and
|
|
* Docker Compose profiles are documented in `monitoring/README.md`.
|
|
*
|
|
* ## Public surface
|
|
* - `printBanner` — exported API; see TSDoc on the symbol for behavior.
|
|
* - `printServerInfo` — exported API; see TSDoc on the symbol for behavior.
|
|
* - `printReady` — exported API; see TSDoc on the symbol for behavior.
|
|
* - `printShutdown` — exported API; see TSDoc on the symbol for behavior.
|
|
*
|
|
* ## Testing pointers
|
|
* - Prefer colocated `__tests__` with Vitest + Testing Library for UI.
|
|
* - Server contract changes require `npm run test:server` and OpenAPI sync.
|
|
* - MCP edits: `npm run mcp:typecheck` and `npm run mcp:build`.
|
|
*
|
|
* ## Related docs
|
|
* - `ARCHITECTURE.md` — hooks → API → SQLite → WebSocket → UI pipeline.
|
|
* - `docs/API.md` — REST reference.
|
|
* - `.claude/skills/file-headers/` — mandatory `@author` header policy.
|
|
* ============================================================================= */
|
|
/* -----------------------------------------------------------------------------
|
|
* EXPORT CATALOG — quick index of symbols defined below (documentation only).
|
|
* -----------------------------------------------------------------------------
|
|
* **printBanner**
|
|
* Part of this module's public contract. Downstream imports should treat
|
|
* the signature and return type as stable unless release notes say otherwise.
|
|
* When behavior changes, update the `@file` overview and relevant tests.
|
|
*
|
|
* **printServerInfo**
|
|
* Part of this module's public contract. Downstream imports should treat
|
|
* the signature and return type as stable unless release notes say otherwise.
|
|
* When behavior changes, update the `@file` overview and relevant tests.
|
|
*
|
|
* **printReady**
|
|
* Part of this module's public contract. Downstream imports should treat
|
|
* the signature and return type as stable unless release notes say otherwise.
|
|
* When behavior changes, update the `@file` overview and relevant tests.
|
|
*
|
|
* **printShutdown**
|
|
* Part of this module's public contract. Downstream imports should treat
|
|
* the signature and return type as stable unless release notes say otherwise.
|
|
* When behavior changes, update the `@file` overview and relevant tests.
|
|
*
|
|
* ----------------------------------------------------------------------------- */
|
|
import * as c from "./colors.js";
|
|
/** ASCII-art wordmark rendered by {@link printBanner} with a color gradient. */
|
|
const BANNER = `
|
|
$$\\ $$\\ $$$$$$\\ $$$$$$$\\ $$$$$$$$\\ $$\\
|
|
$$$\\ $$$ |$$ __$$\\ $$ __$$\\ \\__$$ __| $$ |
|
|
$$$$\\ $$$$ |$$ / \\__|$$ | $$ | $$ | $$$$$$\\ $$$$$$\\ $$ | $$$$$$$\\
|
|
$$\\$$\\$$ $$ |$$ | $$$$$$$ | $$ |$$ __$$\\ $$ __$$\\ $$ |$$ _____|
|
|
$$ \\$$$ $$ |$$ | $$ ____/ $$ |$$ / $$ |$$ / $$ |$$ |\\$$$$$$\\
|
|
$$ |\\$ /$$ |$$ | $$\\ $$ | $$ |$$ | $$ |$$ | $$ |$$ | \\____$$\\
|
|
$$ | \\_/ $$ |\\$$$$$$ |$$ | $$ |\\$$$$$$ |\\$$$$$$ |$$ |$$$$$$$ |
|
|
\\__| \\__| \\______/ \\__| \\__| \\______/ \\______/ \\__|\\_______/ `;
|
|
/** Prints {@link BANNER} one line per gradient color (cyan to magenta).
|
|
* Called at HTTP/REPL startup only. */
|
|
export function printBanner() {
|
|
const gradient = [c.brightCyan, c.cyan, c.brightBlue, c.blue, c.brightMagenta, c.magenta];
|
|
const lines = BANNER.split("\n").filter((l) => l.length > 0);
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const colorFn = gradient[Math.min(i, gradient.length - 1)];
|
|
process.stdout.write(colorFn(lines[i]) + "\n");
|
|
}
|
|
process.stdout.write("\n");
|
|
}
|
|
/** Prints a boxed config summary beneath the banner, shared by HTTP (`port`
|
|
* set) and REPL (`port` omitted). Mutations/Destructive rows mirror the
|
|
* `policy/tool-guards.ts` flags, warning-colored when enabled. Ends with a
|
|
* reminder that the dashboard must already be running at the printed URL. */
|
|
export function printServerInfo(info) {
|
|
const divider = c.dim(c.cyan("─".repeat(62)));
|
|
const line = (label, value) => ` ${c.dim(c.cyan("│"))} ${c.label(label.padEnd(18))} ${value}`;
|
|
process.stdout.write(divider + "\n");
|
|
process.stdout.write(` ${c.dim(c.cyan("│"))} ${c.bold(c.brightWhite("Agent Dashboard MCP Server"))}\n`);
|
|
process.stdout.write(divider + "\n");
|
|
process.stdout.write(line("Version", c.brightCyan(info.version)) + "\n");
|
|
process.stdout.write(line("Transport", c.accent(info.transport.toUpperCase())) + "\n");
|
|
process.stdout.write(line("Dashboard API", c.green(info.dashboard)) + "\n");
|
|
if (info.port !== undefined) {
|
|
process.stdout.write(line("HTTP Port", c.brightYellow(String(info.port))) + "\n");
|
|
}
|
|
process.stdout.write(line("Tools Registered", c.brightWhite(String(info.tools))) + "\n");
|
|
process.stdout.write(line("Mutations", info.mutations ? c.warn("ENABLED") : c.success("disabled")) + "\n");
|
|
process.stdout.write(line("Destructive", info.destructive ? c.error("ENABLED") : c.success("disabled")) + "\n");
|
|
process.stdout.write(divider + "\n");
|
|
process.stdout.write(` ${c.dim(c.cyan("│"))} ${c.warn("⚠")} ${c.dim("Dashboard must be running at the URL above.")}\n`);
|
|
process.stdout.write(` ${c.dim(c.cyan("│"))} ${c.dim(" Start it first:")} ${c.brightWhite("npm run dev")} ${c.dim("or")} ${c.brightWhite("npm start")}\n`);
|
|
process.stdout.write(divider + "\n\n");
|
|
}
|
|
/** Prints "Server ready" once the HTTP server has bound to its port; not
|
|
* used by the REPL transport. */
|
|
export function printReady(transport) {
|
|
const icon = "✔";
|
|
process.stdout.write(` ${c.success(icon)} ${c.bold(c.brightWhite("Server ready"))} ${c.muted(`(${transport})`)}\n\n`);
|
|
}
|
|
/** Prints "Shutting down...". Called from HTTP/REPL shutdown paths and
|
|
* `index.ts`'s SIGINT/SIGTERM handler; never from stdio. */
|
|
export function printShutdown() {
|
|
process.stdout.write(`\n ${c.warn("⏻")} ${c.bold(c.brightWhite("Shutting down..."))}\n`);
|
|
}
|