feat(S-203): Test-drive and implement src/config.ts

This commit is contained in:
2026-05-18 17:51:44 -05:00
parent 8340933f8a
commit f3458734d4
2 changed files with 203 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
import { homedir } from "node:os";
import {
readFileSync as realReadFileSync,
existsSync as realExistsSync,
} from "node:fs";
import { spawnSync } from "node:child_process";
import type { ClientName } from "./types.js";
const CLIENT_NAMES: ClientName[] = ["codex", "claude", "opencode"];
export interface ResolvedConfig {
paths: Partial<Record<ClientName, string>>;
defaultClient?: ClientName;
}
export interface ResolveConfigOptions {
flags?: Record<string, string | boolean | undefined>;
env?: NodeJS.ProcessEnv;
homeDir?: string;
readFileSync?: (path: string, encoding: BufferEncoding) => string;
existsSync?: (path: string) => boolean;
whichSync?: (cmd: string) => string | undefined;
}
function defaultWhichSync(cmd: string): string | undefined {
const isWin = process.platform === "win32";
const result = spawnSync(isWin ? "where" : "which", [cmd], {
encoding: "utf-8",
});
if (result.status === 0) {
return result.stdout.trim().split("\n")[0];
}
return undefined;
}
export function resolveConfig(
options: ResolveConfigOptions = {}
): ResolvedConfig {
const {
flags = {},
env = process.env,
homeDir = homedir(),
readFileSync = realReadFileSync,
existsSync = realExistsSync,
whichSync = defaultWhichSync,
} = options;
const configPath = `${homeDir}/.openclaw/ai-cli-dispatch.json`;
let fileConfig: Record<string, unknown> = {};
if (existsSync(configPath)) {
try {
fileConfig = JSON.parse(readFileSync(configPath, "utf-8"));
} catch {
fileConfig = {};
}
}
const filePaths = (fileConfig.paths ?? {}) as Partial<
Record<ClientName, string>
>;
const fileDefault = fileConfig.defaultClient as ClientName | undefined;
const paths: Partial<Record<ClientName, string>> = {};
for (const name of CLIENT_NAMES) {
const flagKey = `${name}-path`;
const envKey = `AI_CLI_${name.toUpperCase()}_PATH`;
const resolved =
(typeof flags[flagKey] === "string"
? (flags[flagKey] as string)
: undefined) ??
env[envKey] ??
filePaths[name] ??
whichSync(name);
if (resolved !== undefined) {
paths[name] = resolved;
}
}
const defaultClient =
(typeof flags["default-client"] === "string"
? (flags["default-client"] as string)
: undefined) ??
env.AI_CLI_DEFAULT_CLIENT ??
fileDefault;
const result: ResolvedConfig = { paths };
if (defaultClient !== undefined) {
result.defaultClient = defaultClient as ClientName;
}
return result;
}