opencode-plugin-md-expandOpenCode plugin and CLI for expanding Markdown prompt templates.
2
15
7 in 7 days
27.5
Multi-signal model
2 months ago
2026-06-07
Install and configure
opencode.jsonWrites to this project's opencode.json — applies to this repository only.
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-plugin-md-expand@0.2.0"]
}Writes to ~/.config/opencode/opencode.json — applies to every project.
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-plugin-md-expand@0.2.0"]
}If you want to modify the plugin locally, install it into the project and reference the local path.
shell
pnpm add -D opencode-plugin-md-expandopencode loads npm dependencies through its embedded runtime on startup and caches them locally — no manual global install needed.
OpenCode plugin that expands {{...}} templates in .md agent, command, mode,
skill files, and user messages before the LLM sees them.
Expand every
{{...}}in your markdown files.
File includes, environment variables, scoped arguments, and inline conditionals: all resolved before the LLM reads.
At a glance
Include shared rules files in any prompt:
You must follow these rules:
{{ file="./rules/general.md" }}
{{ file="./rules/style.md" }}
Inject environment variables at runtime:
Base API URL: {{env:API_URL}}
{{ if=env:CI }}Skip interactive prompts.{{ endif }}
Switch instructions by mode (a custom arg):
{{ file="./review.md" mode=cached }}
Inside review.md:
{{ if=mode==cached }}
Reuse previous analysis results.
{{ else }}
Run a fresh analysis.
{{ endif }}
Pass args into a reusable template:
{{ file="./template.md" title="Plan Review" domain=correctness }}
Inside template.md:
# {{arg:title}}
Domain: {{arg:domain}}
Reference scripts and files by absolute path:
After changes, run `{{path:.cargo/verify.sh}}` before returning.
{{path:...}} resolves relative to current working directory.
{{gitpath:...}} resolves relative to the repository root.
(Path tokens emit absolute paths without reading the file - see Path resolution in Template grammar.)
Install in OpenCode
Add to opencode.json (or .opencode/opencode.json):
{
"plugin": ["opencode-plugin-md-expand@^0.1.0"],
}
OpenCode auto-installs npm packages into its cache directory on first
load - no manual npm install needed.
With options:
{
"plugin": [
[
"opencode-plugin-md-expand@^0.1.0",
{
"debug": true,
},
],
],
}
Configuration
The plugin auto-derives fallback directories from standard OpenCode paths:
<project>/.opencode<cwd>/.opencode$XDG_CONFIG_HOME/opencode(e.g.~/.config/opencode)
If your config lives in a non-standard location, override with
configDirs:
{
"plugin": [
[
"opencode-plugin-md-expand@^0.1.0",
{
"configDirs": ["./my-custom-config"],
},
],
],
}
configDirsreplaces the three auto-derived defaults entirely. If you only want to add directories while keeping the defaults (project root, current working directory,$XDG_CONFIG_HOME), useextraConfigDirsinstead:
{
"plugin": [
[
"opencode-plugin-md-expand@^0.1.0",
{
"extraConfigDirs": ["{env:HOME}/.config/opencode/extra-md"],
},
],
],
}
OpenCode applies {env:VAR} substitution to the raw JSON before the
plugin processes it, so {env:HOME} expands to your home directory at
config-load time.
The CLI and wrapper scripts also respect OPENCODE_CONFIG_DIR for
runtime overrides.
Options
| Option | Description |
|---|---|
configDirs |
string[] · default ["<project>/.opencode", "<cwd>/.opencode", "$XDG_CONFIG_HOME/opencode"]. Ordered fallback dirs for relative {{ file="./..." }} includes. Replaces defaults entirely. See Configuration. |
extraConfigDirs |
string[] · default []. Additional dirs appended to auto-derived defaults. Ignored when configDirs is set. See Configuration. |
maxDepth |
number · default 10. Maximum recursive file include depth. At limit, file templates stay literal; env/arg/if still expand. |
debug |
boolean · env-based. Write debug logs. Also enabled by OPENCODE_PLUGIN_MD_EXPAND_DEBUG=1. |
logDir |
string · default <configDirs[0]>/plugins/.logs/opencode-plugin-md-expand. Debug log directory. |
initialArgs |
Record<string, string> · default {}. Key-value pairs injected as {{arg:*}} variables for every expansion. Useful for global defaults like mode or domain. |
cache |
boolean · default false. Cache raw and recursively-expanded file content across plugin transform calls. Also enabled by OPENCODE_PLUGIN_MD_EXPAND_CACHE=1; leave off while editing config files. |
Template grammar
File includes
{{ file="./rules/general.md" }}
{{ file="~/.secrets/project-context" }}
{{ file="./template.md" domain=correctness mode="cached review" }}
Path rules:
~/...resolves to$HOME./...and../...resolve relative to<project>/or<cwd>/- other relative paths resolve relative to
<project>/or<cwd>/ - if
configDirsis set, missing./.../../...paths fall back to those directories in order
Args
{{ file="./template.md" title="Plan Review" }}
In template.md:
# {{arg:title}}
Arg rules:
- undefined
{{arg:key}}expands to an empty string - args are scoped to one file include
- nested file includes do not inherit parent args unless the parent
{{ file=... }}directive includes them askey=valuepairs - arg values are literal for env/file tokens, but
{{arg:...}}references can be nested within arg values and are resolved recursively
Path resolution
Run {{path:.cargo/verify.sh}} before committing.
Source: {{gitpath:src/main.ts}}
Path tokens:
{{path:./file.sh}}-- relative to current working directory{{path:../sibling/file.txt}}--../relative to current working directory{{path:~/file.txt}}--~/expands to$HOME{{gitpath:src/main.ts}}-- relative to git root{{gitpath:...}}-- fallback to base directory outside git repo
{{path:...}} and {{gitpath:...}} are tokens that
emit an absolute path string without reading the file. They are resolved
during the same expansion pass as {{env:...}} and {{arg:...}}.
Rules:
- The path must be a literal string; env/arg interpolation inside the path value is not supported.
{{gitpath:...}}discovers the git root viagit rev-parse --show-toplevel, with a module-level cache so repeated lookups in the same expansion are free.
Conditionals
{{ if=mode==cached }}
Use cached review scope.
{{ else }}
Use cacheless review scope.
{{ endif }}
{{ if=mode!=cached }}
Use cacheless review scope.
{{ endif }}
{{ if=env:CI }}
CI-only instructions.
{{ endif }}
{{ if=env:CI!=true }}
Local-only instructions.
{{ endif }}
Condition operators:
==- exact equality (if=arg==valueincludes when arg equals value)!=- inequality (if=arg!=valueincludes when arg differs from value;if=arg!=includes when arg is empty or absent)
CLI
Install globally or locally:
npm install -g opencode-plugin-md-expand
opencode-plugin-md-expand --help
Or add to devDependencies and use via npx:
{
"devDependencies": {
"opencode-plugin-md-expand": "^0.1.0",
},
"scripts": {
"validate": "opencode-plugin-md-expand validate --config-dir config",
},
}
To run from source (without publishing), use bun src/cli/cli.ts
instead.
Validate templates
Useful in git hooks and CI:
opencode-plugin-md-expand validate --config-dir config config/agent config/command config/rules
Validation fails on:
- missing file includes
- empty file include paths
- file include cycles (circular references where A includes B which includes A)
- unexpanded
{{ file=... }}tokens - unexpanded
{{ if=... }}/{{ endif }}markers - unexpanded
{{arg:...}}or{{env:...}}tokens - unexpanded
{{path:...}}or{{gitpath:...}}tokens
Git hook example:
#!/usr/bin/env sh
set -eu
repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"
opencode-plugin-md-expand validate --config-dir config config/agent config/command config/rules
Render one template
Useful for prompt debugging:
opencode-plugin-md-expand render --config-dir config agent/_plan/finalize.md
opencode-plugin-md-expand render --config-dir config rules/groups/style/self-wording.md -o /tmp/rendered.md
Pass top-level args manually:
opencode-plugin-md-expand render --config-dir config --arg mode=cached agent/example.md
Enable debug:
opencode-plugin-md-expand render --config-dir config --debug agent/example.md
cat config/plugins/.logs/opencode-plugin-md-expand/debug.log
Development
Requires Bun.
bun install # install dependencies
bun run typecheck # check TypeScript types (no runtime)
bun test # run tests
bun run build # compile TypeScript to dist/
bun run check # run all checks (typecheck + test + format + build)
bun run format # auto-format all files
bun run format:check # check formatting (CI enforces this)
Editor setup: .vscode/settings.json enables format-on-save with oxc.
Local path
For development or private forks, reference the plugin directory directly:
{
"plugin": ["plugins/opencode-plugin-md-expand"],
}
No wrapper .ts file needed: OpenCode detects local plugin directories
and loads them directly from index.ts or package.json.
Publishing
GitHub Actions publishes the package to npm when you push a v* tag.
Release workflow:
npm version patch|minor|major # bumps package.json + creates matching git tag
git push --follow-tags # pushes commit + tag → triggers publish.yml
The CI workflow runs a full check (typecheck, test,
format:check, build) before publishing with --provenance,
creating a cryptographically signed link between the published version
and its originating git commit.
Manual fallback:
npm login
npm publish --provenance --access public
Benchmark prompt transforms
The benchmark harness uses Tinybench and copied fixtures from a real OpenCode config:
bun run bench
It measures the experimental.chat.system.transform hook for simple
imports, deep/complex imports, and no-op static markdown. Override run
length when needed:
MD_EXPAND_BENCH_TIME_MS=5000 MD_EXPAND_BENCH_MIN_ITERATIONS=512 bun run bench
Package shape
OpenCode npm plugin entrypoint is the default export:
export default {
id: "opencode-plugin-md-expand",
server: MdExpandPlugin,
};
License
MIT