pull/9261/merge
Eric Guo 2026-04-08 06:33:24 +00:00 committed by GitHub
commit 5c82025f32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 80 additions and 2 deletions

View File

@ -7,6 +7,10 @@ export namespace ConfigMarkdown {
export const FILE_REGEX = /(?<![\w`])@(\.?[^\s`,.]*(?:\.[^\s`,.]+)*)/g
export const SHELL_REGEX = /!`([^`]+)`/g
export function substituteEnv(content: string) {
return content.replace(/\{env:([^}]+)\}/g, (_, varName) => process.env[varName] || "")
}
export function files(template: string) {
return Array.from(template.matchAll(FILE_REGEX))
}
@ -69,7 +73,8 @@ export namespace ConfigMarkdown {
}
export async function parse(filePath: string) {
const template = await Filesystem.readText(filePath)
const raw = await Filesystem.readText(filePath)
const template = substituteEnv(raw)
try {
const md = matter(template)

View File

@ -0,0 +1,7 @@
---
description: "Token is {env:TEST_MCP_TOKEN}"
note: "{env:EMPTY_ENV_VAR}"
---
Token: {env:TEST_MCP_TOKEN}
Missing: {env:EMPTY_ENV_VAR}

View File

@ -1,4 +1,4 @@
import { expect, test, describe } from "bun:test"
import { expect, test, describe, beforeAll, afterAll } from "bun:test"
import { ConfigMarkdown } from "../../src/config/markdown"
describe("ConfigMarkdown: normal template", () => {
@ -226,3 +226,37 @@ describe("ConfigMarkdown: frontmatter has weird model id", async () => {
expect(result.content.trim()).toBe("Strictly follow da rules")
})
})
describe("ConfigMarkdown: env substitution", () => {
const tokenKey = "TEST_MCP_TOKEN"
const emptyKey = "EMPTY_ENV_VAR"
const prevToken = process.env[tokenKey]
const prevEmpty = process.env[emptyKey]
let parsed: Awaited<ReturnType<typeof ConfigMarkdown.parse>>
beforeAll(async () => {
process.env[tokenKey] = "abc123"
delete process.env[emptyKey]
parsed = await ConfigMarkdown.parse(import.meta.dir + "/fixtures/env-frontmatter.md")
})
afterAll(() => {
if (prevToken === undefined) delete process.env[tokenKey]
else process.env[tokenKey] = prevToken
if (prevEmpty === undefined) delete process.env[emptyKey]
else process.env[emptyKey] = prevEmpty
})
test("should substitute env vars in frontmatter", () => {
expect(parsed.data.description).toBe("Token is abc123")
})
test("should substitute missing env vars with empty string in frontmatter", () => {
expect(parsed.data.note).toBe("")
})
test("should substitute env vars in content", () => {
expect(parsed.content).toContain("Token: abc123")
expect(parsed.content).toContain("Missing: ")
})
})

View File

@ -211,6 +211,38 @@ Provide constructive feedback without making direct changes.
The markdown file name becomes the agent name. For example, `review.md` creates a `review` agent.
#### Variables
You can substitute environment variables in markdown agent files using `{env:VAR_NAME}`. This works in both frontmatter and the prompt body.
```markdown title="~/.config/opencode/agents/mcdonalds.md"
---
name: "McDonalds"
description: Agent to help you ordering McDonalds
mode: primary
model: "{env:OPENCODE_SMALL_MODEL}"
tools:
question: true
bash: false
read: false
glob: false
grep: false
write: false
edit: false
task: false
todowrite: false
todoread: false
skill: false
"mcdonalds-mcp_*": true
---
You are a McDonalds clerk who can help user make ordering, save user money as much as possible, unless user say no.
If MCP_TOKEN:`{env:MCDONALDS_MCP_TOKEN}` is empty, tell the user to apply for a token at https://open.mcd.cn/mcp/doc
If MCP_TOKEN not set, it will be replaced with an empty string. Avoid echoing secrets back to the user.
```
---
## Options