SPB Git

spb/khaelor Public

KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.

TypeScript 82.9% HTML 14.9% CSS 1.1% JavaScript 0.7%
5.2 KB · 188 lines typescript
Raw Blame History
1/**2 * KHAELOR3 * File: src/cli/args.ts4 * Description: Dependency-free argv parsing for the khaelor entry point (--model, --print, --debug, --version, --help).5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import { readFileSync } from "node:fs";11import { dirname, join } from "node:path";12import { fileURLToPath } from "node:url";1314/** Version read from package.json (dist/cli/ and src/cli/ are both two levels below it). */15function readPackageVersion(): string {16  try {17    const here = dirname(fileURLToPath(import.meta.url));18    const raw = readFileSync(join(here, "..", "..", "package.json"), "utf8");19    const pkg = JSON.parse(raw) as { version?: string };20    return pkg.version ?? "0.0.0";21  } catch {22    return "0.0.0";23  }24}2526export const KHAELOR_VERSION = readPackageVersion();2728/** Parsed CLI arguments. `errors` is non-empty when parsing failed. */29export interface ParsedArgs {30  help: boolean;31  version: boolean;32  debug: boolean;33  /** Non-interactive mode: run one agent turn, print the final text, exit. */34  print: string | null;35  /** Resume an existing session by id (interactive mode). */36  resume: string | null;37  model: string | null;38  auxModel: string | null;39  thinking: string | null;40  maxOutputTokens: number | null;41  errors: string[];42}4344const HELP_TEXT = `khaelor — a terminal-native autonomous engineering agent4546Usage47  khaelor                       start the interactive terminal agent48  khaelor --print "<prompt>"    run one agent turn without the TUI, print the answer, exit49  khaelor --resume <session-id> resume a previous session5051Options52  --model <id>              Anthropic model id for this run53  --aux-model <id>          cheaper model used for context compaction54  --thinking <mode>         off | adaptive | always55  --max-output-tokens <n>   output token ceiling per model response56  -p, --print <prompt>      non-interactive single turn (mirrors claude -p)57  --resume <session-id>     resume a session by id58  --debug                   write developer logs to ~/.khaelor/logs/59  -v, --version             print the version and exit60  -h, --help                show this help6162Environment63  ANTHROPIC_API_KEY         required — your Anthropic API key64`;6566export function helpText(): string {67  return HELP_TEXT;68}6970/**71 * The actionable message shown when no API key is configured. Never echoes72 * any secret material — there is nothing to echo.73 */74export function missingApiKeyMessage(): string {75  return [76    "khaelor needs an Anthropic API key.",77    "",78    "Set the ANTHROPIC_API_KEY environment variable and try again:",79    "",80    "  export ANTHROPIC_API_KEY=<your key>",81    "  khaelor",82    "",83    "Keys are created at https://console.anthropic.com/ — KHAELOR never",84    "stores or logs the key; it is read from the environment on each start.",85  ].join("\n");86}8788/** Hand-rolled, zero-dependency argv parsing (ARCHITECTURE.md §12.1). */89export function parseArgs(argv: readonly string[]): ParsedArgs {90  const parsed: ParsedArgs = {91    help: false,92    version: false,93    debug: false,94    print: null,95    resume: null,96    model: null,97    auxModel: null,98    thinking: null,99    maxOutputTokens: null,100    errors: [],101  };102103  const takeValue = (flag: string, index: number): string | null => {104    const value = argv[index + 1];105    if (value === undefined || value.startsWith("--")) {106      parsed.errors.push(`${flag} requires a value`);107      return null;108    }109    return value;110  };111112  for (let i = 0; i < argv.length; i += 1) {113    const arg = argv[i] as string;114    switch (arg) {115      case "-h":116      case "--help":117        parsed.help = true;118        break;119      case "-v":120      case "--version":121        parsed.version = true;122        break;123      case "--debug":124        parsed.debug = true;125        break;126      case "-p":127      case "--print": {128        const value = takeValue("--print", i);129        if (value !== null) {130          parsed.print = value;131          i += 1;132        }133        break;134      }135      case "--resume": {136        const value = takeValue("--resume", i);137        if (value !== null) {138          parsed.resume = value;139          i += 1;140        }141        break;142      }143      case "--model": {144        const value = takeValue("--model", i);145        if (value !== null) {146          parsed.model = value;147          i += 1;148        }149        break;150      }151      case "--aux-model": {152        const value = takeValue("--aux-model", i);153        if (value !== null) {154          parsed.auxModel = value;155          i += 1;156        }157        break;158      }159      case "--thinking": {160        const value = takeValue("--thinking", i);161        if (value !== null) {162          parsed.thinking = value;163          i += 1;164        }165        break;166      }167      case "--max-output-tokens": {168        const value = takeValue("--max-output-tokens", i);169        if (value !== null) {170          const n = Number.parseInt(value, 10);171          if (!Number.isInteger(n) || n <= 0) {172            parsed.errors.push("--max-output-tokens must be a positive integer");173          } else {174            parsed.maxOutputTokens = n;175          }176          i += 1;177        }178        break;179      }180      default:181        parsed.errors.push(`unknown option: ${arg}`);182        break;183    }184  }185186  return parsed;187}188