Cross-platform dotfiles managed by dotter.
AGENTS.md: repo-level architecture, package organization, template conventions, variable strategy.dotter/AGENTS.md: Dotter internals,pre_deploy/post_deployscripts, post-deploy patching, and other implementation exceptions
If you touch .dotter/, read .dotter/AGENTS.md first.
Read this section before modifying any agent configs.
Do not assume default upstream paths like ~/.codex or ~/.claude.
This repo manages configs for multiple AI coding agents. Their paths follow XDG spec (~/.config/<name>) instead of defaults (~/.<name>):
| Agent | Env Var | Path |
|---|---|---|
| Codex | CODEX_HOME |
~/.config/codex |
| Claude | CLAUDE_CONFIG_DIR |
~/.config/claude |
| Pi | PI_CODING_AGENT_DIR |
~/.config/pi |
| Copilot | COPILOT_HOME |
~/.config/copilot |
| Gemini | GEMINI_CLI_HOME |
~/.config/gemini |
| Qoder | QODERCLI_HOME |
~/.config/qoder |
When reading or writing agent configs, use these paths.
This repo uses Dotter packages as a layered grouping system rather than a flat list of unrelated folders.
- Base packages hold concrete config payloads for one tool or concern
- Higher-level packages compose those base packages into reusable environments
- Current agent-related grouping is intentionally nested:
agent: shared entry package for agent-facing configs and common glueagent-tunnel: agent-adjacent remote/service integrationsagent-runtime: local runtime support for agent workflows
When changing package names, dependencies, or file mappings, preserve this layered relationship unless the change is explicitly a package-architecture refactor.
When a package should remain in the default package groups but must not overwrite the real live config on a specific machine, remap its deploy target to a blackhole directory such as ~/.cache/dotter-blackhole/<package> in a machine-specific include like .dotter/wsl.toml.
- Use this when the package is still part of shared package composition, but the actual live path is intentionally managed locally and should not be overwritten by Dotter.
- Prefer this over removing the package from global groups when you still want dependency structure, variables, and repo defaults to stay intact.
- Keep the blackhole target obviously non-live and disposable, typically under
~/.cache/dotter-blackhole/. - Document the real live config ownership nearby so future changes do not accidentally assume Dotter still controls the production path.
Use command_success to detect WSL:
Dotter variables in this repo fall into two categories: global.toml / local.toml override variables, and shared secrets injected inline with rbw get inside templates.
# .dotter/global.toml
[package_name.variables]
scalar_value = ""
nested_value = { key_a = "", key_b = "" }# .dotter/local.toml
[variables]
scalar_value = "local"
nested_value = { key_b = "overridden" }[package.variables]inglobal.tomldefines package-level placeholders and schema.local.tomldoes not use[package.variables]; it only uses top-level[variables].- Dotter first merges variables from the selected packages, then recursively overrides them with top-level
[variables]fromlocal.tomlby variable name. - Scalar values with the same name are replaced; tables with the same name are recursively merged by key.
- If there is no sensible default, prefer an empty placeholder in
global.tomlto reduce template branching. This is a schema choice, not a recommended default value.
- Per-machine / not synced across machines: define a placeholder in
global.toml, then override it from top-level[variables]in.dotter/local.toml. - Shareable secrets: keep using inline
rbw getin templates or scripts instead of storing them inlocal.toml. - Detailed constraints for
.dotter/pre_deploy.shand.dotter/post_deploy.shlive in.dotter/AGENTS.md.
| Variable | Shape | Source | Notes |
|---|---|---|---|
slock_api_key |
string | global + local |
May be empty; the service should still render |
slock_wss_proxy |
string | global + local |
May be empty; render no proxy env when unset |
git_repo_identities |
table | global + local |
Keyed by identity name; values include repo_dir, name, and email |
skm_local_packages |
array of tables | global + local |
Each item includes repo, with optional skills |
mihomo_direct_suffixes |
array of strings | global + local |
May be empty; render no extra rules when unset |
Add repo-scoped Git identities in .dotter/local.toml under variables.git_repo_identities:
[variables.git_repo_identities.company_a]
repo_dir = "~/Documents/company-a.repos/"
name = "your-work-name"
email = "your-work-email@company-a.example"
[variables.git_repo_identities.company_b]
repo_dir = "~/Documents/company-b.repos/"
name = "your-other-work-name"
email = "your-other-work-email@company-b.example"Each entry generates git/generated/<key>.conf, then Dotter links it to ~/.config/git/generated/<key>.conf.
Inject secrets from Bitwarden using rbw get:
The replace ... '\n' '' removes trailing newline from rbw output.
Workflow:
rbw add my-api-key- store secret in Bitwarden- Use
{{replace (command_output 'rbw get my-api-key') '\n' ''}}in template dotter deploy --force- deploy with secrets injected
Always wrap #each with #if to handle undefined variables gracefully.
Dotter's #each fails when the variable is undefined, breaking template rendering. Wrap it with #if to skip the block when the variable is missing:
This pattern ensures templates render correctly even when my_items is not defined in local.toml.
When a YAML file is both a Dotter template and pre-commit formatted with yamlfmt, keep these rules:
# {{#each skm_local_packages}}
- repo: "{{repo}}"
# {{#if skills}}
skills:
# {{#each skills}}
- "{{this}}"
# {{/each}}
# {{/if}}
# {{/each}}- Put control blocks like
#eachand#ifin YAML comments so YAML formatters and editors can still parse the file. - Quote inline Handlebars values in YAML scalars, for example
repo: "{{repo}}"and- "{{this}}". Unquoted forms may be rewritten into invalid{ { repo } }style text by formatters. - Dotter treats any file containing
{{as a template;.tmplis not a required or special suffix. - After deploy, generated YAML files may contain empty
#lines left by commented control blocks. It is safe to delete lines matching^[[:space:]]*#[[:space:]]*$in both the rendered target and the Dotter cache copy so future deploys stay in sync.
dotter deploy # deploy dotfiles
dotter deploy --force # overwrite existing files
dotter deploy --dry-run # preview changes