feat: auto-load ~/.openclaw/.env in buildConfig

Skills no longer need to manually export env vars. auth-runtime
reads the global config file automatically on first client creation.
Won't overwrite explicitly set environment variables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ywkj 2026-03-19 07:51:38 +08:00
parent b05363e8f0
commit 22bc6f7438
9 changed files with 95 additions and 2 deletions

View File

@ -1 +1 @@
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAyB,eAAe,EAAE,MAAM,YAAY,CAAC;AAapG,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkBD,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;gBAErB,OAAO,GAAE,kBAAuB;IAU5C,iDAAiD;IAC3C,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC;IAOzC,yEAAyE;IACnE,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;IAoBrC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAIvC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;IAIxD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;IAIvD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;IAIzD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;YAMlD,OAAO;YAoBP,QAAQ;YAUR,YAAY;YASZ,YAAY;CAmB3B;AAED,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,WAAW,CAE3E"} {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAyB,eAAe,EAAE,MAAM,YAAY,CAAC;AAcpG,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAmBD,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;gBAErB,OAAO,GAAE,kBAAuB;IAU5C,iDAAiD;IAC3C,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC;IAOzC,yEAAyE;IACnE,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;IAoBrC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAIvC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;IAIxD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;IAIvD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;IAIzD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;YAMlD,OAAO;YAoBP,QAAQ;YAUR,YAAY;YASZ,YAAY;CAmB3B;AAED,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,WAAW,CAE3E"}

2
dist/client.js vendored
View File

@ -1,5 +1,6 @@
import { requestApi } from './http.js'; import { requestApi } from './http.js';
import { getCacheFile, readCachedToken, writeCache, deleteCache } from './cache.js'; import { getCacheFile, readCachedToken, writeCache, deleteCache } from './cache.js';
import { loadGlobalEnv } from './env.js';
const SESSION_RETRYABLE_STATUS = new Set([401, 403]); const SESSION_RETRYABLE_STATUS = new Set([401, 403]);
const SESSION_RETRYABLE_BODY_MARKERS = [ const SESSION_RETRYABLE_BODY_MARKERS = [
'session not found or expired', 'session not found or expired',
@ -9,6 +10,7 @@ const SESSION_RETRYABLE_BODY_MARKERS = [
'client key revoked', 'client key revoked',
]; ];
function buildConfig(options) { function buildConfig(options) {
loadGlobalEnv();
return { return {
authBase: (options.authBase || process.env.AUTH_BASE || 'https://api-gw-test.yuanwei-lnc.com').replace(/\/$/, ''), authBase: (options.authBase || process.env.AUTH_BASE || 'https://api-gw-test.yuanwei-lnc.com').replace(/\/$/, ''),
clientKey: options.clientKey || process.env.CLIENT_KEY || '', clientKey: options.clientKey || process.env.CLIENT_KEY || '',

2
dist/client.js.map vendored

File diff suppressed because one or more lines are too long

5
dist/env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/**
* Load ~/.openclaw/.env into process.env (once, won't overwrite existing vars).
*/
export declare function loadGlobalEnv(): void;
//# sourceMappingURL=env.d.ts.map

1
dist/env.d.ts.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CA+BpC"}

39
dist/env.js vendored Normal file
View File

@ -0,0 +1,39 @@
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
const GLOBAL_ENV_PATH = path.join(os.homedir(), '.openclaw', '.env');
let loaded = false;
/**
* Load ~/.openclaw/.env into process.env (once, won't overwrite existing vars).
*/
export function loadGlobalEnv() {
if (loaded)
return;
loaded = true;
let content;
try {
content = fs.readFileSync(GLOBAL_ENV_PATH, 'utf-8');
}
catch {
return; // file doesn't exist — that's fine
}
for (const line of content.split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#'))
continue;
const eqIdx = trimmed.indexOf('=');
if (eqIdx < 1)
continue;
const key = trimmed.slice(0, eqIdx).trim();
let value = trimmed.slice(eqIdx + 1).trim();
// strip surrounding quotes
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1);
}
// don't overwrite explicitly set env vars
if (process.env[key] === undefined) {
process.env[key] = value;
}
}
}
//# sourceMappingURL=env.js.map

1
dist/env.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAEzB,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;AAErE,IAAI,MAAM,GAAG,KAAK,CAAC;AAEnB;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,IAAI,MAAM;QAAE,OAAO;IACnB,MAAM,GAAG,IAAI,CAAC;IAEd,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,mCAAmC;IAC7C,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAElD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,KAAK,GAAG,CAAC;YAAE,SAAS;QAExB,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3C,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAE5C,2BAA2B;QAC3B,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACrG,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;QAED,0CAA0C;QAC1C,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;YACnC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAC3B,CAAC;IACH,CAAC;AACH,CAAC"}

View File

@ -1,6 +1,7 @@
import type { ApiResponse, ClientConfig, EnvConfig, HttpMethod, SessionResponse } from './types.js'; import type { ApiResponse, ClientConfig, EnvConfig, HttpMethod, SessionResponse } from './types.js';
import { requestApi } from './http.js'; import { requestApi } from './http.js';
import { getCacheFile, readCachedToken, writeCache, deleteCache } from './cache.js'; import { getCacheFile, readCachedToken, writeCache, deleteCache } from './cache.js';
import { loadGlobalEnv } from './env.js';
const SESSION_RETRYABLE_STATUS = new Set([401, 403]); const SESSION_RETRYABLE_STATUS = new Set([401, 403]);
const SESSION_RETRYABLE_BODY_MARKERS = [ const SESSION_RETRYABLE_BODY_MARKERS = [
@ -27,6 +28,7 @@ export interface SkillClientOptions {
} }
function buildConfig(options: SkillClientOptions): EnvConfig { function buildConfig(options: SkillClientOptions): EnvConfig {
loadGlobalEnv();
return { return {
authBase: (options.authBase || process.env.AUTH_BASE || 'https://api-gw-test.yuanwei-lnc.com').replace(/\/$/, ''), authBase: (options.authBase || process.env.AUTH_BASE || 'https://api-gw-test.yuanwei-lnc.com').replace(/\/$/, ''),
clientKey: options.clientKey || process.env.CLIENT_KEY || '', clientKey: options.clientKey || process.env.CLIENT_KEY || '',

43
src/env.ts Normal file
View File

@ -0,0 +1,43 @@
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
const GLOBAL_ENV_PATH = path.join(os.homedir(), '.openclaw', '.env');
let loaded = false;
/**
* Load ~/.openclaw/.env into process.env (once, won't overwrite existing vars).
*/
export function loadGlobalEnv(): void {
if (loaded) return;
loaded = true;
let content: string;
try {
content = fs.readFileSync(GLOBAL_ENV_PATH, 'utf-8');
} catch {
return; // file doesn't exist — that's fine
}
for (const line of content.split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const eqIdx = trimmed.indexOf('=');
if (eqIdx < 1) continue;
const key = trimmed.slice(0, eqIdx).trim();
let value = trimmed.slice(eqIdx + 1).trim();
// strip surrounding quotes
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1);
}
// don't overwrite explicitly set env vars
if (process.env[key] === undefined) {
process.env[key] = value;
}
}
}