feat(S-203): Test-drive and implement src/config.ts
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user