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%
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 /** Phase-gate rigor: strict | auto | off (v2 §1). */42 gate: string | null;43 /** Print the detected terminal capability report and exit (TUI v2 §7). */44 doctorTui: boolean;45 /** Show the full splash even on repeat launches (TUI v2 §2). */46 splash: boolean;47 errors: string[];48}4950const HELP_TEXT = `khaelor — a terminal-native autonomous engineering agent5152Usage53 khaelor start the interactive terminal agent54 khaelor --print "<prompt>" run one agent turn without the TUI, print the answer, exit55 khaelor --resume <session-id> resume a previous session5657Options58 --model <id> Anthropic model id for this run59 --aux-model <id> cheaper model used for context compaction60 --thinking <mode> off | adaptive | always61 --max-output-tokens <n> output token ceiling per model response62 --gate <mode> phase-gate rigor: strict | auto (default) | off63 -p, --print <prompt> non-interactive single turn (mirrors claude -p)64 --resume <session-id> resume a session by id65 --splash show the full splash on launch66 --doctor-tui print the detected terminal capabilities and exit67 --debug write developer logs to ~/.khaelor/logs/68 -v, --version print the version and exit69 -h, --help show this help7071Autonomy72 khaelord the long-running daemon: goals, heartbeat, budget (khaelord --help)7374Environment75 ANTHROPIC_API_KEY required — your Anthropic API key76`;7778export function helpText(): string {79 return HELP_TEXT;80}8182/**83 * The actionable message shown when no API key is configured. Never echoes84 * any secret material — there is nothing to echo.85 */86export function missingApiKeyMessage(): string {87 return [88 "khaelor needs an Anthropic API key.",89 "",90 "Set the ANTHROPIC_API_KEY environment variable and try again:",91 "",92 " export ANTHROPIC_API_KEY=<your key>",93 " khaelor",94 "",95 "Keys are created at https://console.anthropic.com/ — KHAELOR never",96 "stores or logs the key; it is read from the environment on each start.",97 ].join("\n");98}99100/** Hand-rolled, zero-dependency argv parsing (ARCHITECTURE.md §12.1). */101export function parseArgs(argv: readonly string[]): ParsedArgs {102 const parsed: ParsedArgs = {103 help: false,104 version: false,105 debug: false,106 print: null,107 resume: null,108 model: null,109 auxModel: null,110 thinking: null,111 maxOutputTokens: null,112 gate: null,113 doctorTui: false,114 splash: false,115 errors: [],116 };117118 const takeValue = (flag: string, index: number): string | null => {119 const value = argv[index + 1];120 if (value === undefined || value.startsWith("--")) {121 parsed.errors.push(`${flag} requires a value`);122 return null;123 }124 return value;125 };126127 for (let i = 0; i < argv.length; i += 1) {128 const arg = argv[i] as string;129 switch (arg) {130 case "-h":131 case "--help":132 parsed.help = true;133 break;134 case "-v":135 case "--version":136 parsed.version = true;137 break;138 case "--debug":139 parsed.debug = true;140 break;141 case "-p":142 case "--print": {143 const value = takeValue("--print", i);144 if (value !== null) {145 parsed.print = value;146 i += 1;147 }148 break;149 }150 case "--resume": {151 const value = takeValue("--resume", i);152 if (value !== null) {153 parsed.resume = value;154 i += 1;155 }156 break;157 }158 case "--model": {159 const value = takeValue("--model", i);160 if (value !== null) {161 parsed.model = value;162 i += 1;163 }164 break;165 }166 case "--aux-model": {167 const value = takeValue("--aux-model", i);168 if (value !== null) {169 parsed.auxModel = value;170 i += 1;171 }172 break;173 }174 case "--thinking": {175 const value = takeValue("--thinking", i);176 if (value !== null) {177 parsed.thinking = value;178 i += 1;179 }180 break;181 }182 case "--gate": {183 const value = takeValue("--gate", i);184 if (value !== null) {185 if (!["strict", "auto", "off"].includes(value)) {186 parsed.errors.push("--gate must be strict, auto, or off");187 } else {188 parsed.gate = value;189 }190 i += 1;191 }192 break;193 }194 case "--doctor-tui":195 parsed.doctorTui = true;196 break;197 case "--splash":198 parsed.splash = true;199 break;200 case "--max-output-tokens": {201 const value = takeValue("--max-output-tokens", i);202 if (value !== null) {203 const n = Number.parseInt(value, 10);204 if (!Number.isInteger(n) || n <= 0) {205 parsed.errors.push("--max-output-tokens must be a positive integer");206 } else {207 parsed.maxOutputTokens = n;208 }209 i += 1;210 }211 break;212 }213 default:214 parsed.errors.push(`unknown option: ${arg}`);215 break;216 }217 }218219 return parsed;220}221