@@ -4,6 +4,49 @@ const path = require('path');
44
55const DEFAULT_CONFIG_DIR = path . join ( os . homedir ( ) , '.claude-code-router' ) ;
66const DEFAULT_CONFIG_PATH = path . join ( DEFAULT_CONFIG_DIR , 'config.json' ) ;
7+ const DEFAULT_ENV_PATH = path . join ( os . homedir ( ) , '.env' ) ;
8+ let envLoaded = false ;
9+
10+ function parseEnvLine ( line ) {
11+ const trimmed = line . trim ( ) ;
12+ if ( ! trimmed || trimmed . startsWith ( '#' ) ) return null ;
13+
14+ const withoutExport = trimmed . startsWith ( 'export ' )
15+ ? trimmed . slice ( 'export ' . length ) . trim ( )
16+ : trimmed ;
17+
18+ const separatorIndex = withoutExport . indexOf ( '=' ) ;
19+ if ( separatorIndex === - 1 ) return null ;
20+
21+ const key = withoutExport . slice ( 0 , separatorIndex ) . trim ( ) ;
22+ if ( ! key ) return null ;
23+
24+ let value = withoutExport . slice ( separatorIndex + 1 ) . trim ( ) ;
25+ const hasQuotes = ( value . startsWith ( '"' ) && value . endsWith ( '"' ) )
26+ || ( value . startsWith ( '\'' ) && value . endsWith ( '\'' ) ) ;
27+ if ( hasQuotes ) {
28+ value = value . slice ( 1 , - 1 ) ;
29+ }
30+
31+ return { key, value } ;
32+ }
33+
34+ function loadDotenv ( ) {
35+ if ( envLoaded ) return ;
36+ envLoaded = true ;
37+
38+ const envPath = process . env . CCR_ENV_PATH || DEFAULT_ENV_PATH ;
39+ if ( ! fs . existsSync ( envPath ) ) return ;
40+
41+ const content = fs . readFileSync ( envPath , 'utf8' ) ;
42+ content . split ( / \r ? \n / ) . forEach ( ( line ) => {
43+ const parsed = parseEnvLine ( line ) ;
44+ if ( ! parsed ) return ;
45+ if ( process . env [ parsed . key ] === undefined ) {
46+ process . env [ parsed . key ] = parsed . value ;
47+ }
48+ } ) ;
49+ }
750
851function resolveEnv ( value ) {
952 if ( typeof value !== 'string' ) return value ;
@@ -19,15 +62,19 @@ function resolveEnv(value) {
1962 } ) ;
2063}
2164
22- function resolveConfigValue ( value ) {
65+ function resolveConfigValue ( value , key ) {
2366 if ( Array . isArray ( value ) ) {
24- return value . map ( resolveConfigValue ) ;
67+ return value . map ( ( item ) => resolveConfigValue ( item , key ) ) ;
2568 }
2669 if ( value && typeof value === 'object' ) {
2770 return Object . fromEntries (
28- Object . entries ( value ) . map ( ( [ key , val ] ) => [ key , resolveConfigValue ( val ) ] )
71+ Object . entries ( value ) . map ( ( [ childKey , val ] ) => [
72+ childKey ,
73+ resolveConfigValue ( val , childKey )
74+ ] )
2975 ) ;
3076 }
77+ if ( key === 'api_key' ) return value ;
3178 return resolveEnv ( value ) ;
3279}
3380
@@ -47,13 +94,14 @@ function applyDefaults(config) {
4794}
4895
4996function loadConfig ( ) {
97+ loadDotenv ( ) ;
5098 const configPath = process . env . CCR_CONFIG_PATH || DEFAULT_CONFIG_PATH ;
5199 if ( ! fs . existsSync ( configPath ) ) {
52100 throw new Error ( `Config file not found at ${ configPath } ` ) ;
53101 }
54102
55103 const raw = JSON . parse ( fs . readFileSync ( configPath , 'utf8' ) ) ;
56- const resolved = resolveConfigValue ( raw ) ;
104+ const resolved = resolveConfigValue ( raw , null ) ;
57105 return applyDefaults ( resolved ) ;
58106}
59107
@@ -66,6 +114,7 @@ function getConfigDir() {
66114}
67115
68116function resolveProviderKey ( provider ) {
117+ loadDotenv ( ) ;
69118 if ( ! provider ?. api_key ) return null ;
70119 if ( typeof provider . api_key === 'string' && provider . api_key . startsWith ( '$' ) ) {
71120 const envKey = provider . api_key . slice ( 1 ) ;
0 commit comments