@singhak/nodeui-cli 0.0.0-stage → 0.5.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/attach.ts","../src/dashboard.ts","../src/targets.ts","../src/mcp.ts","../package.json","../src/digest.ts"],"sourcesContent":["export { attach, attachEnv, preloadPath, type AttachOptions } from './attach';\r\nexport { startDashboard, type Dashboard, type DashboardOptions } from './dashboard';\r\nexport { fetchPanel, parseTarget, PanelError, type ServiceTarget } from './targets';\r\nexport { serveMcp, type McpOptions } from './mcp';\r\nexport { buildDigest, type DigestInput } from './digest';\r\n","import { spawn } from 'node:child_process';\r\nimport { join } from 'node:path';\r\n\r\nexport interface AttachOptions {\r\n path?: string;\r\n token?: string;\r\n persist?: string;\r\n otlp?: string;\r\n bodies?: boolean;\r\n /** Force-enable even when NODE_ENV=production. */\r\n force?: boolean;\r\n}\r\n\r\n/** `node --require` accepts quoted paths; forward slashes avoid backslash escapes on Windows. */\r\nexport function preloadPath(): string {\r\n return join(__dirname, 'preload.cjs').replace(/\\\\/g, '/');\r\n}\r\n\r\n/** Environment for the child process: the preload plus the NODEUI_* settings. */\r\nexport function attachEnv(\r\n base: NodeJS.ProcessEnv,\r\n options: AttachOptions,\r\n preload = preloadPath(),\r\n): NodeJS.ProcessEnv {\r\n const env: NodeJS.ProcessEnv = { ...base };\r\n env.NODE_OPTIONS = [base.NODE_OPTIONS, `--require \"${preload}\"`].filter(Boolean).join(' ');\r\n if (options.path) env.NODEUI_PATH = options.path;\r\n if (options.token) env.NODEUI_TOKEN = options.token;\r\n if (options.persist) env.NODEUI_PERSIST_FILE = options.persist;\r\n if (options.otlp) env.NODEUI_OTLP_ENDPOINT = options.otlp;\r\n if (options.bodies) env.NODEUI_CAPTURE_BODIES = 'true';\r\n if (options.force) env.NODEUI_ENABLED = 'true';\r\n return env;\r\n}\r\n\r\n/** Runs `command` with NodeUI preloaded; resolves with the child's exit code. */\r\nexport function attach(command: string[], options: AttachOptions): Promise<number> {\r\n const [cmd, ...args] = command;\r\n if (!cmd) return Promise.reject(new Error('nothing to run; usage: nodeui attach -- node app.js'));\r\n return new Promise((resolve, reject) => {\r\n const child = spawn(cmd, args, {\r\n stdio: 'inherit',\r\n env: attachEnv(process.env, options),\r\n shell: process.platform === 'win32',\r\n });\r\n const onInt = (): void => {\r\n child.kill('SIGINT');\r\n };\r\n const onTerm = (): void => {\r\n child.kill('SIGTERM');\r\n };\r\n const cleanup = (): void => {\r\n process.off('SIGINT', onInt);\r\n process.off('SIGTERM', onTerm);\r\n };\r\n process.on('SIGINT', onInt);\r\n process.on('SIGTERM', onTerm);\r\n child.on('error', (err) => {\r\n cleanup();\r\n reject(err);\r\n });\r\n child.on('exit', (code, signal) => {\r\n cleanup();\r\n resolve(code ?? (signal ? 1 : 0));\r\n });\r\n });\r\n}\r\n","import http, { type IncomingMessage, type Server, type ServerResponse } from 'node:http';\r\nimport https from 'node:https';\r\nimport type { AddressInfo } from 'node:net';\r\nimport { createGuard, resolveStaticAsset, uiAssetsDir } from '@singhak/nodeui-core';\r\nimport { fetchPanel, type ServiceTarget } from './targets';\r\n\r\nexport interface DashboardOptions {\r\n services: ServiceTarget[];\r\n port?: number;\r\n}\r\n\r\nexport interface Dashboard {\r\n server: Server;\r\n port: number;\r\n close(): Promise<void>;\r\n}\r\n\r\ninterface ServiceStatus {\r\n name: string;\r\n url: string;\r\n up: boolean;\r\n /** Why the service is unreachable or inactive. */\r\n reason?: string;\r\n health?: string;\r\n requests?: number;\r\n errorRate?: number;\r\n p95Ms?: number;\r\n errorGroups?: number;\r\n}\r\n\r\nconst HOP_BY_HOP = new Set([\r\n 'connection',\r\n 'keep-alive',\r\n 'proxy-authenticate',\r\n 'proxy-authorization',\r\n 'te',\r\n 'trailer',\r\n 'transfer-encoding',\r\n 'upgrade',\r\n]);\r\n\r\nconst PAGE = `<!doctype html>\r\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\r\n<title>NodeUI services</title>\r\n<style>\r\n:root{--bg:#0f1115;--fg:#e6e8ee;--card:#181b22;--bd:#2a2f3a;--mut:#8b93a5;--ok:#3ecf8e;--warn:#f5a524;--bad:#f0616d}\r\n@media (prefers-color-scheme:light){:root{--bg:#f6f7f9;--fg:#15171c;--card:#fff;--bd:#dfe3ea;--mut:#5d6578}}\r\nbody{margin:0;background:var(--bg);color:var(--fg);font:14px/1.5 system-ui,sans-serif}\r\nmain{max-width:960px;margin:0 auto;padding:24px 16px}\r\nh1{font-size:20px;margin:0 0 4px}p.sub{color:var(--mut);margin:0 0 20px}\r\n.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px}\r\na.card{display:block;background:var(--card);border:1px solid var(--bd);border-radius:10px;padding:14px;color:inherit;text-decoration:none}\r\na.card:hover{border-color:var(--mut)}\r\n.name{font-weight:600;font-size:16px;display:flex;align-items:center;gap:8px}\r\n.dot{width:9px;height:9px;border-radius:50%;background:var(--mut)}\r\n.ok{background:var(--ok)}.degraded{background:var(--warn)}.critical,.down{background:var(--bad)}\r\n.url{color:var(--mut);font-size:12px;word-break:break-all;margin:2px 0 10px}\r\ndl{display:grid;grid-template-columns:auto 1fr;gap:2px 12px;margin:0}dt{color:var(--mut)}dd{margin:0;text-align:right}\r\n.reason{color:var(--bad);font-size:13px}\r\n</style></head><body><main>\r\n<h1>NodeUI services</h1><p class=\"sub\">Local services, refreshed every 3 seconds. Select one to open its console.</p>\r\n<div class=\"grid\" id=\"grid\"></div></main>\r\n<script src=\"/_dashboard.js\"></script></body></html>`;\r\n\r\n// Rendered with textContent only: service names, URLs and reasons are never parsed as HTML.\r\nconst SCRIPT = `\r\nconst grid = document.getElementById('grid');\r\nfunction el(tag, cls, text) {\r\n const e = document.createElement(tag);\r\n if (cls) e.className = cls;\r\n if (text !== undefined) e.textContent = text;\r\n return e;\r\n}\r\nfunction row(dl, k, v) { dl.append(el('dt', '', k), el('dd', '', v)); }\r\nasync function refresh() {\r\n let list;\r\n try { list = (await (await fetch('api/services')).json()).data; } catch { return; }\r\n grid.replaceChildren(...list.map((s) => {\r\n const card = el('a', 'card');\r\n card.href = 's/' + encodeURIComponent(s.name) + '/';\r\n const title = el('div', 'name');\r\n title.append(el('span', 'dot ' + (s.up ? (s.health || 'ok') : 'down')), el('span', '', s.name));\r\n card.append(title, el('div', 'url', s.url));\r\n if (!s.up) { card.append(el('div', 'reason', s.reason || 'unreachable')); return card; }\r\n const dl = el('dl');\r\n row(dl, 'Health', s.health || 'unknown');\r\n row(dl, 'Requests', String(s.requests ?? 0));\r\n row(dl, 'Error rate', ((s.errorRate || 0) * 100).toFixed(1) + '%');\r\n row(dl, 'p95', (s.p95Ms || 0).toFixed(1) + ' ms');\r\n row(dl, 'Error groups', String(s.errorGroups ?? 0));\r\n card.append(dl);\r\n return card;\r\n }));\r\n}\r\nrefresh(); setInterval(refresh, 3000);\r\n`;\r\n\r\nasync function status(target: ServiceTarget): Promise<ServiceStatus> {\r\n const out: ServiceStatus = { name: target.name, url: target.base, up: false };\r\n try {\r\n const config = await fetchPanel<{ enabled: boolean; activationReason: string }>(\r\n target,\r\n 'config',\r\n );\r\n if (!config.enabled) {\r\n out.reason = config.activationReason;\r\n return out;\r\n }\r\n const [health, requests, errors] = await Promise.all([\r\n fetchPanel<{ status: string }>(target, 'health').catch(() => null),\r\n fetchPanel<{ total: number; summary: { errorRate: number; p95Ms: number } }>(\r\n target,\r\n 'requests',\r\n ).catch(() => null),\r\n fetchPanel<{ groups: unknown[] }>(target, 'errors').catch(() => null),\r\n ]);\r\n out.up = true;\r\n out.health = health?.status;\r\n out.requests = requests?.total;\r\n out.errorRate = requests?.summary.errorRate;\r\n out.p95Ms = requests?.summary.p95Ms;\r\n out.errorGroups = errors?.groups.length;\r\n } catch (err) {\r\n out.reason = err instanceof Error ? err.message : String(err);\r\n }\r\n return out;\r\n}\r\n\r\nfunction json(res: ServerResponse, code: number, body: unknown): void {\r\n res.writeHead(code, {\r\n 'content-type': 'application/json; charset=utf-8',\r\n 'cache-control': 'no-store',\r\n 'x-content-type-options': 'nosniff',\r\n });\r\n res.end(JSON.stringify(body));\r\n}\r\n\r\nfunction proxy(\r\n target: ServiceTarget,\r\n rest: string,\r\n search: string,\r\n req: IncomingMessage,\r\n res: ServerResponse,\r\n): void {\r\n const url = new URL(`${target.base}${rest}${search}`);\r\n const headers: Record<string, string | string[]> = {};\r\n for (const [k, v] of Object.entries(req.headers)) {\r\n if (v === undefined || HOP_BY_HOP.has(k)) continue;\r\n // Browser-identifying headers would trip the service's own cross-origin and cookie checks.\r\n if (k === 'host' || k === 'origin' || k === 'referer' || k === 'cookie') continue;\r\n headers[k] = v;\r\n }\r\n if (target.token) headers.authorization = `Bearer ${target.token}`;\r\n const client = url.protocol === 'https:' ? https : http;\r\n const upstream = client.request(url, { method: req.method, headers }, (up) => {\r\n const out: Record<string, string | string[]> = {};\r\n for (const [k, v] of Object.entries(up.headers)) {\r\n if (v === undefined || HOP_BY_HOP.has(k) || k === 'set-cookie') continue;\r\n out[k] = v;\r\n }\r\n res.writeHead(up.statusCode ?? 502, out);\r\n up.pipe(res);\r\n });\r\n upstream.on('error', (err) => {\r\n if (res.headersSent) {\r\n res.destroy();\r\n return;\r\n }\r\n json(res, 502, {\r\n ok: false,\r\n error: { code: 'bad-gateway', message: `cannot reach ${target.name}: ${err.message}` },\r\n });\r\n });\r\n res.on('close', () => upstream.destroy());\r\n req.pipe(upstream);\r\n}\r\n\r\n/**\r\n * Serves one landing page that lists several NodeUI consoles and proxies each\r\n * one's console and API under `/s/<name>/`. Loopback-only, like the consoles.\r\n */\r\nexport async function startDashboard(options: DashboardOptions): Promise<Dashboard> {\r\n const byName = new Map(options.services.map((s) => [s.name, s]));\r\n if (byName.size !== options.services.length) throw new Error('service names must be unique');\r\n const guard = createGuard({\r\n host: '127.0.0.1',\r\n allowedHosts: [],\r\n allowedOrigins: [],\r\n allowedRemoteAddresses: [],\r\n trustProxy: false,\r\n });\r\n const assets = uiAssetsDir();\r\n\r\n const server = http.createServer((req, res) => {\r\n const verdict = guard(req);\r\n if (!verdict.ok) {\r\n json(res, verdict.status, {\r\n ok: false,\r\n error: { code: verdict.code, message: verdict.message },\r\n });\r\n return;\r\n }\r\n const url = new URL(req.url ?? '/', 'http://localhost');\r\n const path = url.pathname;\r\n\r\n if (path === '/' && req.method === 'GET') {\r\n res.writeHead(200, {\r\n 'content-type': 'text/html; charset=utf-8',\r\n 'content-security-policy': \"default-src 'self'; style-src 'self' 'unsafe-inline'\",\r\n 'x-content-type-options': 'nosniff',\r\n });\r\n res.end(PAGE);\r\n return;\r\n }\r\n if (path === '/_dashboard.js') {\r\n res.writeHead(200, { 'content-type': 'text/javascript; charset=utf-8' });\r\n res.end(SCRIPT);\r\n return;\r\n }\r\n if (path === '/api/services') {\r\n void Promise.all(options.services.map(status)).then((data) =>\r\n json(res, 200, { ok: true, data }),\r\n );\r\n return;\r\n }\r\n\r\n const match = /^\\/s\\/([^/]+)(\\/.*)?$/.exec(path);\r\n let target: ServiceTarget | undefined;\r\n try {\r\n target = match ? byName.get(decodeURIComponent(match[1] ?? '')) : undefined;\r\n } catch {\r\n target = undefined;\r\n }\r\n if (!match || !target) {\r\n json(res, 404, { ok: false, error: { code: 'not-found', message: 'unknown service' } });\r\n return;\r\n }\r\n const rest = match[2];\r\n if (rest === undefined) {\r\n res.writeHead(308, { location: `${path}/` });\r\n res.end();\r\n return;\r\n }\r\n if (rest.startsWith('/api/')) {\r\n proxy(target, rest, url.search, req, res);\r\n return;\r\n }\r\n const asset = resolveStaticAsset(assets, rest === '/' ? '' : rest);\r\n if (!asset) {\r\n json(res, 404, { ok: false, error: { code: 'not-found', message: 'console not built' } });\r\n return;\r\n }\r\n res.writeHead(200, {\r\n 'content-type': asset.contentType,\r\n 'content-length': asset.length,\r\n 'x-content-type-options': 'nosniff',\r\n });\r\n asset.content.pipe(res);\r\n });\r\n\r\n await new Promise<void>((resolve, reject) => {\r\n server.once('error', reject);\r\n server.listen(options.port ?? 4000, '127.0.0.1', resolve);\r\n });\r\n return {\r\n server,\r\n port: (server.address() as AddressInfo).port,\r\n close: () =>\r\n new Promise<void>((resolve) => {\r\n server.closeAllConnections?.();\r\n server.close(() => resolve());\r\n }),\r\n };\r\n}\r\n","/** A running NodeUI console that the CLI talks to over its REST API. */\r\nexport interface ServiceTarget {\r\n name: string;\r\n /** Console base URL without a trailing slash, e.g. `http://127.0.0.1:3000/nodeui`. */\r\n base: string;\r\n token?: string;\r\n}\r\n\r\nconst NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,39}$/;\r\n\r\n/**\r\n * Parses `name=url` or a bare `url`. A URL without a path means the default\r\n * console path `/nodeui`; a `?token=` query becomes the access token.\r\n */\r\nexport function parseTarget(spec: string): ServiceTarget {\r\n const eq = spec.indexOf('=');\r\n const hasName = eq > 0 && !spec.slice(0, eq).includes('/') && !spec.slice(0, eq).includes(':');\r\n const rawUrl = hasName ? spec.slice(eq + 1) : spec;\r\n let url: URL;\r\n try {\r\n url = new URL(/^[a-z][a-z0-9+.-]*:\\/\\//i.test(rawUrl) ? rawUrl : `http://${rawUrl}`);\r\n } catch {\r\n throw new Error(`invalid service URL \"${rawUrl}\"`);\r\n }\r\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\r\n throw new Error(`service URL must be http(s), got \"${rawUrl}\"`);\r\n }\r\n const name = hasName ? spec.slice(0, eq) : `${url.hostname}-${url.port || '80'}`;\r\n if (!NAME_PATTERN.test(name)) {\r\n throw new Error(`invalid service name \"${name}\" (letters, digits, \".\", \"-\" and \"_\")`);\r\n }\r\n const token = url.searchParams.get('token') ?? undefined;\r\n const path = url.pathname.replace(/\\/+$/, '') || '/nodeui';\r\n return { name, base: `${url.origin}${path}`, token };\r\n}\r\n\r\nexport class PanelError extends Error {\r\n constructor(\r\n message: string,\r\n readonly code: string,\r\n ) {\r\n super(message);\r\n this.name = 'PanelError';\r\n }\r\n}\r\n\r\n/** Reads one panel (`/api/<panel>`) from a service and unwraps the `{ok,data}` envelope. */\r\nexport async function fetchPanel<T>(\r\n target: ServiceTarget,\r\n panel: string,\r\n timeoutMs = 3000,\r\n): Promise<T> {\r\n const headers: Record<string, string> = { accept: 'application/json' };\r\n if (target.token) headers.authorization = `Bearer ${target.token}`;\r\n let res: Response;\r\n try {\r\n res = await fetch(`${target.base}/api/${panel}`, {\r\n headers,\r\n signal: AbortSignal.timeout(timeoutMs),\r\n });\r\n } catch (err) {\r\n throw new PanelError(\r\n `cannot reach ${target.name} at ${target.base}: ${err instanceof Error ? err.message : String(err)}`,\r\n 'unreachable',\r\n );\r\n }\r\n let body: { ok?: boolean; data?: T; error?: { code?: string; message?: string } };\r\n try {\r\n body = (await res.json()) as typeof body;\r\n } catch {\r\n throw new PanelError(`${target.name}: invalid response (HTTP ${res.status})`, 'invalid');\r\n }\r\n if (!body.ok) {\r\n throw new PanelError(body.error?.message ?? `HTTP ${res.status}`, body.error?.code ?? 'error');\r\n }\r\n return body.data as T;\r\n}\r\n","import { createInterface } from 'node:readline';\r\nimport type { Readable, Writable } from 'node:stream';\r\nimport type {\r\n ErrorsData,\r\n HealthData,\r\n LogsData,\r\n OutgoingData,\r\n QueriesData,\r\n RequestsData,\r\n RoutesData,\r\n} from '@singhak/nodeui-core';\r\nimport pkg from '../package.json';\r\nimport { buildDigest } from './digest';\r\nimport { fetchPanel, PanelError, type ServiceTarget } from './targets';\r\n\r\nconst SERVER_INFO = { name: 'nodeui', version: pkg.version };\r\nconst DEFAULT_PROTOCOL = '2025-03-26';\r\nconst DEFAULT_LIMIT = 20;\r\nconst MAX_LIMIT = 200;\r\n\r\ninterface Tool {\r\n name: string;\r\n description: string;\r\n properties: Record<string, unknown>;\r\n run(args: Record<string, unknown>, target: ServiceTarget): Promise<string>;\r\n}\r\n\r\nconst num = (v: unknown, fallback: number): number =>\r\n typeof v === 'number' && Number.isFinite(v) ? v : fallback;\r\nconst limitOf = (v: unknown): number =>\r\n Math.min(Math.max(Math.trunc(num(v, DEFAULT_LIMIT)), 1), MAX_LIMIT);\r\nconst text = (v: unknown): string => JSON.stringify(v, null, 1);\r\nconst optional = <T>(p: Promise<T>): Promise<T | null> => p.catch(() => null);\r\n\r\nconst limitProp = {\r\n limit: { type: 'integer', description: `Max items, newest last (default ${DEFAULT_LIMIT}).` },\r\n};\r\n\r\nconst TOOLS: Tool[] = [\r\n {\r\n name: 'nodeui_overview',\r\n description:\r\n 'START HERE. Prioritised digest of the running app: health, traffic, grouped errors with stack frames, failing and slow requests, slow/N+1 queries, failing outgoing calls.',\r\n properties: {},\r\n async run(_args, t) {\r\n const [health, requests, errors, queries, outgoing] = await Promise.all([\r\n optional(fetchPanel<HealthData>(t, 'health')),\r\n optional(fetchPanel<RequestsData>(t, 'requests')),\r\n optional(fetchPanel<ErrorsData>(t, 'errors')),\r\n optional(fetchPanel<QueriesData>(t, 'queries')),\r\n optional(fetchPanel<OutgoingData>(t, 'outgoing')),\r\n ]);\r\n return buildDigest({ service: t.name, health, requests, errors, queries, outgoing });\r\n },\r\n },\r\n {\r\n name: 'nodeui_errors',\r\n description:\r\n 'Errors grouped by fingerprint with count, first/last seen, full stack and the last linked request id.',\r\n properties: limitProp,\r\n async run(args, t) {\r\n const data = await fetchPanel<ErrorsData>(t, 'errors');\r\n return text(data.groups.slice(0, limitOf(args.limit)));\r\n },\r\n },\r\n {\r\n name: 'nodeui_requests',\r\n description:\r\n 'Recent HTTP requests handled by the app (method, route pattern, status, duration, query/headers when captured). Filter by minimum status or minimum duration.',\r\n properties: {\r\n ...limitProp,\r\n minStatus: { type: 'integer', description: 'Only requests with status >= this (e.g. 500).' },\r\n minDurationMs: { type: 'number', description: 'Only requests at least this slow.' },\r\n },\r\n async run(args, t) {\r\n const data = await fetchPanel<RequestsData>(t, 'requests');\r\n const minStatus = num(args.minStatus, 0);\r\n const minMs = num(args.minDurationMs, 0);\r\n const rows = data.entries.filter((r) => r.status >= minStatus && r.durationMs >= minMs);\r\n return text({ summary: data.summary, entries: rows.slice(-limitOf(args.limit)) });\r\n },\r\n },\r\n {\r\n name: 'nodeui_request',\r\n description:\r\n 'One request in full: its details plus everything it caused (outgoing HTTP calls, database queries, log lines) joined by request id.',\r\n properties: { id: { type: 'integer', description: 'Request id, from nodeui_requests.' } },\r\n async run(args, t) {\r\n const id = num(args.id, NaN);\r\n if (!Number.isFinite(id)) throw new PanelError('id is required', 'bad-args');\r\n const [requests, outgoing, queries, logs] = await Promise.all([\r\n fetchPanel<RequestsData>(t, 'requests'),\r\n optional(fetchPanel<OutgoingData>(t, 'outgoing')),\r\n optional(fetchPanel<QueriesData>(t, 'queries')),\r\n optional(fetchPanel<LogsData>(t, 'logs')),\r\n ]);\r\n const request = requests.entries.find((r) => r.id === id);\r\n if (!request) throw new PanelError(`request #${id} is not in the recent buffer`, 'not-found');\r\n return text({\r\n request,\r\n outgoing: outgoing?.entries.filter((e) => e.requestId === id) ?? [],\r\n queries: queries?.entries.filter((e) => e.requestId === id) ?? [],\r\n logs: logs?.entries.filter((e) => e.requestId === id) ?? [],\r\n });\r\n },\r\n },\r\n {\r\n name: 'nodeui_queries',\r\n description:\r\n 'Database statements (parameters never recorded) with duration, rows, and slow / N+1 flags.',\r\n properties: {\r\n ...limitProp,\r\n onlyProblems: { type: 'boolean', description: 'Only slow, N+1 or failed queries.' },\r\n },\r\n async run(args, t) {\r\n const data = await fetchPanel<QueriesData>(t, 'queries');\r\n const rows =\r\n args.onlyProblems === true\r\n ? data.entries.filter((q) => q.slow || q.nPlusOne || q.error)\r\n : data.entries;\r\n return text({\r\n slowQueryMs: data.slowQueryMs,\r\n nPlusOneGroups: data.nPlusOneGroups,\r\n entries: rows.slice(-limitOf(args.limit)),\r\n });\r\n },\r\n },\r\n {\r\n name: 'nodeui_outgoing',\r\n description: 'Outgoing HTTP calls the app made (host/path, status, duration, error).',\r\n properties: {\r\n ...limitProp,\r\n onlyFailed: { type: 'boolean', description: 'Only calls with an error or status >= 500.' },\r\n },\r\n async run(args, t) {\r\n const data = await fetchPanel<OutgoingData>(t, 'outgoing');\r\n const rows =\r\n args.onlyFailed === true\r\n ? data.entries.filter((e) => e.error || (e.status ?? 0) >= 500)\r\n : data.entries;\r\n return text(rows.slice(-limitOf(args.limit)));\r\n },\r\n },\r\n {\r\n name: 'nodeui_logs',\r\n description: 'Recent application log lines (secrets masked), optionally only warn/error.',\r\n properties: {\r\n ...limitProp,\r\n minLevel: { type: 'string', enum: ['debug', 'info', 'warn', 'error'] },\r\n },\r\n async run(args, t) {\r\n const order = ['debug', 'info', 'warn', 'error'];\r\n const min = order.indexOf(typeof args.minLevel === 'string' ? args.minLevel : 'debug');\r\n const data = await fetchPanel<LogsData>(t, 'logs');\r\n const rows = data.entries.filter((l) => order.indexOf(l.level) >= Math.max(min, 0));\r\n return text(rows.slice(-limitOf(args.limit)));\r\n },\r\n },\r\n {\r\n name: 'nodeui_routes',\r\n description: 'The registered route table (method, path, handler).',\r\n properties: {},\r\n async run(_args, t) {\r\n return text((await fetchPanel<RoutesData>(t, 'routes')).routes);\r\n },\r\n },\r\n];\r\n\r\nexport interface McpOptions {\r\n services: ServiceTarget[];\r\n input?: Readable;\r\n output?: Writable;\r\n}\r\n\r\ninterface RpcRequest {\r\n jsonrpc: '2.0';\r\n id?: string | number | null;\r\n method: string;\r\n params?: Record<string, unknown>;\r\n}\r\n\r\nfunction toolDefinitions(multi: boolean): unknown[] {\r\n return TOOLS.map((t) => ({\r\n name: t.name,\r\n description: t.description + ' Read-only.',\r\n inputSchema: {\r\n type: 'object',\r\n properties: multi\r\n ? { service: { type: 'string', description: 'Service name.' }, ...t.properties }\r\n : t.properties,\r\n additionalProperties: false,\r\n },\r\n annotations: { readOnlyHint: true, openWorldHint: false },\r\n }));\r\n}\r\n\r\n/**\r\n * A Model Context Protocol server over stdio (newline-delimited JSON-RPC)\r\n * exposing the console's data to AI agents. All tools are read-only; it never\r\n * triggers heap snapshots or any confirmation-gated action, and what it\r\n * returns is the already-masked REST data.\r\n */\r\nexport function serveMcp(options: McpOptions): { done: Promise<void> } {\r\n const input = options.input ?? process.stdin;\r\n const output = options.output ?? process.stdout;\r\n const services = options.services;\r\n const multi = services.length > 1;\r\n const send = (msg: unknown): void => {\r\n output.write(`${JSON.stringify(msg)}\\n`);\r\n };\r\n const reply = (id: RpcRequest['id'], result: unknown): void =>\r\n send({ jsonrpc: '2.0', id, result });\r\n const fail = (id: RpcRequest['id'], code: number, message: string): void =>\r\n send({ jsonrpc: '2.0', id, error: { code, message } });\r\n\r\n async function call(params: Record<string, unknown> | undefined): Promise<unknown> {\r\n const tool = TOOLS.find((t) => t.name === params?.name);\r\n if (!tool) return { isError: true, content: [{ type: 'text', text: 'unknown tool' }] };\r\n const args = (params?.arguments ?? {}) as Record<string, unknown>;\r\n const wanted = typeof args.service === 'string' ? args.service : undefined;\r\n const target = wanted\r\n ? services.find((s) => s.name === wanted)\r\n : multi\r\n ? undefined\r\n : services[0];\r\n try {\r\n if (!target) {\r\n throw new PanelError(\r\n `specify \"service\": one of ${services.map((s) => s.name).join(', ')}`,\r\n 'bad-args',\r\n );\r\n }\r\n return { content: [{ type: 'text', text: await tool.run(args, target) }] };\r\n } catch (err) {\r\n return {\r\n isError: true,\r\n content: [{ type: 'text', text: err instanceof Error ? err.message : String(err) }],\r\n };\r\n }\r\n }\r\n\r\n async function handle(req: RpcRequest): Promise<void> {\r\n const isNotification = req.id === undefined;\r\n switch (req.method) {\r\n case 'initialize':\r\n reply(req.id, {\r\n protocolVersion:\r\n typeof req.params?.protocolVersion === 'string'\r\n ? req.params.protocolVersion\r\n : DEFAULT_PROTOCOL,\r\n capabilities: { tools: {} },\r\n serverInfo: SERVER_INFO,\r\n instructions:\r\n 'Read-only view of a running Node app. Call nodeui_overview first, then drill into errors, requests, queries.',\r\n });\r\n // Lazy panels (outgoing calls, queries) only start recording once read.\r\n for (const s of services) {\r\n void Promise.all([\r\n optional(fetchPanel(s, 'outgoing')),\r\n optional(fetchPanel(s, 'queries')),\r\n ]);\r\n }\r\n return;\r\n case 'ping':\r\n reply(req.id, {});\r\n return;\r\n case 'tools/list':\r\n reply(req.id, { tools: toolDefinitions(multi) });\r\n return;\r\n case 'tools/call':\r\n reply(req.id, await call(req.params));\r\n return;\r\n default:\r\n if (!isNotification) fail(req.id, -32601, `method not found: ${req.method}`);\r\n }\r\n }\r\n\r\n const rl = createInterface({ input });\r\n const pending = new Set<Promise<void>>();\r\n rl.on('line', (line) => {\r\n if (!line.trim()) return;\r\n let req: RpcRequest;\r\n try {\r\n req = JSON.parse(line) as RpcRequest;\r\n } catch {\r\n fail(null, -32700, 'parse error');\r\n return;\r\n }\r\n const p = handle(req)\r\n .catch((err: unknown) => {\r\n if (req.id !== undefined)\r\n fail(req.id, -32603, err instanceof Error ? err.message : 'error');\r\n })\r\n .finally(() => pending.delete(p));\r\n pending.add(p);\r\n });\r\n const done = new Promise<void>((resolve) => {\r\n rl.on('close', () => {\r\n void Promise.all(pending).then(() => resolve());\r\n });\r\n });\r\n return { done };\r\n}\r\n","{\r\n \"name\": \"@singhak/nodeui-cli\",\r\n \"version\": \"0.5.0\",\r\n \"description\": \"NodeUI command line: attach the console to any Node app without code changes, a multi-service dashboard, and an MCP server for AI agents.\",\r\n \"license\": \"Apache-2.0\",\r\n \"bin\": {\r\n \"nodeui\": \"dist/cli.cjs\"\r\n },\r\n \"main\": \"./dist/index.cjs\",\r\n \"types\": \"./dist/index.d.ts\",\r\n \"exports\": {\r\n \".\": {\r\n \"types\": \"./dist/index.d.ts\",\r\n \"default\": \"./dist/index.cjs\"\r\n },\r\n \"./package.json\": \"./package.json\"\r\n },\r\n \"files\": [\r\n \"dist\"\r\n ],\r\n \"keywords\": [\r\n \"cli\",\r\n \"npx\",\r\n \"developer-tools\",\r\n \"observability\",\r\n \"debugging\",\r\n \"mcp\",\r\n \"nodejs\"\r\n ],\r\n \"engines\": {\r\n \"node\": \">=18.0.0\"\r\n },\r\n \"publishConfig\": {\r\n \"access\": \"public\"\r\n },\r\n \"repository\": {\r\n \"type\": \"git\",\r\n \"url\": \"https://github.com/Singhak/nodeui.git\"\r\n },\r\n \"homepage\": \"https://github.com/Singhak/nodeui#readme\",\r\n \"bugs\": {\r\n \"url\": \"https://github.com/Singhak/nodeui/issues\"\r\n },\r\n \"scripts\": {\r\n \"prepublishOnly\": \"npm run build\",\r\n \"build\": \"tsup\",\r\n \"typecheck\": \"tsc -p tsconfig.typecheck.json\",\r\n \"test\": \"vitest run\"\r\n },\r\n \"dependencies\": {\r\n \"@singhak/nodeui-core\": \"^0.5.0\"\r\n },\r\n \"devDependencies\": {\r\n \"@types/node\": \"^22.19.0\",\r\n \"tsup\": \"^8.5.0\",\r\n \"typescript\": \"^5.9.3\",\r\n \"vitest\": \"^3.2.4\"\r\n },\r\n \"author\": \"Anil Kumar\",\r\n \"type\": \"commonjs\"\r\n}\r\n","import type {\r\n ErrorsData,\r\n HealthData,\r\n OutgoingData,\r\n QueriesData,\r\n RequestEntry,\r\n RequestsData,\r\n} from '@singhak/nodeui-core';\r\n\r\nexport interface DigestInput {\r\n service: string;\r\n health: HealthData | null;\r\n requests: RequestsData | null;\r\n errors: ErrorsData | null;\r\n queries: QueriesData | null;\r\n outgoing: OutgoingData | null;\r\n}\r\n\r\nconst ms = (n: number): string => `${n.toFixed(n < 10 ? 1 : 0)} ms`;\r\nconst clip = (s: string, n: number): string => (s.length > n ? `${s.slice(0, n)}…` : s);\r\nconst oneLine = (s: string): string => s.replace(/\\s+/g, ' ').trim();\r\n\r\nfunction routeOf(r: RequestEntry): string {\r\n return `${r.method} ${r.route ?? r.path}`;\r\n}\r\n\r\n/**\r\n * A compact, prioritised Markdown summary of one service for an AI agent:\r\n * what is broken first, then what is slow, then context. Everything in it is\r\n * already masked by the console; this only selects and orders.\r\n */\r\nexport function buildDigest(input: DigestInput): string {\r\n const out: string[] = [`# ${input.service}`];\r\n const { health, requests, errors, queries, outgoing } = input;\r\n\r\n if (health) {\r\n out.push(\r\n `Health: **${health.status}**${health.statusReason ? ` (${oneLine(health.statusReason)})` : ''}; ` +\r\n `up ${Math.round(health.uptimeSeconds)}s; event-loop lag ${health.eventLoopLagMs ?? '?'} ms; ` +\r\n `memory ${health.memoryUsedPercent ?? '?'}%.`,\r\n );\r\n const failing = health.checks.filter((c) => c.status === 'down');\r\n for (const c of failing) out.push(`- dependency check \\`${c.name}\\` is down`);\r\n }\r\n\r\n const problems: string[] = [];\r\n if (errors && errors.groups.length > 0) {\r\n const groups = [...errors.groups].sort((a, b) => b.lastSeenMs - a.lastSeenMs).slice(0, 5);\r\n problems.push('## Errors (most recent first)');\r\n for (const g of groups) {\r\n problems.push(\r\n `- **${g.name}: ${clip(oneLine(g.message), 160)}** x${g.count}` +\r\n `${g.lastRoute ? ` on \\`${g.lastRoute}\\`` : ''}` +\r\n `${g.lastRequestId !== undefined ? `, request #${g.lastRequestId}` : ''}` +\r\n ` [${g.source}]; top frame: \\`${clip(oneLine(g.stack.split('\\n')[1] ?? ''), 140)}\\``,\r\n );\r\n }\r\n }\r\n\r\n if (requests) {\r\n const s = requests.summary;\r\n out.push(\r\n `Traffic: ${s.count} recent requests, ${(s.errorRate * 100).toFixed(1)}% 5xx, ` +\r\n `p50 ${ms(s.p50Ms)}, p95 ${ms(s.p95Ms)}, p99 ${ms(s.p99Ms)}.`,\r\n );\r\n const failed = requests.entries.filter((r) => r.status >= 500).slice(-5);\r\n if (failed.length > 0) {\r\n problems.push('## Failing requests');\r\n for (const r of failed) {\r\n problems.push(`- #${r.id} ${routeOf(r)} -> ${r.status} in ${ms(r.durationMs)}`);\r\n }\r\n }\r\n const slow = [...s.routes].sort((a, b) => b.p95Ms - a.p95Ms).slice(0, 3);\r\n if (slow.length > 0) {\r\n problems.push('## Slowest routes (by p95)');\r\n for (const r of slow) {\r\n problems.push(\r\n `- ${r.method} ${r.path}: p95 ${ms(r.p95Ms)}, avg ${ms(r.avgMs)}, ${r.count} calls, ${r.errors} errors`,\r\n );\r\n }\r\n }\r\n }\r\n\r\n if (queries && (queries.slow > 0 || queries.nPlusOneGroups > 0 || queries.failed > 0)) {\r\n problems.push(\r\n `## Database: ${queries.slow} slow (>= ${queries.slowQueryMs} ms), ` +\r\n `${queries.nPlusOneGroups} N+1 suspects, ${queries.failed} failed`,\r\n );\r\n const interesting = queries.entries.filter((q) => q.slow || q.nPlusOne || q.error).slice(-5);\r\n for (const q of interesting) {\r\n const flags = [q.slow && 'slow', q.nPlusOne && `N+1 x${q.repeats ?? '?'}`, q.error && 'error']\r\n .filter(Boolean)\r\n .join(', ');\r\n problems.push(\r\n `- [${flags}] ${ms(q.durationMs)}${q.requestId !== undefined ? `, request #${q.requestId}` : ''}: \\`${clip(oneLine(q.sql), 160)}\\``,\r\n );\r\n }\r\n }\r\n\r\n if (outgoing && outgoing.failed > 0) {\r\n problems.push(`## Outgoing calls: ${outgoing.failed} failed`);\r\n for (const o of outgoing.entries.filter((e) => e.error || (e.status ?? 0) >= 500).slice(-5)) {\r\n problems.push(\r\n `- ${o.method} ${o.url} -> ${o.error ?? o.status}${o.requestId !== undefined ? `, request #${o.requestId}` : ''}`,\r\n );\r\n }\r\n }\r\n\r\n out.push(\r\n problems.length > 0\r\n ? problems.join('\\n')\r\n : 'No errors, failing requests, slow or N+1 queries, or failing outgoing calls were observed.',\r\n );\r\n return out.join('\\n\\n');\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,gCAAsB;AACtB,uBAAqB;AAad,SAAS,cAAsB;AACpC,aAAO,uBAAK,WAAW,aAAa,EAAE,QAAQ,OAAO,GAAG;AAC1D;AAGO,SAAS,UACd,MACA,SACA,UAAU,YAAY,GACH;AACnB,QAAM,MAAyB,EAAE,GAAG,KAAK;AACzC,MAAI,eAAe,CAAC,KAAK,cAAc,cAAc,OAAO,GAAG,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AACzF,MAAI,QAAQ,KAAM,KAAI,cAAc,QAAQ;AAC5C,MAAI,QAAQ,MAAO,KAAI,eAAe,QAAQ;AAC9C,MAAI,QAAQ,QAAS,KAAI,sBAAsB,QAAQ;AACvD,MAAI,QAAQ,KAAM,KAAI,uBAAuB,QAAQ;AACrD,MAAI,QAAQ,OAAQ,KAAI,wBAAwB;AAChD,MAAI,QAAQ,MAAO,KAAI,iBAAiB;AACxC,SAAO;AACT;AAGO,SAAS,OAAO,SAAmB,SAAyC;AACjF,QAAM,CAAC,KAAK,GAAG,IAAI,IAAI;AACvB,MAAI,CAAC,IAAK,QAAO,QAAQ,OAAO,IAAI,MAAM,qDAAqD,CAAC;AAChG,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,YAAQ,iCAAM,KAAK,MAAM;AAAA,MAC7B,OAAO;AAAA,MACP,KAAK,UAAU,QAAQ,KAAK,OAAO;AAAA,MACnC,OAAO,QAAQ,aAAa;AAAA,IAC9B,CAAC;AACD,UAAM,QAAQ,MAAY;AACxB,YAAM,KAAK,QAAQ;AAAA,IACrB;AACA,UAAM,SAAS,MAAY;AACzB,YAAM,KAAK,SAAS;AAAA,IACtB;AACA,UAAM,UAAU,MAAY;AAC1B,cAAQ,IAAI,UAAU,KAAK;AAC3B,cAAQ,IAAI,WAAW,MAAM;AAAA,IAC/B;AACA,YAAQ,GAAG,UAAU,KAAK;AAC1B,YAAQ,GAAG,WAAW,MAAM;AAC5B,UAAM,GAAG,SAAS,CAAC,QAAQ;AACzB,cAAQ;AACR,aAAO,GAAG;AAAA,IACZ,CAAC;AACD,UAAM,GAAG,QAAQ,CAAC,MAAM,WAAW;AACjC,cAAQ;AACR,cAAQ,SAAS,SAAS,IAAI,EAAE;AAAA,IAClC,CAAC;AAAA,EACH,CAAC;AACH;;;AClEA,uBAA6E;AAC7E,wBAAkB;AAElB,yBAA6D;;;ACK7D,IAAM,eAAe;AAMd,SAAS,YAAY,MAA6B;AACvD,QAAM,KAAK,KAAK,QAAQ,GAAG;AAC3B,QAAM,UAAU,KAAK,KAAK,CAAC,KAAK,MAAM,GAAG,EAAE,EAAE,SAAS,GAAG,KAAK,CAAC,KAAK,MAAM,GAAG,EAAE,EAAE,SAAS,GAAG;AAC7F,QAAM,SAAS,UAAU,KAAK,MAAM,KAAK,CAAC,IAAI;AAC9C,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,IAAI,2BAA2B,KAAK,MAAM,IAAI,SAAS,UAAU,MAAM,EAAE;AAAA,EACrF,QAAQ;AACN,UAAM,IAAI,MAAM,wBAAwB,MAAM,GAAG;AAAA,EACnD;AACA,MAAI,IAAI,aAAa,WAAW,IAAI,aAAa,UAAU;AACzD,UAAM,IAAI,MAAM,qCAAqC,MAAM,GAAG;AAAA,EAChE;AACA,QAAM,OAAO,UAAU,KAAK,MAAM,GAAG,EAAE,IAAI,GAAG,IAAI,QAAQ,IAAI,IAAI,QAAQ,IAAI;AAC9E,MAAI,CAAC,aAAa,KAAK,IAAI,GAAG;AAC5B,UAAM,IAAI,MAAM,yBAAyB,IAAI,uCAAuC;AAAA,EACtF;AACA,QAAM,QAAQ,IAAI,aAAa,IAAI,OAAO,KAAK;AAC/C,QAAM,OAAO,IAAI,SAAS,QAAQ,QAAQ,EAAE,KAAK;AACjD,SAAO,EAAE,MAAM,MAAM,GAAG,IAAI,MAAM,GAAG,IAAI,IAAI,MAAM;AACrD;AAEO,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACE,SACS,MACT;AACA,UAAM,OAAO;AAFJ;AAGT,SAAK,OAAO;AAAA,EACd;AAAA,EAJW;AAKb;AAGA,eAAsB,WACpB,QACA,OACA,YAAY,KACA;AACZ,QAAM,UAAkC,EAAE,QAAQ,mBAAmB;AACrE,MAAI,OAAO,MAAO,SAAQ,gBAAgB,UAAU,OAAO,KAAK;AAChE,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,MAAM,GAAG,OAAO,IAAI,QAAQ,KAAK,IAAI;AAAA,MAC/C;AAAA,MACA,QAAQ,YAAY,QAAQ,SAAS;AAAA,IACvC,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,gBAAgB,OAAO,IAAI,OAAO,OAAO,IAAI,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MAClG;AAAA,IACF;AAAA,EACF;AACA,MAAI;AACJ,MAAI;AACF,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB,QAAQ;AACN,UAAM,IAAI,WAAW,GAAG,OAAO,IAAI,4BAA4B,IAAI,MAAM,KAAK,SAAS;AAAA,EACzF;AACA,MAAI,CAAC,KAAK,IAAI;AACZ,UAAM,IAAI,WAAW,KAAK,OAAO,WAAW,QAAQ,IAAI,MAAM,IAAI,KAAK,OAAO,QAAQ,OAAO;AAAA,EAC/F;AACA,SAAO,KAAK;AACd;;;AD9CA,IAAM,aAAa,oBAAI,IAAI;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBb,IAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCf,eAAe,OAAO,QAA+C;AACnE,QAAM,MAAqB,EAAE,MAAM,OAAO,MAAM,KAAK,OAAO,MAAM,IAAI,MAAM;AAC5E,MAAI;AACF,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA;AAAA,IACF;AACA,QAAI,CAAC,OAAO,SAAS;AACnB,UAAI,SAAS,OAAO;AACpB,aAAO;AAAA,IACT;AACA,UAAM,CAAC,QAAQ,UAAU,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,MACnD,WAA+B,QAAQ,QAAQ,EAAE,MAAM,MAAM,IAAI;AAAA,MACjE;AAAA,QACE;AAAA,QACA;AAAA,MACF,EAAE,MAAM,MAAM,IAAI;AAAA,MAClB,WAAkC,QAAQ,QAAQ,EAAE,MAAM,MAAM,IAAI;AAAA,IACtE,CAAC;AACD,QAAI,KAAK;AACT,QAAI,SAAS,QAAQ;AACrB,QAAI,WAAW,UAAU;AACzB,QAAI,YAAY,UAAU,QAAQ;AAClC,QAAI,QAAQ,UAAU,QAAQ;AAC9B,QAAI,cAAc,QAAQ,OAAO;AAAA,EACnC,SAAS,KAAK;AACZ,QAAI,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,EAC9D;AACA,SAAO;AACT;AAEA,SAAS,KAAK,KAAqB,MAAc,MAAqB;AACpE,MAAI,UAAU,MAAM;AAAA,IAClB,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,0BAA0B;AAAA,EAC5B,CAAC;AACD,MAAI,IAAI,KAAK,UAAU,IAAI,CAAC;AAC9B;AAEA,SAAS,MACP,QACA,MACA,QACA,KACA,KACM;AACN,QAAM,MAAM,IAAI,IAAI,GAAG,OAAO,IAAI,GAAG,IAAI,GAAG,MAAM,EAAE;AACpD,QAAM,UAA6C,CAAC;AACpD,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AAChD,QAAI,MAAM,UAAa,WAAW,IAAI,CAAC,EAAG;AAE1C,QAAI,MAAM,UAAU,MAAM,YAAY,MAAM,aAAa,MAAM,SAAU;AACzE,YAAQ,CAAC,IAAI;AAAA,EACf;AACA,MAAI,OAAO,MAAO,SAAQ,gBAAgB,UAAU,OAAO,KAAK;AAChE,QAAM,SAAS,IAAI,aAAa,WAAW,kBAAAA,UAAQ,iBAAAC;AACnD,QAAM,WAAW,OAAO,QAAQ,KAAK,EAAE,QAAQ,IAAI,QAAQ,QAAQ,GAAG,CAAC,OAAO;AAC5E,UAAM,MAAyC,CAAC;AAChD,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,GAAG,OAAO,GAAG;AAC/C,UAAI,MAAM,UAAa,WAAW,IAAI,CAAC,KAAK,MAAM,aAAc;AAChE,UAAI,CAAC,IAAI;AAAA,IACX;AACA,QAAI,UAAU,GAAG,cAAc,KAAK,GAAG;AACvC,OAAG,KAAK,GAAG;AAAA,EACb,CAAC;AACD,WAAS,GAAG,SAAS,CAAC,QAAQ;AAC5B,QAAI,IAAI,aAAa;AACnB,UAAI,QAAQ;AACZ;AAAA,IACF;AACA,SAAK,KAAK,KAAK;AAAA,MACb,IAAI;AAAA,MACJ,OAAO,EAAE,MAAM,eAAe,SAAS,gBAAgB,OAAO,IAAI,KAAK,IAAI,OAAO,GAAG;AAAA,IACvF,CAAC;AAAA,EACH,CAAC;AACD,MAAI,GAAG,SAAS,MAAM,SAAS,QAAQ,CAAC;AACxC,MAAI,KAAK,QAAQ;AACnB;AAMA,eAAsB,eAAe,SAA+C;AAClF,QAAM,SAAS,IAAI,IAAI,QAAQ,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAC/D,MAAI,OAAO,SAAS,QAAQ,SAAS,OAAQ,OAAM,IAAI,MAAM,8BAA8B;AAC3F,QAAM,YAAQ,gCAAY;AAAA,IACxB,MAAM;AAAA,IACN,cAAc,CAAC;AAAA,IACf,gBAAgB,CAAC;AAAA,IACjB,wBAAwB,CAAC;AAAA,IACzB,YAAY;AAAA,EACd,CAAC;AACD,QAAM,aAAS,gCAAY;AAE3B,QAAM,SAAS,iBAAAA,QAAK,aAAa,CAAC,KAAK,QAAQ;AAC7C,UAAM,UAAU,MAAM,GAAG;AACzB,QAAI,CAAC,QAAQ,IAAI;AACf,WAAK,KAAK,QAAQ,QAAQ;AAAA,QACxB,IAAI;AAAA,QACJ,OAAO,EAAE,MAAM,QAAQ,MAAM,SAAS,QAAQ,QAAQ;AAAA,MACxD,CAAC;AACD;AAAA,IACF;AACA,UAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB;AACtD,UAAM,OAAO,IAAI;AAEjB,QAAI,SAAS,OAAO,IAAI,WAAW,OAAO;AACxC,UAAI,UAAU,KAAK;AAAA,QACjB,gBAAgB;AAAA,QAChB,2BAA2B;AAAA,QAC3B,0BAA0B;AAAA,MAC5B,CAAC;AACD,UAAI,IAAI,IAAI;AACZ;AAAA,IACF;AACA,QAAI,SAAS,kBAAkB;AAC7B,UAAI,UAAU,KAAK,EAAE,gBAAgB,iCAAiC,CAAC;AACvE,UAAI,IAAI,MAAM;AACd;AAAA,IACF;AACA,QAAI,SAAS,iBAAiB;AAC5B,WAAK,QAAQ,IAAI,QAAQ,SAAS,IAAI,MAAM,CAAC,EAAE;AAAA,QAAK,CAAC,SACnD,KAAK,KAAK,KAAK,EAAE,IAAI,MAAM,KAAK,CAAC;AAAA,MACnC;AACA;AAAA,IACF;AAEA,UAAM,QAAQ,wBAAwB,KAAK,IAAI;AAC/C,QAAI;AACJ,QAAI;AACF,eAAS,QAAQ,OAAO,IAAI,mBAAmB,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI;AAAA,IACpE,QAAQ;AACN,eAAS;AAAA,IACX;AACA,QAAI,CAAC,SAAS,CAAC,QAAQ;AACrB,WAAK,KAAK,KAAK,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,aAAa,SAAS,kBAAkB,EAAE,CAAC;AACtF;AAAA,IACF;AACA,UAAM,OAAO,MAAM,CAAC;AACpB,QAAI,SAAS,QAAW;AACtB,UAAI,UAAU,KAAK,EAAE,UAAU,GAAG,IAAI,IAAI,CAAC;AAC3C,UAAI,IAAI;AACR;AAAA,IACF;AACA,QAAI,KAAK,WAAW,OAAO,GAAG;AAC5B,YAAM,QAAQ,MAAM,IAAI,QAAQ,KAAK,GAAG;AACxC;AAAA,IACF;AACA,UAAM,YAAQ,uCAAmB,QAAQ,SAAS,MAAM,KAAK,IAAI;AACjE,QAAI,CAAC,OAAO;AACV,WAAK,KAAK,KAAK,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,aAAa,SAAS,oBAAoB,EAAE,CAAC;AACxF;AAAA,IACF;AACA,QAAI,UAAU,KAAK;AAAA,MACjB,gBAAgB,MAAM;AAAA,MACtB,kBAAkB,MAAM;AAAA,MACxB,0BAA0B;AAAA,IAC5B,CAAC;AACD,UAAM,QAAQ,KAAK,GAAG;AAAA,EACxB,CAAC;AAED,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAO,KAAK,SAAS,MAAM;AAC3B,WAAO,OAAO,QAAQ,QAAQ,KAAM,aAAa,OAAO;AAAA,EAC1D,CAAC;AACD,SAAO;AAAA,IACL;AAAA,IACA,MAAO,OAAO,QAAQ,EAAkB;AAAA,IACxC,OAAO,MACL,IAAI,QAAc,CAAC,YAAY;AAC7B,aAAO,sBAAsB;AAC7B,aAAO,MAAM,MAAM,QAAQ,CAAC;AAAA,IAC9B,CAAC;AAAA,EACL;AACF;;;AEjRA,2BAAgC;;;ACAhC;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,aAAe;AAAA,EACf,SAAW;AAAA,EACX,KAAO;AAAA,IACL,QAAU;AAAA,EACZ;AAAA,EACA,MAAQ;AAAA,EACR,OAAS;AAAA,EACT,SAAW;AAAA,IACT,KAAK;AAAA,MACH,OAAS;AAAA,MACT,SAAW;AAAA,IACb;AAAA,IACA,kBAAkB;AAAA,EACpB;AAAA,EACA,OAAS;AAAA,IACP;AAAA,EACF;AAAA,EACA,UAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,SAAW;AAAA,IACT,MAAQ;AAAA,EACV;AAAA,EACA,eAAiB;AAAA,IACf,QAAU;AAAA,EACZ;AAAA,EACA,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,UAAY;AAAA,EACZ,MAAQ;AAAA,IACN,KAAO;AAAA,EACT;AAAA,EACA,SAAW;AAAA,IACT,gBAAkB;AAAA,IAClB,OAAS;AAAA,IACT,WAAa;AAAA,IACb,MAAQ;AAAA,EACV;AAAA,EACA,cAAgB;AAAA,IACd,wBAAwB;AAAA,EAC1B;AAAA,EACA,iBAAmB;AAAA,IACjB,eAAe;AAAA,IACf,MAAQ;AAAA,IACR,YAAc;AAAA,IACd,QAAU;AAAA,EACZ;AAAA,EACA,QAAU;AAAA,EACV,MAAQ;AACV;;;AC1CA,IAAM,KAAK,CAAC,MAAsB,GAAG,EAAE,QAAQ,IAAI,KAAK,IAAI,CAAC,CAAC;AAC9D,IAAM,OAAO,CAAC,GAAW,MAAuB,EAAE,SAAS,IAAI,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC,WAAM;AACrF,IAAM,UAAU,CAAC,MAAsB,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAEnE,SAAS,QAAQ,GAAyB;AACxC,SAAO,GAAG,EAAE,MAAM,IAAI,EAAE,SAAS,EAAE,IAAI;AACzC;AAOO,SAAS,YAAY,OAA4B;AACtD,QAAM,MAAgB,CAAC,KAAK,MAAM,OAAO,EAAE;AAC3C,QAAM,EAAE,QAAQ,UAAU,QAAQ,SAAS,SAAS,IAAI;AAExD,MAAI,QAAQ;AACV,QAAI;AAAA,MACF,aAAa,OAAO,MAAM,KAAK,OAAO,eAAe,KAAK,QAAQ,OAAO,YAAY,CAAC,MAAM,EAAE,QACtF,KAAK,MAAM,OAAO,aAAa,CAAC,qBAAqB,OAAO,kBAAkB,GAAG,eAC7E,OAAO,qBAAqB,GAAG;AAAA,IAC7C;AACA,UAAM,UAAU,OAAO,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM;AAC/D,eAAW,KAAK,QAAS,KAAI,KAAK,wBAAwB,EAAE,IAAI,YAAY;AAAA,EAC9E;AAEA,QAAM,WAAqB,CAAC;AAC5B,MAAI,UAAU,OAAO,OAAO,SAAS,GAAG;AACtC,UAAM,SAAS,CAAC,GAAG,OAAO,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,GAAG,CAAC;AACxF,aAAS,KAAK,+BAA+B;AAC7C,eAAW,KAAK,QAAQ;AACtB,eAAS;AAAA,QACP,OAAO,EAAE,IAAI,KAAK,KAAK,QAAQ,EAAE,OAAO,GAAG,GAAG,CAAC,OAAO,EAAE,KAAK,GACxD,EAAE,YAAY,SAAS,EAAE,SAAS,OAAO,EAAE,GAC3C,EAAE,kBAAkB,SAAY,cAAc,EAAE,aAAa,KAAK,EAAE,KAClE,EAAE,MAAM,mBAAmB,KAAK,QAAQ,EAAE,MAAM,MAAM,IAAI,EAAE,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC;AAAA,MACpF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,UAAU;AACZ,UAAM,IAAI,SAAS;AACnB,QAAI;AAAA,MACF,YAAY,EAAE,KAAK,sBAAsB,EAAE,YAAY,KAAK,QAAQ,CAAC,CAAC,cAC7D,GAAG,EAAE,KAAK,CAAC,SAAS,GAAG,EAAE,KAAK,CAAC,SAAS,GAAG,EAAE,KAAK,CAAC;AAAA,IAC9D;AACA,UAAM,SAAS,SAAS,QAAQ,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,EAAE,MAAM,EAAE;AACvE,QAAI,OAAO,SAAS,GAAG;AACrB,eAAS,KAAK,qBAAqB;AACnC,iBAAW,KAAK,QAAQ;AACtB,iBAAS,KAAK,MAAM,EAAE,EAAE,IAAI,QAAQ,CAAC,CAAC,OAAO,EAAE,MAAM,OAAO,GAAG,EAAE,UAAU,CAAC,EAAE;AAAA,MAChF;AAAA,IACF;AACA,UAAM,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC;AACvE,QAAI,KAAK,SAAS,GAAG;AACnB,eAAS,KAAK,4BAA4B;AAC1C,iBAAW,KAAK,MAAM;AACpB,iBAAS;AAAA,UACP,KAAK,EAAE,MAAM,IAAI,EAAE,IAAI,SAAS,GAAG,EAAE,KAAK,CAAC,SAAS,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,WAAW,EAAE,MAAM;AAAA,QAChG;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAY,QAAQ,OAAO,KAAK,QAAQ,iBAAiB,KAAK,QAAQ,SAAS,IAAI;AACrF,aAAS;AAAA,MACP,gBAAgB,QAAQ,IAAI,aAAa,QAAQ,WAAW,SACvD,QAAQ,cAAc,kBAAkB,QAAQ,MAAM;AAAA,IAC7D;AACA,UAAM,cAAc,QAAQ,QAAQ,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE;AAC3F,eAAW,KAAK,aAAa;AAC3B,YAAM,QAAQ,CAAC,EAAE,QAAQ,QAAQ,EAAE,YAAY,QAAQ,EAAE,WAAW,GAAG,IAAI,EAAE,SAAS,OAAO,EAC1F,OAAO,OAAO,EACd,KAAK,IAAI;AACZ,eAAS;AAAA,QACP,MAAM,KAAK,KAAK,GAAG,EAAE,UAAU,CAAC,GAAG,EAAE,cAAc,SAAY,cAAc,EAAE,SAAS,KAAK,EAAE,OAAO,KAAK,QAAQ,EAAE,GAAG,GAAG,GAAG,CAAC;AAAA,MACjI;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAY,SAAS,SAAS,GAAG;AACnC,aAAS,KAAK,sBAAsB,SAAS,MAAM,SAAS;AAC5D,eAAW,KAAK,SAAS,QAAQ,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,MAAM,GAAG,EAAE,MAAM,EAAE,GAAG;AAC3F,eAAS;AAAA,QACP,KAAK,EAAE,MAAM,IAAI,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,EAAE,cAAc,SAAY,cAAc,EAAE,SAAS,KAAK,EAAE;AAAA,MACjH;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AAAA,IACF,SAAS,SAAS,IACd,SAAS,KAAK,IAAI,IAClB;AAAA,EACN;AACA,SAAO,IAAI,KAAK,MAAM;AACxB;;;AFnGA,IAAM,cAAc,EAAE,MAAM,UAAU,SAAS,gBAAI,QAAQ;AAC3D,IAAM,mBAAmB;AACzB,IAAM,gBAAgB;AACtB,IAAM,YAAY;AASlB,IAAM,MAAM,CAAC,GAAY,aACvB,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,IAAI,IAAI;AACpD,IAAM,UAAU,CAAC,MACf,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,SAAS;AACpE,IAAM,OAAO,CAAC,MAAuB,KAAK,UAAU,GAAG,MAAM,CAAC;AAC9D,IAAM,WAAW,CAAI,MAAqC,EAAE,MAAM,MAAM,IAAI;AAE5E,IAAM,YAAY;AAAA,EAChB,OAAO,EAAE,MAAM,WAAW,aAAa,mCAAmC,aAAa,KAAK;AAC9F;AAEA,IAAM,QAAgB;AAAA,EACpB;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY,CAAC;AAAA,IACb,MAAM,IAAI,OAAO,GAAG;AAClB,YAAM,CAAC,QAAQ,UAAU,QAAQ,SAAS,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,QACtE,SAAS,WAAuB,GAAG,QAAQ,CAAC;AAAA,QAC5C,SAAS,WAAyB,GAAG,UAAU,CAAC;AAAA,QAChD,SAAS,WAAuB,GAAG,QAAQ,CAAC;AAAA,QAC5C,SAAS,WAAwB,GAAG,SAAS,CAAC;AAAA,QAC9C,SAAS,WAAyB,GAAG,UAAU,CAAC;AAAA,MAClD,CAAC;AACD,aAAO,YAAY,EAAE,SAAS,EAAE,MAAM,QAAQ,UAAU,QAAQ,SAAS,SAAS,CAAC;AAAA,IACrF;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY;AAAA,IACZ,MAAM,IAAI,MAAM,GAAG;AACjB,YAAM,OAAO,MAAM,WAAuB,GAAG,QAAQ;AACrD,aAAO,KAAK,KAAK,OAAO,MAAM,GAAG,QAAQ,KAAK,KAAK,CAAC,CAAC;AAAA,IACvD;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY;AAAA,MACV,GAAG;AAAA,MACH,WAAW,EAAE,MAAM,WAAW,aAAa,gDAAgD;AAAA,MAC3F,eAAe,EAAE,MAAM,UAAU,aAAa,oCAAoC;AAAA,IACpF;AAAA,IACA,MAAM,IAAI,MAAM,GAAG;AACjB,YAAM,OAAO,MAAM,WAAyB,GAAG,UAAU;AACzD,YAAM,YAAY,IAAI,KAAK,WAAW,CAAC;AACvC,YAAM,QAAQ,IAAI,KAAK,eAAe,CAAC;AACvC,YAAM,OAAO,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,UAAU,aAAa,EAAE,cAAc,KAAK;AACtF,aAAO,KAAK,EAAE,SAAS,KAAK,SAAS,SAAS,KAAK,MAAM,CAAC,QAAQ,KAAK,KAAK,CAAC,EAAE,CAAC;AAAA,IAClF;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,aAAa,oCAAoC,EAAE;AAAA,IACxF,MAAM,IAAI,MAAM,GAAG;AACjB,YAAM,KAAK,IAAI,KAAK,IAAI,GAAG;AAC3B,UAAI,CAAC,OAAO,SAAS,EAAE,EAAG,OAAM,IAAI,WAAW,kBAAkB,UAAU;AAC3E,YAAM,CAAC,UAAU,UAAU,SAAS,IAAI,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC5D,WAAyB,GAAG,UAAU;AAAA,QACtC,SAAS,WAAyB,GAAG,UAAU,CAAC;AAAA,QAChD,SAAS,WAAwB,GAAG,SAAS,CAAC;AAAA,QAC9C,SAAS,WAAqB,GAAG,MAAM,CAAC;AAAA,MAC1C,CAAC;AACD,YAAM,UAAU,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACxD,UAAI,CAAC,QAAS,OAAM,IAAI,WAAW,YAAY,EAAE,gCAAgC,WAAW;AAC5F,aAAO,KAAK;AAAA,QACV;AAAA,QACA,UAAU,UAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,CAAC;AAAA,QAClE,SAAS,SAAS,QAAQ,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,CAAC;AAAA,QAChE,MAAM,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,CAAC;AAAA,MAC5D,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY;AAAA,MACV,GAAG;AAAA,MACH,cAAc,EAAE,MAAM,WAAW,aAAa,oCAAoC;AAAA,IACpF;AAAA,IACA,MAAM,IAAI,MAAM,GAAG;AACjB,YAAM,OAAO,MAAM,WAAwB,GAAG,SAAS;AACvD,YAAM,OACJ,KAAK,iBAAiB,OAClB,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,IAC1D,KAAK;AACX,aAAO,KAAK;AAAA,QACV,aAAa,KAAK;AAAA,QAClB,gBAAgB,KAAK;AAAA,QACrB,SAAS,KAAK,MAAM,CAAC,QAAQ,KAAK,KAAK,CAAC;AAAA,MAC1C,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,MACV,GAAG;AAAA,MACH,YAAY,EAAE,MAAM,WAAW,aAAa,6CAA6C;AAAA,IAC3F;AAAA,IACA,MAAM,IAAI,MAAM,GAAG;AACjB,YAAM,OAAO,MAAM,WAAyB,GAAG,UAAU;AACzD,YAAM,OACJ,KAAK,eAAe,OAChB,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,MAAM,GAAG,IAC5D,KAAK;AACX,aAAO,KAAK,KAAK,MAAM,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC;AAAA,IAC9C;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,MACV,GAAG;AAAA,MACH,UAAU,EAAE,MAAM,UAAU,MAAM,CAAC,SAAS,QAAQ,QAAQ,OAAO,EAAE;AAAA,IACvE;AAAA,IACA,MAAM,IAAI,MAAM,GAAG;AACjB,YAAM,QAAQ,CAAC,SAAS,QAAQ,QAAQ,OAAO;AAC/C,YAAM,MAAM,MAAM,QAAQ,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW,OAAO;AACrF,YAAM,OAAO,MAAM,WAAqB,GAAG,MAAM;AACjD,YAAM,OAAO,KAAK,QAAQ,OAAO,CAAC,MAAM,MAAM,QAAQ,EAAE,KAAK,KAAK,KAAK,IAAI,KAAK,CAAC,CAAC;AAClF,aAAO,KAAK,KAAK,MAAM,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC;AAAA,IAC9C;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,CAAC;AAAA,IACb,MAAM,IAAI,OAAO,GAAG;AAClB,aAAO,MAAM,MAAM,WAAuB,GAAG,QAAQ,GAAG,MAAM;AAAA,IAChE;AAAA,EACF;AACF;AAeA,SAAS,gBAAgB,OAA2B;AAClD,SAAO,MAAM,IAAI,CAAC,OAAO;AAAA,IACvB,MAAM,EAAE;AAAA,IACR,aAAa,EAAE,cAAc;AAAA,IAC7B,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY,QACR,EAAE,SAAS,EAAE,MAAM,UAAU,aAAa,gBAAgB,GAAG,GAAG,EAAE,WAAW,IAC7E,EAAE;AAAA,MACN,sBAAsB;AAAA,IACxB;AAAA,IACA,aAAa,EAAE,cAAc,MAAM,eAAe,MAAM;AAAA,EAC1D,EAAE;AACJ;AAQO,SAAS,SAAS,SAA8C;AACrE,QAAM,QAAQ,QAAQ,SAAS,QAAQ;AACvC,QAAM,SAAS,QAAQ,UAAU,QAAQ;AACzC,QAAM,WAAW,QAAQ;AACzB,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,OAAO,CAAC,QAAuB;AACnC,WAAO,MAAM,GAAG,KAAK,UAAU,GAAG,CAAC;AAAA,CAAI;AAAA,EACzC;AACA,QAAM,QAAQ,CAAC,IAAsB,WACnC,KAAK,EAAE,SAAS,OAAO,IAAI,OAAO,CAAC;AACrC,QAAM,OAAO,CAAC,IAAsB,MAAc,YAChD,KAAK,EAAE,SAAS,OAAO,IAAI,OAAO,EAAE,MAAM,QAAQ,EAAE,CAAC;AAEvD,iBAAe,KAAK,QAA+D;AACjF,UAAM,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,IAAI;AACtD,QAAI,CAAC,KAAM,QAAO,EAAE,SAAS,MAAM,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,eAAe,CAAC,EAAE;AACrF,UAAM,OAAQ,QAAQ,aAAa,CAAC;AACpC,UAAM,SAAS,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;AACjE,UAAM,SAAS,SACX,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,IACtC,QACE,SACA,SAAS,CAAC;AAChB,QAAI;AACF,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI;AAAA,UACR,6BAA6B,SAAS,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,UACnE;AAAA,QACF;AAAA,MACF;AACA,aAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,MAAM,KAAK,IAAI,MAAM,MAAM,EAAE,CAAC,EAAE;AAAA,IAC3E,SAAS,KAAK;AACZ,aAAO;AAAA,QACL,SAAS;AAAA,QACT,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC;AAAA,MACpF;AAAA,IACF;AAAA,EACF;AAEA,iBAAe,OAAO,KAAgC;AACpD,UAAM,iBAAiB,IAAI,OAAO;AAClC,YAAQ,IAAI,QAAQ;AAAA,MAClB,KAAK;AACH,cAAM,IAAI,IAAI;AAAA,UACZ,iBACE,OAAO,IAAI,QAAQ,oBAAoB,WACnC,IAAI,OAAO,kBACX;AAAA,UACN,cAAc,EAAE,OAAO,CAAC,EAAE;AAAA,UAC1B,YAAY;AAAA,UACZ,cACE;AAAA,QACJ,CAAC;AAED,mBAAW,KAAK,UAAU;AACxB,eAAK,QAAQ,IAAI;AAAA,YACf,SAAS,WAAW,GAAG,UAAU,CAAC;AAAA,YAClC,SAAS,WAAW,GAAG,SAAS,CAAC;AAAA,UACnC,CAAC;AAAA,QACH;AACA;AAAA,MACF,KAAK;AACH,cAAM,IAAI,IAAI,CAAC,CAAC;AAChB;AAAA,MACF,KAAK;AACH,cAAM,IAAI,IAAI,EAAE,OAAO,gBAAgB,KAAK,EAAE,CAAC;AAC/C;AAAA,MACF,KAAK;AACH,cAAM,IAAI,IAAI,MAAM,KAAK,IAAI,MAAM,CAAC;AACpC;AAAA,MACF;AACE,YAAI,CAAC,eAAgB,MAAK,IAAI,IAAI,QAAQ,qBAAqB,IAAI,MAAM,EAAE;AAAA,IAC/E;AAAA,EACF;AAEA,QAAM,SAAK,sCAAgB,EAAE,MAAM,CAAC;AACpC,QAAM,UAAU,oBAAI,IAAmB;AACvC,KAAG,GAAG,QAAQ,CAAC,SAAS;AACtB,QAAI,CAAC,KAAK,KAAK,EAAG;AAClB,QAAI;AACJ,QAAI;AACF,YAAM,KAAK,MAAM,IAAI;AAAA,IACvB,QAAQ;AACN,WAAK,MAAM,QAAQ,aAAa;AAChC;AAAA,IACF;AACA,UAAM,IAAI,OAAO,GAAG,EACjB,MAAM,CAAC,QAAiB;AACvB,UAAI,IAAI,OAAO;AACb,aAAK,IAAI,IAAI,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO;AAAA,IACrE,CAAC,EACA,QAAQ,MAAM,QAAQ,OAAO,CAAC,CAAC;AAClC,YAAQ,IAAI,CAAC;AAAA,EACf,CAAC;AACD,QAAM,OAAO,IAAI,QAAc,CAAC,YAAY;AAC1C,OAAG,GAAG,SAAS,MAAM;AACnB,WAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,MAAM,QAAQ,CAAC;AAAA,IAChD,CAAC;AAAA,EACH,CAAC;AACD,SAAO,EAAE,KAAK;AAChB;","names":["https","http"]}
@@ -0,0 +1,85 @@
1
+ import { Server } from 'node:http';
2
+ import { Readable, Writable } from 'node:stream';
3
+ import { HealthData, RequestsData, ErrorsData, QueriesData, OutgoingData } from '@singhak/nodeui-core';
4
+
5
+ interface AttachOptions {
6
+ path?: string;
7
+ token?: string;
8
+ persist?: string;
9
+ otlp?: string;
10
+ bodies?: boolean;
11
+ /** Force-enable even when NODE_ENV=production. */
12
+ force?: boolean;
13
+ }
14
+ /** `node --require` accepts quoted paths; forward slashes avoid backslash escapes on Windows. */
15
+ declare function preloadPath(): string;
16
+ /** Environment for the child process: the preload plus the NODEUI_* settings. */
17
+ declare function attachEnv(base: NodeJS.ProcessEnv, options: AttachOptions, preload?: string): NodeJS.ProcessEnv;
18
+ /** Runs `command` with NodeUI preloaded; resolves with the child's exit code. */
19
+ declare function attach(command: string[], options: AttachOptions): Promise<number>;
20
+
21
+ /** A running NodeUI console that the CLI talks to over its REST API. */
22
+ interface ServiceTarget {
23
+ name: string;
24
+ /** Console base URL without a trailing slash, e.g. `http://127.0.0.1:3000/nodeui`. */
25
+ base: string;
26
+ token?: string;
27
+ }
28
+ /**
29
+ * Parses `name=url` or a bare `url`. A URL without a path means the default
30
+ * console path `/nodeui`; a `?token=` query becomes the access token.
31
+ */
32
+ declare function parseTarget(spec: string): ServiceTarget;
33
+ declare class PanelError extends Error {
34
+ readonly code: string;
35
+ constructor(message: string, code: string);
36
+ }
37
+ /** Reads one panel (`/api/<panel>`) from a service and unwraps the `{ok,data}` envelope. */
38
+ declare function fetchPanel<T>(target: ServiceTarget, panel: string, timeoutMs?: number): Promise<T>;
39
+
40
+ interface DashboardOptions {
41
+ services: ServiceTarget[];
42
+ port?: number;
43
+ }
44
+ interface Dashboard {
45
+ server: Server;
46
+ port: number;
47
+ close(): Promise<void>;
48
+ }
49
+ /**
50
+ * Serves one landing page that lists several NodeUI consoles and proxies each
51
+ * one's console and API under `/s/<name>/`. Loopback-only, like the consoles.
52
+ */
53
+ declare function startDashboard(options: DashboardOptions): Promise<Dashboard>;
54
+
55
+ interface McpOptions {
56
+ services: ServiceTarget[];
57
+ input?: Readable;
58
+ output?: Writable;
59
+ }
60
+ /**
61
+ * A Model Context Protocol server over stdio (newline-delimited JSON-RPC)
62
+ * exposing the console's data to AI agents. All tools are read-only; it never
63
+ * triggers heap snapshots or any confirmation-gated action, and what it
64
+ * returns is the already-masked REST data.
65
+ */
66
+ declare function serveMcp(options: McpOptions): {
67
+ done: Promise<void>;
68
+ };
69
+
70
+ interface DigestInput {
71
+ service: string;
72
+ health: HealthData | null;
73
+ requests: RequestsData | null;
74
+ errors: ErrorsData | null;
75
+ queries: QueriesData | null;
76
+ outgoing: OutgoingData | null;
77
+ }
78
+ /**
79
+ * A compact, prioritised Markdown summary of one service for an AI agent:
80
+ * what is broken first, then what is slow, then context. Everything in it is
81
+ * already masked by the console; this only selects and orders.
82
+ */
83
+ declare function buildDigest(input: DigestInput): string;
84
+
85
+ export { type AttachOptions, type Dashboard, type DashboardOptions, type DigestInput, type McpOptions, PanelError, type ServiceTarget, attach, attachEnv, buildDigest, fetchPanel, parseTarget, preloadPath, serveMcp, startDashboard };
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/preload.ts
26
+ var import_node_http = __toESM(require("http"));
27
+ var import_node_https = __toESM(require("https"));
28
+ var import_nodeui_core = require("@singhak/nodeui-core");
29
+ var GUARD = /* @__PURE__ */ Symbol.for("nodeui.cli.preload");
30
+ var globals = globalThis;
31
+ if (!globals[GUARD]) {
32
+ globals[GUARD] = true;
33
+ try {
34
+ install();
35
+ } catch (err) {
36
+ console.warn(`[nodeui] attach failed: ${err instanceof Error ? err.message : String(err)}`);
37
+ }
38
+ }
39
+ function install() {
40
+ const ui = (0, import_nodeui_core.createNodeUI)();
41
+ if (!ui.active) {
42
+ console.warn(`[nodeui] not attached (${ui.activationReason}).`);
43
+ return;
44
+ }
45
+ const prefix = ui.config.path;
46
+ const recorder = ui.middleware();
47
+ const announced = /* @__PURE__ */ new WeakSet();
48
+ const token = process.env.NODEUI_TOKEN;
49
+ const underPrefix = (url) => {
50
+ let path = url ?? "/";
51
+ try {
52
+ path = new URL(path, "http://localhost").pathname;
53
+ } catch {
54
+ }
55
+ return path === prefix || path.startsWith(`${prefix}/`);
56
+ };
57
+ for (const proto of [import_node_http.default.Server.prototype, import_node_https.default.Server.prototype]) {
58
+ const original = proto.emit;
59
+ proto.emit = function patched(event, ...args) {
60
+ if (event === "request") {
61
+ const [req, res] = args;
62
+ if (underPrefix(req.url)) {
63
+ void ui.handle(req, res);
64
+ return true;
65
+ }
66
+ recorder(req, res, () => void 0);
67
+ return (0, import_nodeui_core.runInRequestContext)(req, () => original.call(this, event, ...args));
68
+ }
69
+ if (event === "listening" && !announced.has(this)) {
70
+ announced.add(this);
71
+ const addr = this.address();
72
+ if (addr && typeof addr === "object") {
73
+ const scheme = this instanceof import_node_https.default.Server ? "https" : "http";
74
+ const query = token ? `?token=${encodeURIComponent(token)}` : "";
75
+ const line = `[nodeui] console: ${scheme}://127.0.0.1:${addr.port}${prefix}/${query}`;
76
+ process.stdout.write(`${line}
77
+ `);
78
+ }
79
+ }
80
+ return original.call(this, event, ...args);
81
+ };
82
+ }
83
+ }
84
+ //# sourceMappingURL=preload.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/preload.ts"],"sourcesContent":["/**\r\n * Loaded into the target app with `node --require`. It wraps `emit` on\r\n * `http.Server` / `https.Server` so every request is recorded and the console\r\n * is served on the app's own port, with no change to the app's code.\r\n * Configuration comes from `NODEUI_*` environment variables.\r\n */\r\nimport http from 'node:http';\r\nimport https from 'node:https';\r\nimport type { AddressInfo } from 'node:net';\r\nimport { createNodeUI, runInRequestContext } from '@singhak/nodeui-core';\r\n\r\nconst GUARD = Symbol.for('nodeui.cli.preload');\r\nconst globals = globalThis as { [GUARD]?: boolean };\r\n\r\nif (!globals[GUARD]) {\r\n globals[GUARD] = true;\r\n try {\r\n install();\r\n } catch (err) {\r\n // Never stop the app because the console could not start.\r\n console.warn(`[nodeui] attach failed: ${err instanceof Error ? err.message : String(err)}`);\r\n }\r\n}\r\n\r\ntype Emit = (this: unknown, event: string | symbol, ...args: unknown[]) => boolean;\r\n\r\nfunction install(): void {\r\n const ui = createNodeUI();\r\n if (!ui.active) {\r\n console.warn(`[nodeui] not attached (${ui.activationReason}).`);\r\n return;\r\n }\r\n const prefix = ui.config.path;\r\n const recorder = ui.middleware();\r\n const announced = new WeakSet<object>();\r\n const token = process.env.NODEUI_TOKEN;\r\n\r\n const underPrefix = (url: string | undefined): boolean => {\r\n let path = url ?? '/';\r\n try {\r\n path = new URL(path, 'http://localhost').pathname;\r\n } catch {\r\n // keep the raw value\r\n }\r\n return path === prefix || path.startsWith(`${prefix}/`);\r\n };\r\n\r\n for (const proto of [http.Server.prototype, https.Server.prototype]) {\r\n const original = proto.emit as unknown as Emit;\r\n proto.emit = function patched(this: http.Server, event: string | symbol, ...args: unknown[]) {\r\n if (event === 'request') {\r\n const [req, res] = args as [http.IncomingMessage, http.ServerResponse];\r\n if (underPrefix(req.url)) {\r\n void ui.handle(req, res);\r\n return true;\r\n }\r\n recorder(req, res, () => undefined);\r\n return runInRequestContext(req, () => original.call(this, event, ...args));\r\n }\r\n if (event === 'listening' && !announced.has(this)) {\r\n announced.add(this);\r\n const addr = this.address() as AddressInfo | string | null;\r\n if (addr && typeof addr === 'object') {\r\n const scheme = this instanceof https.Server ? 'https' : 'http';\r\n const query = token ? `?token=${encodeURIComponent(token)}` : '';\r\n const line = `[nodeui] console: ${scheme}://127.0.0.1:${addr.port}${prefix}/${query}`;\r\n process.stdout.write(`${line}\\n`);\r\n }\r\n }\r\n return original.call(this, event, ...args);\r\n } as typeof proto.emit;\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAMA,uBAAiB;AACjB,wBAAkB;AAElB,yBAAkD;AAElD,IAAM,QAAQ,uBAAO,IAAI,oBAAoB;AAC7C,IAAM,UAAU;AAEhB,IAAI,CAAC,QAAQ,KAAK,GAAG;AACnB,UAAQ,KAAK,IAAI;AACjB,MAAI;AACF,YAAQ;AAAA,EACV,SAAS,KAAK;AAEZ,YAAQ,KAAK,2BAA2B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,EAC5F;AACF;AAIA,SAAS,UAAgB;AACvB,QAAM,SAAK,iCAAa;AACxB,MAAI,CAAC,GAAG,QAAQ;AACd,YAAQ,KAAK,0BAA0B,GAAG,gBAAgB,IAAI;AAC9D;AAAA,EACF;AACA,QAAM,SAAS,GAAG,OAAO;AACzB,QAAM,WAAW,GAAG,WAAW;AAC/B,QAAM,YAAY,oBAAI,QAAgB;AACtC,QAAM,QAAQ,QAAQ,IAAI;AAE1B,QAAM,cAAc,CAAC,QAAqC;AACxD,QAAI,OAAO,OAAO;AAClB,QAAI;AACF,aAAO,IAAI,IAAI,MAAM,kBAAkB,EAAE;AAAA,IAC3C,QAAQ;AAAA,IAER;AACA,WAAO,SAAS,UAAU,KAAK,WAAW,GAAG,MAAM,GAAG;AAAA,EACxD;AAEA,aAAW,SAAS,CAAC,iBAAAA,QAAK,OAAO,WAAW,kBAAAC,QAAM,OAAO,SAAS,GAAG;AACnE,UAAM,WAAW,MAAM;AACvB,UAAM,OAAO,SAAS,QAA2B,UAA2B,MAAiB;AAC3F,UAAI,UAAU,WAAW;AACvB,cAAM,CAAC,KAAK,GAAG,IAAI;AACnB,YAAI,YAAY,IAAI,GAAG,GAAG;AACxB,eAAK,GAAG,OAAO,KAAK,GAAG;AACvB,iBAAO;AAAA,QACT;AACA,iBAAS,KAAK,KAAK,MAAM,MAAS;AAClC,mBAAO,wCAAoB,KAAK,MAAM,SAAS,KAAK,MAAM,OAAO,GAAG,IAAI,CAAC;AAAA,MAC3E;AACA,UAAI,UAAU,eAAe,CAAC,UAAU,IAAI,IAAI,GAAG;AACjD,kBAAU,IAAI,IAAI;AAClB,cAAM,OAAO,KAAK,QAAQ;AAC1B,YAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,gBAAM,SAAS,gBAAgB,kBAAAA,QAAM,SAAS,UAAU;AACxD,gBAAM,QAAQ,QAAQ,UAAU,mBAAmB,KAAK,CAAC,KAAK;AAC9D,gBAAM,OAAO,qBAAqB,MAAM,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI,KAAK;AACnF,kBAAQ,OAAO,MAAM,GAAG,IAAI;AAAA,CAAI;AAAA,QAClC;AAAA,MACF;AACA,aAAO,SAAS,KAAK,MAAM,OAAO,GAAG,IAAI;AAAA,IAC3C;AAAA,EACF;AACF;","names":["http","https"]}
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
- {
2
- "name": "@singhak/nodeui-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "@singhak/nodeui-cli",
3
+ "version": "0.5.0",
4
+ "description": "NodeUI command line: attach the console to any Node app without code changes, a multi-service dashboard, and an MCP server for AI agents.",
5
+ "license": "Apache-2.0",
6
+ "bin": {
7
+ "nodeui": "dist/cli.cjs"
8
+ },
9
+ "main": "./dist/index.cjs",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.cjs"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "keywords": [
22
+ "cli",
23
+ "npx",
24
+ "developer-tools",
25
+ "observability",
26
+ "debugging",
27
+ "mcp",
28
+ "nodejs"
29
+ ],
30
+ "engines": {
31
+ "node": ">=18.0.0"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/Singhak/nodeui.git"
39
+ },
40
+ "homepage": "https://github.com/Singhak/nodeui#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/Singhak/nodeui/issues"
43
+ },
44
+ "scripts": {
45
+ "prepublishOnly": "npm run build",
46
+ "build": "tsup",
47
+ "typecheck": "tsc -p tsconfig.typecheck.json",
48
+ "test": "vitest run"
49
+ },
50
+ "dependencies": {
51
+ "@singhak/nodeui-core": "^0.5.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^22.19.0",
55
+ "tsup": "^8.5.0",
56
+ "typescript": "^5.9.3",
57
+ "vitest": "^3.2.4"
58
+ },
59
+ "author": "Anil Kumar",
60
+ "type": "commonjs"
61
+ }