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

169 lines (168 loc) • 5.48 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/. /** * Module-level quiet/verbose flags and optional file sink for stderr output. * * `quiet` is controlled by the CLI `--quiet`/`-q` flag. * `verbose` is controlled by the CLI `--verbose` flag, with `AKM_VERBOSE` * (env var) winning regardless: env > flag > default (false). * * Call `setLogFile(path)` to tee all warn/error/info output to a file in * addition to stderr. The file sink is written even when `--quiet` suppresses * console output, so logs remain available for post-run inspection. */ import fs from "node:fs"; import path from "node:path"; let quiet = false; let verbose = false; let logFilePath; let sinkOverride; /** TEST-ONLY. Swap the output sink; pass undefined to restore real output. */ export function _setWarnSinkForTests(fake) { sinkOverride = fake; } export function setQuiet(value) { quiet = value; } /** * Reset the quiet flag to false. * Intended for test teardown to prevent quiet state from leaking between tests. */ export function resetQuiet() { quiet = false; } export function isQuiet() { return quiet; } /** * Set the verbose flag from a CLI flag. The `AKM_VERBOSE` env var, when set, * always wins regardless of this flag (env > flag > default). */ export function setVerbose(value) { verbose = value; } /** * Reset the verbose flag to false. Intended for test teardown so verbose * state does not leak between tests. */ export function resetVerbose() { verbose = false; } /** * Returns true when verbose output is requested. * * Precedence: `AKM_VERBOSE` env var (when truthy) > `setVerbose(true)` > false. * Truthy matches `1`, `true`, `yes`, `on` (case-insensitive). The values * `0`, `false`, `no`, `off` hard-disable verbose even if the flag is set, * so operators can override per-invocation. Any other value (including * empty string) is treated as "not set" and falls through to the flag. */ export function isVerbose() { const env = process.env.AKM_VERBOSE?.trim().toLowerCase(); if (env === "1" || env === "true" || env === "yes" || env === "on") return true; if (env === "0" || env === "false" || env === "no" || env === "off") return false; return verbose; } /** * Direct all warn/error/info output to `filePath` in addition to stderr. * The directory is created if it does not exist. Pass `undefined` to disable. * The file is written even when `--quiet` suppresses console output. */ export function setLogFile(filePath) { logFilePath = filePath; fs.mkdirSync(path.dirname(filePath), { recursive: true }); } export function clearLogFile() { logFilePath = undefined; } function appendToLogFile(level, args) { if (!logFilePath) return; const ts = new Date().toISOString(); const msg = args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" "); try { fs.appendFileSync(logFilePath, `[${ts}] [${level}] ${msg}\n`); } catch (e) { // Log file write failed — emit directly to stderr so the message is not lost. process.stderr.write(`[akm:warn] log-file write failed (${logFilePath}): ${e}\n`); process.stderr.write(`[${ts}] [${level}] ${msg}\n`); } } /** * Emit an info/progress line to stderr unless --quiet is active. * Always written to the log file if one is active. * Use for progress counters and status lines (replaces console.error used for progress). */ export function info(...args) { if (sinkOverride) { sinkOverride("info", args); return; } appendToLogFile("INFO", args); if (!quiet) { console.warn(...args); } } /** * Emit a warning to stderr unless --quiet is active. * Always written to the log file if one is active. * Drop-in replacement for console.warn() across the codebase. */ export function warn(...args) { if (sinkOverride) { sinkOverride("warn", args); return; } appendToLogFile("WARN", args); if (!quiet) { console.warn(...args); } } const warnedOnceKeys = new Set(); /** * `warn()` at most once per process for a given `key`. For diagnostics that * would otherwise repeat on every read of the same bad row or value. */ export function warnOnce(key, ...args) { if (warnedOnceKeys.has(key)) return; warnedOnceKeys.add(key); warn(...args); } /** TEST-ONLY. Forget every `warnOnce` key so a test can re-trigger a warning. */ export function _resetWarnOnceForTests() { warnedOnceKeys.clear(); } /** * Emit an error to stderr unless --quiet is active. * Always written to the log file if one is active. * Drop-in replacement for console.error() used for diagnostic failures. */ export function error(...args) { if (sinkOverride) { sinkOverride("error", args); return; } appendToLogFile("ERROR", args); if (!quiet) { console.error(...args); } } /** * Emit a warning only when verbose output is requested. Use for noisy * per-item diagnostics that should be replaced by a one-line summary at * default verbosity (e.g. registry-content workflow validation errors). */ export function warnVerbose(...args) { if (sinkOverride) { sinkOverride("warnVerbose", args); return; } if (isVerbose()) { warn(...args); } }