Skip to content

Commit d1366bc

Browse files
authored
Merge pull request #3556 from Kilo-Org/add-cli-config-overrides-using-env-vars
Add cli config overrides using env vars
2 parents e0cd29a + 4a5e3ad commit d1366bc

File tree

4 files changed

+406
-1
lines changed

4 files changed

+406
-1
lines changed

.changeset/silly-guests-wonder.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@kilocode/cli": minor
3+
---
4+
5+
adds support for overriding config with env vars
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import { describe, it, expect, beforeEach, afterEach } from "vitest"
2+
import { applyEnvOverrides, PROVIDER_ENV_VAR, KILOCODE_PREFIX, KILO_PREFIX } from "../env-overrides.js"
3+
import type { CLIConfig } from "../types.js"
4+
5+
describe("env-overrides", () => {
6+
const originalEnv = process.env
7+
let testConfig: CLIConfig
8+
9+
beforeEach(() => {
10+
// Reset environment variables before each test
11+
process.env = { ...originalEnv }
12+
13+
// Clear any KILOCODE_* or KILO_* environment variables to ensure clean test state
14+
for (const key of Object.keys(process.env)) {
15+
if (key.startsWith(KILOCODE_PREFIX) || key.startsWith(KILO_PREFIX)) {
16+
delete process.env[key]
17+
}
18+
}
19+
20+
// Create a test config
21+
testConfig = {
22+
version: "1.0.0",
23+
mode: "code",
24+
telemetry: true,
25+
provider: "default",
26+
providers: [
27+
{
28+
id: "default",
29+
provider: "kilocode",
30+
kilocodeToken: "test-token",
31+
kilocodeModel: "anthropic/claude-sonnet-4.5",
32+
kilocodeOrganizationId: "original-org-id",
33+
},
34+
{
35+
id: "anthropic-provider",
36+
provider: "anthropic",
37+
apiKey: "test-key",
38+
apiModelId: "claude-3-5-sonnet-20241022",
39+
},
40+
],
41+
autoApproval: {
42+
enabled: true,
43+
},
44+
theme: "dark",
45+
customThemes: {},
46+
}
47+
})
48+
49+
afterEach(() => {
50+
// Restore original environment
51+
process.env = originalEnv
52+
})
53+
54+
describe("KILO_PROVIDER override", () => {
55+
it("should override provider when KILO_PROVIDER is set and provider exists", () => {
56+
process.env[PROVIDER_ENV_VAR] = "anthropic-provider"
57+
58+
const result = applyEnvOverrides(testConfig)
59+
60+
expect(result.provider).toBe("anthropic-provider")
61+
})
62+
63+
it("should not override provider when KILO_PROVIDER provider does not exist", () => {
64+
process.env[PROVIDER_ENV_VAR] = "nonexistent-provider"
65+
66+
const result = applyEnvOverrides(testConfig)
67+
68+
expect(result.provider).toBe("default")
69+
})
70+
71+
it("should not override provider when KILO_PROVIDER is empty", () => {
72+
process.env[PROVIDER_ENV_VAR] = ""
73+
74+
const result = applyEnvOverrides(testConfig)
75+
76+
expect(result.provider).toBe("default")
77+
})
78+
})
79+
80+
describe("KILOCODE_* overrides for kilocode provider", () => {
81+
it("should transform KILOCODE_MODEL to kilocodeModel", () => {
82+
process.env[`${KILOCODE_PREFIX}MODEL`] = "anthropic/claude-opus-4.0"
83+
84+
const result = applyEnvOverrides(testConfig)
85+
86+
const provider = result.providers.find((p) => p.id === "default")
87+
expect(provider?.kilocodeModel).toBe("anthropic/claude-opus-4.0")
88+
})
89+
90+
it("should transform KILOCODE_ORGANIZATION_ID to kilocodeOrganizationId", () => {
91+
process.env[`${KILOCODE_PREFIX}ORGANIZATION_ID`] = "new-org-id"
92+
93+
const result = applyEnvOverrides(testConfig)
94+
95+
const provider = result.providers.find((p) => p.id === "default")
96+
expect(provider?.kilocodeOrganizationId).toBe("new-org-id")
97+
})
98+
99+
it("should handle multiple KILOCODE_* overrides", () => {
100+
process.env[`${KILOCODE_PREFIX}MODEL`] = "anthropic/claude-opus-4.0"
101+
process.env[`${KILOCODE_PREFIX}ORGANIZATION_ID`] = "new-org-id"
102+
process.env[`${KILOCODE_PREFIX}TOKEN`] = "new-token"
103+
104+
const result = applyEnvOverrides(testConfig)
105+
106+
const provider = result.providers.find((p) => p.id === "default")
107+
expect(provider?.kilocodeModel).toBe("anthropic/claude-opus-4.0")
108+
expect(provider?.kilocodeOrganizationId).toBe("new-org-id")
109+
expect(provider?.kilocodeToken).toBe("new-token")
110+
})
111+
})
112+
113+
describe("KILO_* overrides for non-kilocode providers", () => {
114+
it("should transform KILO_API_KEY to apiKey for non-kilocode provider", () => {
115+
process.env[PROVIDER_ENV_VAR] = "anthropic-provider"
116+
process.env[`${KILO_PREFIX}API_KEY`] = "new-key"
117+
118+
const result = applyEnvOverrides(testConfig)
119+
120+
expect(result.provider).toBe("anthropic-provider")
121+
const provider = result.providers.find((p) => p.id === "anthropic-provider")
122+
expect(provider?.apiKey).toBe("new-key")
123+
})
124+
125+
it("should transform KILO_API_MODEL_ID to apiModelId", () => {
126+
process.env[PROVIDER_ENV_VAR] = "anthropic-provider"
127+
process.env[`${KILO_PREFIX}API_MODEL_ID`] = "claude-3-opus-20240229"
128+
129+
const result = applyEnvOverrides(testConfig)
130+
131+
const provider = result.providers.find((p) => p.id === "anthropic-provider")
132+
expect(provider?.apiModelId).toBe("claude-3-opus-20240229")
133+
})
134+
135+
it("should transform KILO_BASE_URL to baseUrl", () => {
136+
process.env[PROVIDER_ENV_VAR] = "anthropic-provider"
137+
process.env[`${KILO_PREFIX}BASE_URL`] = "https://api.example.com"
138+
139+
const result = applyEnvOverrides(testConfig)
140+
141+
const provider = result.providers.find((p) => p.id === "anthropic-provider")
142+
expect(provider?.baseUrl).toBe("https://api.example.com")
143+
})
144+
145+
it("should not apply KILO_* overrides to kilocode provider", () => {
146+
process.env[PROVIDER_ENV_VAR] = "kilocode"
147+
process.env[`${KILO_PREFIX}API_KEY`] = "should-not-apply"
148+
149+
const result = applyEnvOverrides(testConfig)
150+
151+
const provider = result.providers.find((p) => p.id === "default")
152+
expect(provider?.apiKey).toBeUndefined()
153+
})
154+
155+
it("should not apply KILOCODE_* overrides to non-kilocode provider", () => {
156+
process.env[PROVIDER_ENV_VAR] = "anthropic-provider"
157+
process.env[`${KILOCODE_PREFIX}MODEL`] = "should-not-apply"
158+
159+
const result = applyEnvOverrides(testConfig)
160+
161+
const provider = result.providers.find((p) => p.id === "anthropic-provider")
162+
expect(provider?.kilocodeModel).toBeUndefined()
163+
})
164+
})
165+
166+
describe("Combined overrides", () => {
167+
it("should apply both provider and field overrides together for non-kilocode provider", () => {
168+
process.env[PROVIDER_ENV_VAR] = "anthropic-provider"
169+
process.env[`${KILO_PREFIX}API_MODEL_ID`] = "claude-3-opus-20240229"
170+
process.env[`${KILO_PREFIX}API_KEY`] = "new-key"
171+
172+
const result = applyEnvOverrides(testConfig)
173+
174+
expect(result.provider).toBe("anthropic-provider")
175+
const provider = result.providers.find((p) => p.id === "anthropic-provider")
176+
expect(provider?.apiModelId).toBe("claude-3-opus-20240229")
177+
expect(provider?.apiKey).toBe("new-key")
178+
})
179+
180+
it("should apply both provider and field overrides together for kilocode provider", () => {
181+
process.env[PROVIDER_ENV_VAR] = "default"
182+
process.env[`${KILOCODE_PREFIX}MODEL`] = "anthropic/claude-opus-4.0"
183+
process.env[`${KILOCODE_PREFIX}ORGANIZATION_ID`] = "new-org-id"
184+
185+
const result = applyEnvOverrides(testConfig)
186+
187+
expect(result.provider).toBe("default")
188+
const provider = result.providers.find((p) => p.id === "default")
189+
expect(provider?.kilocodeModel).toBe("anthropic/claude-opus-4.0")
190+
expect(provider?.kilocodeOrganizationId).toBe("new-org-id")
191+
})
192+
})
193+
194+
describe("Edge cases", () => {
195+
it("should handle empty config providers array", () => {
196+
testConfig.providers = []
197+
198+
const result = applyEnvOverrides(testConfig)
199+
200+
expect(result.providers).toEqual([])
201+
})
202+
203+
it("should handle config with no current provider", () => {
204+
testConfig.provider = "nonexistent"
205+
206+
const result = applyEnvOverrides(testConfig)
207+
208+
expect(result).toEqual(testConfig)
209+
})
210+
211+
it("should handle empty string override values for KILOCODE_*", () => {
212+
process.env[`${KILOCODE_PREFIX}MODEL`] = ""
213+
214+
const result = applyEnvOverrides(testConfig)
215+
216+
// Empty strings should not trigger overrides
217+
const provider = result.providers.find((p) => p.id === "default")
218+
expect(provider?.kilocodeModel).toBe("anthropic/claude-sonnet-4.5")
219+
})
220+
221+
it("should handle empty string override values for KILO_*", () => {
222+
process.env[PROVIDER_ENV_VAR] = "anthropic-provider"
223+
process.env[`${KILO_PREFIX}API_KEY`] = ""
224+
225+
const result = applyEnvOverrides(testConfig)
226+
227+
// Empty strings should not trigger overrides
228+
const provider = result.providers.find((p) => p.id === "anthropic-provider")
229+
expect(provider?.apiKey).toBe("test-key")
230+
})
231+
232+
it("should ignore KILOCODE_ with no field name", () => {
233+
process.env[KILOCODE_PREFIX.slice(0, -1)] = "value"
234+
235+
const result = applyEnvOverrides(testConfig)
236+
237+
// Should not modify anything
238+
expect(result).toEqual(testConfig)
239+
})
240+
241+
it("should ignore KILO_PROVIDER since it's handled separately", () => {
242+
process.env[PROVIDER_ENV_VAR] = "anthropic-provider"
243+
244+
const result = applyEnvOverrides(testConfig)
245+
246+
// KILO_PROVIDER should change the provider but not add a 'provider' field
247+
expect(result.provider).toBe("anthropic-provider")
248+
249+
const provider = result.providers.find((p) => p.id === "anthropic-provider")
250+
expect(provider?.provider).toBe("anthropic") // Original value
251+
})
252+
})
253+
})

0 commit comments

Comments
 (0)