CLI: streamline startup paths and env parsing

Add shared parseBooleanValue()/isTruthyEnvValue() and apply across CLI, gateway, memory, and live-test flags for consistent env handling.
Introduce route-first fast paths, lazy subcommand registration, and deferred plugin loading to reduce CLI startup overhead.
Centralize config validation via ensureConfigReady() and add config caching/deferred shell env fallback for fewer IO passes.
Harden logger initialization/imports and add focused tests for argv, boolean parsing, frontmatter, and CLI subcommands.
This commit is contained in:
Gustavo Madeira Santana
2026-01-18 15:56:24 -05:00
committed by Peter Steinberger
parent 97531f174f
commit acb523de86
58 changed files with 1274 additions and 500 deletions

View File

@@ -8,6 +8,7 @@ import JSON5 from "json5";
import {
loadShellEnvFallback,
resolveShellEnvFallbackTimeoutMs,
shouldDeferShellEnvFallback,
shouldEnableShellEnvFallback,
} from "../infra/shell-env.js";
import { DuplicateAgentDirError, findDuplicateAgentDirs } from "./agent-dirs.js";
@@ -282,7 +283,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
function loadConfig(): ClawdbotConfig {
try {
if (!deps.fs.existsSync(configPath)) {
if (shouldEnableShellEnvFallback(deps.env)) {
if (shouldEnableShellEnvFallback(deps.env) && !shouldDeferShellEnvFallback(deps.env)) {
loadShellEnvFallback({
enabled: true,
env: deps.env,
@@ -352,7 +353,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
applyConfigEnv(cfg, deps.env);
const enabled = shouldEnableShellEnvFallback(deps.env) || cfg.env?.shellEnv?.enabled === true;
if (enabled) {
if (enabled && !shouldDeferShellEnvFallback(deps.env)) {
loadShellEnvFallback({
enabled: true,
env: deps.env,
@@ -583,8 +584,54 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
// NOTE: These wrappers intentionally do *not* cache the resolved config path at
// module scope. `CLAWDBOT_CONFIG_PATH` (and friends) are expected to work even
// when set after the module has been imported (tests, one-off scripts, etc.).
const DEFAULT_CONFIG_CACHE_MS = 200;
let configCache:
| {
configPath: string;
expiresAt: number;
config: ClawdbotConfig;
}
| null = null;
function resolveConfigCacheMs(env: NodeJS.ProcessEnv): number {
const raw = env.CLAWDBOT_CONFIG_CACHE_MS?.trim();
if (raw === "" || raw === "0") return 0;
if (!raw) return DEFAULT_CONFIG_CACHE_MS;
const parsed = Number.parseInt(raw, 10);
if (!Number.isFinite(parsed)) return DEFAULT_CONFIG_CACHE_MS;
return Math.max(0, parsed);
}
function shouldUseConfigCache(env: NodeJS.ProcessEnv): boolean {
if (env.CLAWDBOT_DISABLE_CONFIG_CACHE?.trim()) return false;
return resolveConfigCacheMs(env) > 0;
}
function clearConfigCache(): void {
configCache = null;
}
export function loadConfig(): ClawdbotConfig {
return createConfigIO({ configPath: resolveConfigPath() }).loadConfig();
const configPath = resolveConfigPath();
const now = Date.now();
if (shouldUseConfigCache(process.env)) {
const cached = configCache;
if (cached && cached.configPath === configPath && cached.expiresAt > now) {
return cached.config;
}
}
const config = createConfigIO({ configPath }).loadConfig();
if (shouldUseConfigCache(process.env)) {
const cacheMs = resolveConfigCacheMs(process.env);
if (cacheMs > 0) {
configCache = {
configPath,
expiresAt: now + cacheMs,
config,
};
}
}
return config;
}
export async function readConfigFileSnapshot(): Promise<ConfigFileSnapshot> {
@@ -594,5 +641,6 @@ export async function readConfigFileSnapshot(): Promise<ConfigFileSnapshot> {
}
export async function writeConfigFile(cfg: ClawdbotConfig): Promise<void> {
clearConfigCache();
await createConfigIO({ configPath: resolveConfigPath() }).writeConfigFile(cfg);
}