import type { AdapterEnvironmentCheck, AdapterEnvironmentTestContext, AdapterEnvironmentTestResult, } from "@paperclip/adapter-utils"; import { asString, parseObject, ensureAbsoluteDirectory, ensureCommandResolvable, ensurePathInEnv, } from "@paperclip/adapter-utils/server-utils"; function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] { if (checks.some((check) => check.level === "error")) return "fail"; if (checks.some((check) => check.level === "warn")) return "warn"; return "pass"; } function isNonEmpty(value: unknown): value is string { return typeof value === "string" && value.trim().length > 0; } export async function testEnvironment( ctx: AdapterEnvironmentTestContext, ): Promise { const checks: AdapterEnvironmentCheck[] = []; const config = parseObject(ctx.config); const command = asString(config.command, "claude"); const cwd = asString(config.cwd, process.cwd()); try { await ensureAbsoluteDirectory(cwd); checks.push({ code: "claude_cwd_valid", level: "info", message: `Working directory is valid: ${cwd}`, }); } catch (err) { checks.push({ code: "claude_cwd_invalid", level: "error", message: err instanceof Error ? err.message : "Invalid working directory", detail: cwd, }); } const envConfig = parseObject(config.env); const env: Record = {}; for (const [key, value] of Object.entries(envConfig)) { if (typeof value === "string") env[key] = value; } const runtimeEnv = ensurePathInEnv({ ...process.env, ...env }); try { await ensureCommandResolvable(command, cwd, runtimeEnv); checks.push({ code: "claude_command_resolvable", level: "info", message: `Command is executable: ${command}`, }); } catch (err) { checks.push({ code: "claude_command_unresolvable", level: "error", message: err instanceof Error ? err.message : "Command is not executable", detail: command, }); } const configApiKey = env.ANTHROPIC_API_KEY; const hostApiKey = process.env.ANTHROPIC_API_KEY; if (isNonEmpty(configApiKey) || isNonEmpty(hostApiKey)) { const source = isNonEmpty(configApiKey) ? "adapter config env" : "server environment"; checks.push({ code: "claude_anthropic_api_key_overrides_subscription", level: "warn", message: "ANTHROPIC_API_KEY is set. Claude will use API-key auth instead of subscription credentials.", detail: `Detected in ${source}.`, hint: "Unset ANTHROPIC_API_KEY if you want subscription-based Claude login behavior.", }); } else { checks.push({ code: "claude_subscription_mode_possible", level: "info", message: "ANTHROPIC_API_KEY is not set; subscription-based auth can be used if Claude is logged in.", }); } return { adapterType: ctx.adapterType, status: summarizeStatus(checks), checks, testedAt: new Date().toISOString(), }; }