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

86 lines (85 loc) • 3.31 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/. /** * Shared skeleton for {@link SessionLogHarness} providers (§4.6 dedup). * * Holds only what the concrete providers genuinely share: safe stat'ing, * recursive directory walking, the mtime-filtered file→summary listing loop, * and conditional-spread assembly of * {@link SessionSummary} refs stamped with the provider's runtime name. * Everything platform-specific — file layouts, metadata peeking, SQLite * stores, message flattening — stays in the subclasses. */ import fs from "node:fs"; import path from "node:path"; export class AbstractSessionLogProvider { isAvailable() { return fs.existsSync(this.availabilityRoot()); } /** `fs.statSync` that returns `undefined` instead of throwing. */ statSafe(target) { try { return fs.statSync(target); } catch { return undefined; } } /** Recursively yield files under `dir` whose basename passes `matches`. */ *walkFiles(dir, matches) { try { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) yield* this.walkFiles(full, matches); else if (matches(entry.name)) yield full; } } catch { // permission errors etc. } } /** * Assemble a {@link SessionSummary} stamped with this provider's name. * Timestamps are included whenever defined (0 is valid); `projectHint` / * `title` only when non-empty, so absent metadata stays absent. */ sessionRef(input) { return { harness: this.name, sessionId: input.sessionId, filePath: input.filePath, ...(input.startedAt !== undefined ? { startedAt: input.startedAt } : {}), ...(input.endedAt !== undefined ? { endedAt: input.endedAt } : {}), ...(input.projectHint ? { projectHint: input.projectHint } : {}), ...(input.title ? { title: input.title } : {}), }; } /** * The shared listing loop: enumerate candidate session files, drop the * unstat'able and the ones older than `sinceMs`, summarize the rest, and * sort newest-ended first. An enumeration failure (root missing or * unreadable) returns what was collected so far rather than throwing. */ listSessionsFromFiles(input) { const summaries = []; try { for (const filePath of input.enumerate()) { const stat = this.statSafe(filePath); if (!stat) continue; if (stat.mtimeMs < input.sinceMs) continue; const summary = input.summarize(filePath, stat); if (summary) summaries.push(summary); } } catch { // Root missing or unreadable — return what we have. } return summaries.sort((a, b) => (b.endedAt ?? 0) - (a.endedAt ?? 0)); } }