Added {env:VAR} substitution to markdown-based config parsing, so agent definitions in .md now get env values injected before frontmatter/prompt parsing.
parent
5d2dc8888c
commit
f3f97e6a70
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
@ -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: ")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue