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 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