Universal agent configuration for Pi, OpenCode, and future AI coding agents.
| Directory | Purpose | Install target |
|---|---|---|
skills/ |
Universal skills (all agents) | ~/.agents/skills/ |
opencode/agents/ |
OpenCode subagent definitions | ~/.config/opencode/agents/ |
pi/skills/ |
Pi-specific skills | ~/.pi/agent/skills/ |
pi/extensions/ |
Pi TypeScript extensions | ~/.pi/agent/extensions/ |
pi/themes/ |
Pi terminal themes | ~/.pi/agent/themes/ |
pi/permissions.json |
Pi permission config | ~/.pi/agent/permissions.json |
opencode/skills/ |
OpenCode-specific skills | ~/.config/opencode/skills/ |
opencode/commands/ |
OpenCode commands | ~/.config/opencode/commands/ |
opencode/extensions/ |
OpenCode extensions | agent-specific |
nix/pi-external-extensions.nix |
External Pi extension registry | ~/.pi/agent/npm/node_modules/<name>/ + settings.json |
Add as a flake input:
# flake.nix
inputs.dot-agents = {
url = "github:<you>/dot-agents";
inputs.nixpkgs.follows = "nixpkgs";
};Import the module and enable:
# home.nix
imports = [ inputs.dot-agents.homeModules.default ];
programs.dot-agents = {
enable = true;
};Everything is auto-discovered from the repository and installed to the appropriate directories. You can still add extra OpenCode commands or override Pi settings:
programs.dot-agents = {
enable = true;
opencode.commands = {
my-command = ./path/to/command.md;
};
pi = {
extensions = null; # auto-discover all
# Store-backed sources are overlaid into ~/.pi/agent/extensions/.
extraExtensions = {
"company-extension.ts" = ./path/to/company-extension.ts;
};
permissions = {
read = { "*" = "allow"; };
};
};
};For non-Nix systems, use the auto-generated stow branch which vendors all external skills.
See the README.md on the stow branch for setup, update, and usage instructions:
git clone --branch stow https://github.com/<you>/dot-agents.git ~/dot-agents
cd ~/dot-agents
cat README.mdThe stow branch is automatically updated by a GitHub Action on every push to development.
dot-agents/
├── skills/ # Universal skills (all agents discover these)
├── pi/ # Pi-specific artifacts
│ ├── skills/
│ ├── extensions/
│ └── themes/
├── opencode/ # OpenCode-specific artifacts
│ ├── agents/ # Subagent definitions (.md files)
│ ├── skills/
│ ├── commands/
│ └── extensions/
├── nix/ # Nix flake, packages, and Home Manager modules
└── home/ # Only exists in the `stow` branch (auto-generated)
The permission-system extension adds configurable permission gates and secret masking to Pi tools.
When a tool matches an "ask" rule, a simple prompt appears with three options:
Yes, No, and Explain. When a tool matches a "cloak" rule, the call is allowed
but sensitive values in the result are masked (read tool only in v1).
programs.dot-agents = {
enable = true;
pi = {
# Auto-discover all extensions in pi/extensions/
extensions = null;
# Permission rules (mirrors OpenCode's permission system)
permissions = {
read = {
"*" = "allow";
# Secrets & credentials — cloak instead of deny so the agent sees variable names but not values
".env" = "cloak";
"*.env" = "cloak";
"*.env.*" = "cloak";
"*.envrc" = "deny";
"secrets/*" = "deny";
# Private keys & auth
".ssh/*" = "deny";
".gnupg/*" = "deny";
".config/1password/*" = "deny";
"*.key" = "deny";
"*.pem" = "deny";
"*.p12" = "deny";
"*.pfx" = "deny";
# Cloud/container credentials
".aws/*" = "deny";
".docker/*" = "deny";
".kube/*" = "deny";
# Version control internals
".git/*" = "deny";
".gitmodules" = "deny";
# Build artifacts
"node_modules/*" = "deny";
".venv/*" = "deny";
"venv/*" = "deny";
"dist/*" = "deny";
"build/*" = "deny";
"target/*" = "deny";
};
write = {
"*" = "ask";
".env" = "deny";
".git/*" = "deny";
"node_modules/*" = "deny";
".venv/*" = "deny";
"venv/*" = "deny";
};
edit = {
"*" = "ask";
".env" = "deny";
".git/*" = "deny";
"node_modules/*" = "deny";
".venv/*" = "deny";
"venv/*" = "deny";
};
bash = {
"*" = "ask";
"ls*" = "allow";
"pwd" = "allow";
"git status*" = "allow";
"git diff*" = "allow";
"git log*" = "allow";
"dex *" = "allow";
};
webfetch = "ask";
};
# Mask patterns applied to read results when a rule resolves to "cloak"
masks = {
read = {
".env" = { pattern = "(=).+"; replace = "$1"; };
"*.env" = { pattern = "(=).+"; replace = "$1"; };
"*.env.*" = { pattern = "(=).+"; replace = "$1"; };
"*.vars*" = { pattern = "(=).+"; replace = "$1"; };
};
};
};
};Copy pi/extensions/permission-system.ts to ~/.pi/agent/extensions/ and create
~/.pi/agent/permissions.json:
{
"rules": {
"read": {
"*": "allow",
".env": "cloak"
},
"bash": {
"*": "ask",
"ls*": "allow",
"pwd": "allow"
}
},
"masks": {
"read": {
".env": { "pattern": "(=).+", "replace": "$1" }
}
}
}Rules and masks are merged with project-local .pi/permissions.json (project takes precedence).
In addition to local extensions in pi/extensions/, dot-agents supports external
Pi extensions — third-party npm packages that Pi loads via its package manager.
The registry at nix/pi-external-extensions.nix is the single source of truth
for which external extensions are available.
Pi discovers external extensions through settings.json → packages:
{
"packages": ["pi-mcp-adapter"]
}Pi resolves packages from ~/.pi/agent/npm/node_modules/<name>/ (global) or .pi/npm/node_modules/<name>/
(project). Each package declares its extension entry point in its package.json
under the pi.extensions field.
nix/pi-external-extensions.nix ← single source of truth (registry)
├── Nix: packages.nix builds each npm package as a fixed-output derivation
│ home-manager.nix deploys to ~/.pi/agent/npm/node_modules/<name>/
│ + merges packages into ~/.pi/agent/settings.json
└── Stow: stow-tree.nix includes a settings.json with packages array
setup.sh runs `pi install npm:<name>` if pi CLI is available
Both paths are reproducible:
- Nix: fixed-output derivations with content hashes
- Stow:
pi installuses npm registry (equivalent tonpm cideterminism)
programs.dot-agents = {
enable = true;
pi = {
# Auto-discover all external extensions from registry
externalExtensions = null;
# Or pick specific ones:
externalExtensions = ["pi-mcp-adapter"];
# Or disable all:
externalExtensions = [];
};
};Available extensions: pi-mcp-adapter
The stow branch includes a .pi/agent/settings.json with external packages.
setup.sh runs pi install npm:<name> for each if pi is available.
If Pi isn't installed yet, the settings are in place and packages will be
installed when pi install is run later.
Step 1 — Register the extension
Add an entry to nix/pi-external-extensions.nix:
"my-extension" = {
type = "npm";
package = "my-extension"; # npm package name
version = "1.0.0"; # exact version to pin
description = "What this extension does";
hash = ""; # fill in step 2
npmDepsHash = ""; # fill in step 3
};Step 2 — Get the tarball hash
nix-prefetch-url https://registry.npmjs.org/my-extension/-/my-extension-1.0.0.tgz
# Copy the sha256 output into the `hash` fieldStep 3 — Get the npm dependencies hash
Temporarily set npmDepsHash = "" in the registry entry, then:
nix build .#my-extension --rebuild 2>&1 | grep 'got:'
# Copy the sha256 from the error message into `npmDepsHash`This is a fixed-output derivation — it only needs to build once, then Nix caches the result.
Step 4 — Add to stow setup (non-Nix users)
Add the package name to the EXTERNAL_PI_PACKAGES array in stow/setup.sh:
EXTERNAL_PI_PACKAGES=(
"pi-mcp-adapter"
"my-extension" # ← add here
)Also add it to the array inside the post-merge hook in the same file.
Step 5 — Verify
Nix: after Home Manager rebuild, the extension is in ~/.pi/agent/npm/node_modules/<name>/
and listed in ~/.pi/agent/settings.json. Start Pi and confirm the extension
loads via /mcp (for pi-mcp-adapter) or the extension's own commands.
Stow: run ./setup.sh — if pi is installed, the extension is installed
automatically. Otherwise Pi will use it on next startup after pi install npm:<name>.
Summary of files to change when adding an extension:
nix/pi-external-extensions.nix— registry entry with hashesstow/setup.sh— add toEXTERNAL_PI_PACKAGESarray (×2: main + post-merge hook)
That's it. The rest is automatic — Nix builds and deploys the derivation,
Home Manager merges settings.json, and stow runs pi install.
| Value | Behavior |
|---|---|
"allow" |
Execute without prompting |
"deny" |
Block immediately |
"ask" |
Show Yes/No/Explain prompt |
"cloak" |
Allow, but mask secrets in the result (read tool only in v1) |
Each mask is a regex applied to the text content of read results:
pattern— JS RegExp pattern stringreplace— Replacement template using native.replace()semantics (e.g."$1","$&")flags— RegExp flags, defaults to"g"
If replace is omitted, the matched text is replaced with asterisks (*).
/permissions— show current rules and masks/permissions-reload— reload config from disk
- Create a directory under
skills/<skill-name>/ - Add a
SKILL.mdwith proper YAML frontmatter - Register the skill in
nix/skills.nix - Rebuild or push to trigger the
stowbranch update