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
95 lines (94 loc) • 3.91 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/.
/**
* Programmatic event listing behind `akm log` (#204).
*
* Programmatic surface — the CLI dispatcher in `src/cli.ts` registers the
* `log` leaf command that delegates here. Returns a JSON envelope shaped by
* `src/output/shapes.ts` so the output flows through the same shape and
* text-renderer pipeline as the rest of the CLI (no silent
* `JSON.stringify` fallback).
*
* 0.9.0 CLI overhaul (S3): `akmEventsTail` (and the `log tail` command it
* backed) was dropped — a foreground polling daemon in a one-shot CLI. The
* lower-level `tailEvents` poller it wrapped (src/core/events.ts) had no
* other caller and was removed with it.
*/
import { makeBundleRef, parseBundleRef } from "../core/asset/asset-ref.js";
import { loadConfig } from "../core/config/config.js";
import { UsageError } from "../core/errors.js";
import { readEvents } from "../core/events.js";
import { parseSinceToIso } from "../core/time.js";
/**
* Parse `--since` accepting either an opaque row cursor (`@offset:<int>`) for
* cross-process resumption, or a timestamp / epoch-ms.
* Returns one of `{ sinceOffset }` or `{ since }`.
*/
function parseSinceFlag(since) {
if (since === undefined)
return {};
const trimmed = since.trim();
if (!trimmed) {
throw new UsageError("--since cannot be empty.", "INVALID_FLAG_VALUE");
}
if (trimmed.startsWith("@offset:")) {
const raw = trimmed.slice("@offset:".length);
const value = Number.parseInt(raw, 10);
if (Number.isNaN(value) || value < 0) {
throw new UsageError(`Invalid --since offset: "${since}". Expected @offset:<non-negative integer>.`, "INVALID_FLAG_VALUE");
}
return { sinceOffset: value };
}
return { since: parseSinceToIso(trimmed) };
}
function validateRef(ref) {
if (ref === undefined)
return undefined;
const trimmed = ref.trim();
if (!trimmed) {
throw new UsageError("--ref cannot be empty.", "INVALID_FLAG_VALUE");
}
const parsed = parseBundleRef(trimmed);
// Deliberately shallow qualification: a bare conceptId is assumed to mean
// the default bundle, without the full priority-walk `resolveRef` performs —
// event refs are recorded fully qualified, so the default is the only
// spelling a short --ref can usefully mean here.
return makeBundleRef(parsed.bundle ?? loadConfig().defaultBundle, parsed.conceptId);
}
function validateRunId(run) {
if (run === undefined)
return undefined;
const trimmed = run.trim();
if (!trimmed) {
throw new UsageError("--run cannot be empty.", "INVALID_FLAG_VALUE");
}
return trimmed;
}
export function akmEventsList(options = {}) {
const ref = validateRef(options.ref);
const run = validateRunId(options.run);
const parsed = parseSinceFlag(options.since);
const result = readEvents({
since: parsed.since,
sinceOffset: parsed.sinceOffset,
type: options.type,
ref,
runId: run,
excludeTags: options.excludeTags,
includeTags: options.includeTags,
limit: options.limit,
}, options.ctx);
return {
schemaVersion: 1,
totalCount: result.events.length,
...(ref !== undefined ? { ref } : {}),
...(options.type !== undefined ? { type: options.type } : {}),
...(parsed.since !== undefined ? { since: parsed.since } : {}),
...(parsed.sinceOffset !== undefined ? { sinceOffset: parsed.sinceOffset } : {}),
...(options.limit !== undefined ? { limit: options.limit } : {}),
...(run !== undefined ? { run } : {}),
nextOffset: result.nextOffset,
events: result.events,
};
}