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
244 lines (243 loc) • 10.1 kB
JavaScript
// 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/.
/** Lists are cut to this many rows at `--detail brief`; `normal`/`full` show what the top-20 cap kept. */
export const BRIEF_LIST_LIMIT = 5;
/** Narrow an unknown envelope to an `AkmMetricsResult`; renderers return `null` (generic fallback) otherwise. */
export function isMetricsResult(value) {
if (value === null || typeof value !== "object")
return false;
const r = value;
return r.schemaVersion === 1 && !!r.window && !!r.usage && !!r.feedback && !!r.llm;
}
const int = (n) => String(n);
const dash = (v) => (v === undefined || v === "" ? "-" : v);
/** A fraction as a percentage; `null` (zero denominator) is `n/a`, never `NaN`. */
const pct = (n) => (n === null ? "n/a" : `${(n * 100).toFixed(1)}%`);
const ms = (n) => (n === null ? "n/a" : `${Math.round(n)} ms`);
const fixed = (n, digits) => (n === null ? "n/a" : n.toFixed(digits));
function sourceFacts(counts) {
const entries = Object.entries(counts);
return entries.length === 0 ? "-" : entries.map(([k, v]) => `${k}=${v}`).join(" ");
}
function llmRows(group) {
return Object.entries(group).map(([name, a]) => [
name,
int(a.calls),
int(a.failures),
int(a.promptTokens),
int(a.completionTokens),
int(a.totalTokens),
ms(a.totalDurationMs),
]);
}
const LLM_HEADERS = ["name", "calls", "failed", "prompt", "completion", "total", "time"];
export function buildMetricsView(r, detail) {
const brief = detail === "brief";
const cut = (items) => (brief ? items.slice(0, BRIEF_LIST_LIMIT) : items);
const { usage, feedback, utility, llm } = r;
const searches = usage.totals.searches;
const sections = [
{
title: "Usage",
facts: [
["searches", int(searches)],
["shows", int(usage.totals.shows)],
["curates", int(usage.totals.curates)],
["selects", int(usage.totals.selects)],
["select rate", pct(usage.selectRate)],
[
"zero-result searches",
`${usage.totals.zeroResultSearches} (${pct(searches === 0 ? null : usage.totals.zeroResultSearches / searches)})`,
],
["search median", ms(usage.searchMedianMs)],
["distinct assets", int(usage.totals.distinctAssets)],
["distinct queries", int(usage.totals.distinctQueries)],
["by source", sourceFacts(usage.bySource)],
],
tables: [
{
title: "Top assets",
headers: ["ref", "shows", "search hits", "selects", "+", "-", "last used"],
rows: cut(usage.topAssets).map((a) => [
a.ref,
int(a.shows),
int(a.searchHits),
int(a.selects),
int(a.positive),
int(a.negative),
dash(a.lastUsedAt),
]),
},
{
title: "Top queries",
headers: ["query", "count", "avg results", "last"],
rows: cut(usage.topQueries).map((q) => [q.query, int(q.count), fixed(q.avgResults, 1), q.lastAt]),
},
{
title: "Zero-result queries",
headers: ["query", "count", "last"],
rows: cut(usage.zeroResultQueries).map((q) => [q.query, int(q.count), q.lastAt]),
},
{
title: "Daily",
headers: ["day", "search", "show", "curate", "feedback"],
rows: brief
? []
: usage.daily.map((d) => [d.day, int(d.search), int(d.show), int(d.curate), int(d.feedback)]),
hideWhenEmpty: true,
},
],
},
{
title: "Feedback",
facts: [
["positive", int(feedback.totals.positive)],
["negative", int(feedback.totals.negative)],
],
tables: [
{
title: "By asset",
headers: ["ref", "+", "-", "valence", "last"],
rows: cut(feedback.byAsset).map((a) => [
a.ref,
int(a.positive),
int(a.negative),
fixed(a.valence, 2),
a.lastAt,
]),
},
{
title: "By tag",
headers: ["tag", "+", "-"],
rows: cut(Object.entries(feedback.byTag)).map(([tag, c]) => [tag, int(c.positive), int(c.negative)]),
hideWhenEmpty: true,
},
{
title: "Recent negative",
headers: ["ref", "at", "reason", "tags"],
rows: cut(feedback.recentNegative).map((n) => [n.ref, n.at, dash(n.reason), dash(n.tags?.join(", "))]),
},
],
},
{
title: "Utility",
facts: [
["scored assets", int(utility.count)],
["never used", int(utility.neverUsed)],
],
tables: [
{
title: "Histogram",
headers: ["bucket", "count"],
rows: utility.histogram.map((h) => [h.bucket, int(h.count)]),
hideWhenEmpty: true,
},
{ title: "Lowest", headers: utilityHeaders(), rows: cut(utility.lowest).map(utilityRow) },
{ title: "Highest", headers: utilityHeaders(), rows: cut(utility.highest).map(utilityRow) },
{
title: "Lowest outcome",
headers: ["ref", "outcome", "retrievals", "negative", "accepted changes"],
rows: cut(r.outcomes.lowestOutcome).map((o) => [
o.ref,
fixed(o.outcomeScore, 2),
int(o.retrievalCount),
int(o.negativeFeedbackCount),
int(o.acceptedChangeCount),
]),
hideWhenEmpty: true,
},
],
},
{
title: "LLM",
facts: [
["calls", int(llm.calls)],
["failed", int(llm.failures)],
["prompt tokens", int(llm.promptTokens)],
["completion tokens", int(llm.completionTokens)],
["reasoning tokens", int(llm.reasoningTokens)],
["total tokens", int(llm.totalTokens)],
["time", ms(llm.totalDurationMs)],
],
tables: [
{ title: "By engine", headers: LLM_HEADERS, rows: cut(llmRows(llm.byEngine)), hideWhenEmpty: true },
{ title: "By process", headers: LLM_HEADERS, rows: cut(llmRows(llm.byProcess)), hideWhenEmpty: true },
{ title: "By stage", headers: LLM_HEADERS, rows: cut(llmRows(llm.byStage)), hideWhenEmpty: true },
],
},
{
title: "Index",
facts: [
["runs", int(r.index.runs)],
["median time", ms(r.index.medianMs)],
],
tables: [
{
title: "Recent runs",
headers: ["at", "mode", "time"],
rows: brief ? [] : r.index.recent.map((i) => [i.at, i.mode, ms(i.totalMs)]),
hideWhenEmpty: true,
},
],
},
{
title: "Tasks",
facts: [
["runs", int(r.tasks.runs)],
["failed", int(r.tasks.failed)],
["fail rate", pct(r.tasks.failRate)],
],
tables: [
{
title: "By task",
headers: ["task", "runs", "failed", "median"],
rows: cut(r.tasks.byTask).map((t) => [t.taskId, int(t.runs), int(t.failed), ms(t.medianMs)]),
hideWhenEmpty: true,
},
],
},
{
title: "Proposals",
facts: [["by status", sourceFacts(r.proposals.byStatus)]],
tables: [
{
title: "Accept rate by source",
headers: ["source", "total", "accepted", "rejected", "pending", "accept rate"],
rows: r.proposals.acceptRateBySource.map((p) => [
p.source,
int(p.total),
int(p.accepted),
int(p.rejected),
int(p.pending),
pct(p.acceptRate),
]),
hideWhenEmpty: true,
},
],
},
{
title: "Workflows",
facts: [
["runs", int(r.workflows.runs)],
["by status", sourceFacts(r.workflows.byStatus)],
["tokens", int(r.workflows.tokens)],
["tokens by model", sourceFacts(r.workflows.byModel)],
],
tables: [],
},
];
const f = r.filters;
return {
window: `${r.window.since} to ${r.window.until}`,
filters: `source=${f.source}`,
sections,
notes: r.notes,
};
}
function utilityHeaders() {
return ["ref", "utility", "shows", "searches", "select rate", "last used"];
}
function utilityRow(u) {
return [u.ref, fixed(u.utility, 2), int(u.showCount), int(u.searchCount), pct(u.selectRate), dash(u.lastUsedAt)];
}