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

158 lines (157 loc) • 6.92 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/. /** * CLI surface for `akm proposal extract` (formerly the top-level `akm * extract`, moved under the `proposal` group in the 0.9 CLI overhaul, S8 — * see src/commands/proposal/proposal-cli.ts for its registration). * * Examples: * akm proposal extract --type claude --session-id <id> * akm proposal extract --type claude --since 24h * akm proposal extract --type opencode --since 7d --dry-run * akm proposal extract --type codex --since 24h * akm proposal extract --auto # iterate all available harnesses * akm proposal extract --type claude --location /custom/path --session-id <id> * * Output is the AkmExtractResult JSON envelope (or an aggregated one when * `--auto` runs multiple harnesses). */ import { getStringArg } from "../../cli/parse-args.js"; import { defineJsonCommand, EXIT_CODES, output } from "../../cli/shared.js"; import { loadConfig } from "../../core/config/config.js"; import { UsageError } from "../../core/errors.js"; import { getAvailableHarnesses } from "../../integrations/session-logs/index.js"; import { akmExtract, resolveStandaloneExtractPlan } from "./extract.js"; export const extractCommand = defineJsonCommand({ meta: { name: "extract", description: "Extract durable insights from native session files (claude, codex, opencode) and queue them as proposals.", }, args: { type: { type: "string", description: "Harness name (claude, codex, opencode). Required unless --auto.", }, "session-id": { type: "string", description: "Process only this session ID. When absent, discover sessions via --since.", }, location: { type: "string", description: "Override the harness's default session-discovery location.", }, since: { type: "string", description: "Discovery cutoff. ISO timestamp or duration (24h, 7d, 30m). Default 24h.", }, auto: { type: "boolean", description: "Iterate every available harness with default --since. Mutually exclusive with --type.", default: false, }, "dry-run": { type: "boolean", description: "Show candidates without queuing proposals.", default: false, }, force: { type: "boolean", description: "Re-process sessions even if they were already extracted and have no new events. Default: skip already-seen sessions.", default: false, }, "timeout-ms": { type: "string", description: "Per-session LLM timeout in ms (default 600000).", }, engine: { type: "string", description: "Named LLM engine for this invocation. Mutually exclusive with --strategy.", }, strategy: { type: "string", description: "Improve strategy supplying extract behavior and engine. Mutually exclusive with --engine.", }, }, async run({ args }) { const type = getStringArg(args, "type") ?? ""; const sessionId = getStringArg(args, "session-id") ?? ""; const location = getStringArg(args, "location") ?? ""; const since = getStringArg(args, "since") ?? ""; const auto = args.auto === true; const dryRun = args["dry-run"] === true; const force = args.force === true; const engine = getStringArg(args, "engine"); const strategy = getStringArg(args, "strategy"); const timeoutMs = typeof args["timeout-ms"] === "string" && args["timeout-ms"] !== "" ? Number.parseInt(args["timeout-ms"], 10) : undefined; if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) { throw new UsageError(`--timeout-ms must be a positive integer (got "${args["timeout-ms"]}").`, "INVALID_FLAG_VALUE"); } if (engine && strategy) { throw new UsageError("--engine and --strategy are mutually exclusive. Pick one.", "INVALID_FLAG_VALUE"); } if (auto && type) { throw new UsageError("--auto and --type are mutually exclusive. Pick one.", "INVALID_FLAG_VALUE"); } if (!auto && !type) { throw new UsageError("--type is required (or pass --auto to try every available harness).", "MISSING_REQUIRED_ARGUMENT"); } const config = loadConfig(); const resolvedPlan = resolveStandaloneExtractPlan(config, { ...(engine ? { engine } : {}), ...(strategy ? { strategy } : {}), ...(timeoutMs !== undefined ? { timeoutMs } : {}), }); const commonOptions = Object.freeze({ ...(sessionId ? { sessionId } : {}), ...(location ? { location } : {}), ...(since ? { since } : {}), dryRun, force, config, resolvedPlan, }); if (auto) { const harnesses = getAvailableHarnesses(); if (harnesses.length === 0) { output("extract", { schemaVersion: 1, ok: false, shape: "extract-auto-result", warnings: ["no available harnesses found on this machine"], results: [], }); return; } const results = []; for (const h of harnesses) { const result = await akmExtract({ type: h.name, ...commonOptions }); results.push(result); } const ok = results.every((r) => r.ok); const totalProposals = results.reduce((sum, r) => sum + r.proposals.length, 0); output("extract", { schemaVersion: 1, ok, shape: "extract-auto-result", dryRun, harnessesProcessed: results.length, totalProposals, results, }); // Signal failure to callers/schedulers when every harness failed. output() // only renders; without this a scheduled run exits 0 on a total failure // and the breakage is invisible to exit-code monitoring. process.exitCode // (not process.exit) lets stdout flush before the process exits. if (!ok) process.exitCode = EXIT_CODES.GENERAL; return; } const result = await akmExtract({ type, ...commonOptions }); output("extract", result); if (!result.ok) process.exitCode = EXIT_CODES.GENERAL; }, });