add credential resolver for OpenClaw key sharing

This commit is contained in:
Dhruv Sharma 2026-03-31 04:02:33 +05:30
parent a10ceb6c09
commit 4d142eac1f
1 changed files with 114 additions and 0 deletions

View File

@ -0,0 +1,114 @@
/**
* Shared credential resolver resolves API keys from multiple sources.
*
* Resolution order per key:
* 1. Environment variable (already set in process.env)
* 2. OpenClaw config (~/.openclaw/openclaw.json keys section)
*
* Call `applyOpenClawCredentials()` early in CLI startup so spawned agent
* sessions inherit the resolved values via the parent process environment.
*/
import { existsSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
/** Keys that AO agents commonly need and OpenClaw may already store. */
const RESOLVABLE_KEYS = [
"ANTHROPIC_API_KEY",
"OPENAI_API_KEY",
"GITHUB_TOKEN",
"GOOGLE_API_KEY",
"OPENROUTER_API_KEY",
] as const;
type ResolvableKey = (typeof RESOLVABLE_KEYS)[number];
interface ResolvedCredential {
key: ResolvableKey;
value: string;
source: "env" | "openclaw";
}
function readOpenClawKeys(): Record<string, string> {
const keys: Record<string, string> = {};
const configPath = join(homedir(), ".openclaw", "openclaw.json");
if (!existsSync(configPath)) return keys;
try {
const raw = readFileSync(configPath, "utf-8");
const config = JSON.parse(raw) as Record<string, unknown>;
// OpenClaw stores keys in a top-level "keys" object:
// { "keys": { "ANTHROPIC_API_KEY": "sk-ant-...", ... } }
const keysSection = config.keys;
if (keysSection && typeof keysSection === "object") {
for (const [k, v] of Object.entries(keysSection as Record<string, unknown>)) {
if (typeof v === "string" && v.length > 0) {
keys[k] = v;
}
}
}
// Also check top-level env / environment block (some OpenClaw setups):
// { "env": { "ANTHROPIC_API_KEY": "sk-ant-..." } }
const envSection = config.env ?? config.environment;
if (envSection && typeof envSection === "object") {
for (const [k, v] of Object.entries(envSection as Record<string, unknown>)) {
if (typeof v === "string" && v.length > 0 && !(k in keys)) {
keys[k] = v;
}
}
}
} catch {
// Malformed config — silently skip.
}
return keys;
}
/**
* Resolve a single credential from all available sources.
* Returns the value and where it came from, or null if not found anywhere.
*/
export function resolveCredential(key: ResolvableKey): ResolvedCredential | null {
const envValue = process.env[key];
if (envValue && envValue.length > 0) {
return { key, value: envValue, source: "env" };
}
const openclawKeys = readOpenClawKeys();
const openclawValue = openclawKeys[key];
if (openclawValue) {
return { key, value: openclawValue, source: "openclaw" };
}
return null;
}
/**
* Populate `process.env` with API keys found in OpenClaw config that are
* not already set in the environment. This makes them available to all
* spawned agent sessions (tmux inherits the parent env).
*
* Returns the list of keys that were injected from OpenClaw.
*/
export function applyOpenClawCredentials(): ResolvedCredential[] {
const openclawKeys = readOpenClawKeys();
const applied: ResolvedCredential[] = [];
for (const key of RESOLVABLE_KEYS) {
if (process.env[key] && process.env[key]!.length > 0) continue;
const value = openclawKeys[key];
if (value) {
process.env[key] = value;
applied.push({ key, value, source: "openclaw" });
}
}
return applied;
}
export { RESOLVABLE_KEYS, type ResolvableKey, type ResolvedCredential };