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
126 lines (125 loc) • 5.1 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/.
/**
* Process-level output mode singleton.
*
* Output mode (format + detail + shape) is parsed once at startup from
* `process.argv` and the persisted user config. All subsequent `output()`
* calls read from this in-memory singleton instead of re-scanning argv and
* re-loading config on every call.
*
* Initialized from `cli.ts` before `runCli` dispatches the command (`cli.ts`
* drives citty's `runCommand` directly rather than `runMain` — see the
* top-level driver comment there).
*/
import { UsageError } from "../core/errors.js";
const OUTPUT_FORMATS = ["json", "yaml", "text", "jsonl", "md", "html"];
const DETAIL_LEVELS = ["brief", "normal", "full"];
const SHAPE_MODES = ["human", "agent", "summary"];
function parseOutputFormat(value) {
if (!value)
return undefined;
if (OUTPUT_FORMATS.includes(value))
return value;
throw new UsageError(`Invalid value for --format: ${value}. Expected one of: ${OUTPUT_FORMATS.join("|")}`, "INVALID_FORMAT_VALUE");
}
export function parseDetailLevel(value) {
if (!value)
return undefined;
if (DETAIL_LEVELS.includes(value))
return value;
throw new UsageError(`Invalid value for --detail: ${value}. Expected one of: ${DETAIL_LEVELS.join("|")}`, "INVALID_DETAIL_VALUE");
}
function parseShapeMode(value) {
if (!value)
return undefined;
if (SHAPE_MODES.includes(value))
return value;
throw new UsageError(`Invalid value for --shape: ${value}. Expected one of: ${SHAPE_MODES.join("|")}`, "INVALID_SHAPE_VALUE");
}
export function parseFlagValue(argv, flag) {
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (arg === "--")
break;
if (arg === flag)
return argv[i + 1];
if (arg.startsWith(`${flag}=`))
return arg.slice(flag.length + 1);
}
return undefined;
}
export function hasBooleanFlag(argv, flag) {
for (const arg of argv) {
if (arg === "--")
return false;
if (arg === flag || arg === `${flag}=true`)
return true;
}
return false;
}
/**
* Read a hyphenated arg out of citty's parsed `args` object.
*
* Verified live: citty DOES auto-camelise a DECLARED hyphenated arg — for
* `"max-pages": { type: "string" }` in a command's `args`, citty's parsed
* object answers both `args["max-pages"]` and `args.maxPages` (a `Proxy`
* falls back through `camelCase`/`kebabCase`, and `--no-<flag>` negation is
* handled the same way for booleans). What citty's own TS types do NOT give
* callers is a statically-typed handle on that mapping, and command handlers
* here type `args` as `unknown` at the boundary — so every read site still
* needs a cast. This helper encapsulates that cast; it exists for typing
* convenience, not to work around a citty parsing limitation.
*/
export function getHyphenatedArg(args, key) {
if (typeof args !== "object" || args === null)
return undefined;
const value = args[key];
return value === undefined ? undefined : value;
}
/** Boolean variant of {@link getHyphenatedArg} for `--<flag>` switches. */
export function getHyphenatedBoolean(args, key) {
return Boolean(getHyphenatedArg(args, key));
}
/**
* Resolve output mode from a synthetic argv array and config defaults.
* Pure function — no IO. Suitable for unit tests.
*/
export function resolveOutputMode(argv, defaults = {}) {
const format = parseOutputFormat(parseFlagValue(argv, "--format")) ?? defaults?.format ?? "json";
const rawDetail = parseFlagValue(argv, "--detail");
const rawShape = parseFlagValue(argv, "--shape");
// `--detail` is verbosity only (brief|normal|full); the projection presets
// (`summary`/`agent`) and the `--for-agent` boolean were removed in 0.9.0 —
// use `--shape`. Unknown `--detail` values fall through to the default.
const detail = parseDetailLevel(rawDetail) ?? defaults?.detail ?? "brief";
const shape = parseShapeMode(rawShape) ?? "human";
const outputPath = parseFlagValue(argv, "--output");
return { format, detail, shape, ...(outputPath ? { outputPath } : {}) };
}
let _mode;
/**
* Initialize the process-level output mode. Must be called once at startup
* before any code calls `getOutputMode()`. Subsequent calls overwrite.
*/
export function initOutputMode(argv, defaults = {}) {
_mode = resolveOutputMode(argv, defaults);
return _mode;
}
/**
* Read the process-level output mode. Throws if `initOutputMode()` was not
* called first — that is a programmer error, not a runtime condition.
*/
export function getOutputMode() {
if (!_mode) {
throw new Error("OutputMode not initialized. Call initOutputMode() before getOutputMode().");
}
return _mode;
}
/**
* Reset the singleton. Test-only utility.
*/
export function resetOutputMode() {
_mode = undefined;
}