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
81 lines (80 loc) • 4 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/.
/** Usage rows (search / show / curate / feedback) in the window, oldest first. */
export function listUsageEventRows(db, filter) {
const sourceSql = filter.source === undefined ? "" : " AND source = ?";
const sourceParams = filter.source === undefined ? [] : [filter.source];
// `created_at` has whole-second resolution, so a row written during the
// bound's own second (e.g. `--until` defaulting to now) is still inside the
// half-open window: round a fractional bound up to the next second.
const untilMs = Date.parse(filter.untilIso);
const untilBound = new Date(Math.ceil(untilMs / 1000) * 1000).toISOString();
return db
.prepare(`SELECT id, event_type, query, entry_id, entry_ref, signal, metadata, source, created_at
FROM usage_events
WHERE datetime(created_at) >= datetime(?) AND datetime(created_at) < datetime(?)${sourceSql}
ORDER BY datetime(created_at), id`)
.all(filter.sinceIso, untilBound, ...sourceParams);
}
/**
* `select` events (a show within 60 s of a search that returned the ref) in the
* window. The events stream stores the ref as the user typed it (often
* bundle-less), so the caller resolves it to the durable ref.
*/
export function listSelectEvents(db, sinceIso, untilIso) {
return db
.prepare(`SELECT ts, ref FROM events
WHERE event_type = 'select' AND ref IS NOT NULL AND ts >= ? AND ts < ?
ORDER BY id`)
.all(sinceIso, untilIso);
}
/** `index_completed` events (one per `akm index` run) in the window, oldest first. */
export function listIndexCompletedEvents(db, sinceIso, untilIso) {
return db
.prepare(`SELECT ts, metadata_json FROM events
WHERE event_type = 'index_completed' AND ts >= ? AND ts < ?
ORDER BY id`)
.all(sinceIso, untilIso);
}
/** The `limit` assets with the lowest `outcome_score`, ties broken by ref. */
export function listLowestOutcomeAssets(db, limit) {
return db
.prepare(`SELECT asset_ref, last_retrieved_at, retrieval_count, expected_retrieval_rate,
negative_feedback_count, accepted_change_count, outcome_score, updated_at
FROM asset_outcome
ORDER BY outcome_score ASC, asset_ref ASC LIMIT ?`)
.all(limit);
}
/** Proposal counts by status, for proposals last updated in the window. */
export function countProposalsByStatus(db, sinceIso, untilIso) {
const rows = db
.prepare(`SELECT status, COUNT(*) AS n FROM proposals
WHERE updated_at >= ? AND updated_at < ?
GROUP BY status ORDER BY status`)
.all(sinceIso, untilIso);
return Object.fromEntries(rows.map((row) => [row.status, row.n]));
}
/**
* Workflow runs created in the window by status, and the tokens their unit
* attempts spent by model. Attempts, not the unit projection, so a retried
* unit keeps the tokens of every attempt.
*/
export function summarizeWorkflowRuns(db, sinceIso, untilIso) {
const statusRows = db
.prepare(`SELECT status, COUNT(*) AS n FROM workflow_runs
WHERE created_at >= ? AND created_at < ? GROUP BY status ORDER BY status`)
.all(sinceIso, untilIso);
const modelRows = db
.prepare(`SELECT COALESCE(model, 'unattributed') AS model, SUM(tokens) AS tokens
FROM workflow_run_unit_attempts
WHERE tokens IS NOT NULL AND started_at >= ? AND started_at < ?
GROUP BY COALESCE(model, 'unattributed') ORDER BY model`)
.all(sinceIso, untilIso);
return {
runs: statusRows.reduce((sum, row) => sum + row.n, 0),
byStatus: Object.fromEntries(statusRows.map((row) => [row.status, row.n])),
tokens: modelRows.reduce((sum, row) => sum + row.tokens, 0),
byModel: Object.fromEntries(modelRows.map((row) => [row.model, row.tokens])),
};
}