UNPKG

akm-cli

Version:

akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur

83 lines (82 loc) • 3.81 kB
// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. /** * `akm metrics --format html` — the self-contained dashboard. * * The page carries the window's data in a JSON island * (`<script type="application/json" id="akm-data">`) and renders every panel * client-side from it, so the viewer's filters re-aggregate without re-running * the command. The template is `src/assets/templates/html/metrics.html`. * * Determinism: nothing here reads the clock. `%%GENERATED_AT%%` is * `window.until`, so identical input gives byte-identical output. */ import { escapeHtml, isoTimeTag, renderHtml, resolveTemplatePath } from "../../output/html-render.js"; import { pkgVersion } from "../../version.js"; import { buildEchartsTag } from "../health/html-report.js"; import { isMetricsResult } from "./report-view.js"; const esc = escapeHtml; /** Most `rows.usage` entries the page carries; older rows are dropped with a note. */ export const MAX_HTML_USAGE_ROWS = 50_000; /** * The result as the page receives it: `rows` always present (empty when the * caller left them out), usage rows cut to the most recent * {@link MAX_HTML_USAGE_ROWS}, and the query text of per-hit rows (those with a * ref) moved into one `rows.queries` table so a query that surfaced many assets * is written once; the page puts it back on load. Never mutates the input. */ function toPageData(result) { const notes = [...result.notes]; let usage = result.rows?.usage ?? []; if (usage.length > MAX_HTML_USAGE_ROWS) { // ISO timestamps sort lexically; id breaks ties so the cut is stable. usage = [...usage].sort((a, b) => (a.at < b.at ? -1 : a.at > b.at ? 1 : a.id - b.id)).slice(-MAX_HTML_USAGE_ROWS); notes.push(`The dashboard shows the most recent ${MAX_HTML_USAGE_ROWS} usage rows of this window; older rows were left out of the page (totals in the JSON output are complete).`); } const queries = []; const queryIndex = new Map(); const pageUsage = usage.map((row) => { if (row.ref === undefined || row.query === undefined) return row; const { query, ...rest } = row; let q = queryIndex.get(query); if (q === undefined) { q = queries.length; queries.push(query); queryIndex.set(query, q); } return { ...rest, q }; }); return { ...result, notes, rows: { usage: pageUsage, llm: result.rows?.llm ?? [], queries } }; } /** * Serialize for an inline `<script type="application/json">`. Escaping `<` is * enough: it stops `</script>` and `<!--` in a query or reason from ending the * island, and `JSON.parse` turns `<` back into `<`. */ function islandJson(value) { return JSON.stringify(value).replaceAll("<", "\\u003c"); } export function buildMetricsHtmlReplacements(result) { const { filters, window: win } = result; return { "%%ECHARTS_TAG%%": buildEchartsTag(), "%%REPORT_TITLE%%": "akm metrics", "%%WINDOW_HTML%%": `${isoTimeTag(win.since)} &rarr; ${isoTimeTag(win.until)}`, "%%FILTERS_HTML%%": `source: ${esc(filters.source)}`, "%%GENERATED_AT%%": esc(win.until), "%%AKM_VERSION%%": esc(pkgVersion), "%%DATA_JSON%%": islandJson(toPageData(result)), }; } /** * `--format html` renderer for `akm metrics`, called directly from * `cli/shared.ts`. Returns `null` for a payload that is not a metrics result so * the generic renderer takes over. */ export function renderMetricsHtml(result) { if (!isMetricsResult(result)) return null; return renderHtml(resolveTemplatePath("metrics"), buildMetricsHtmlReplacements(result)); }