Merge pull request 'feature/agents-rework' (#1) from feature/agents-rework into master
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
7
.pi-lens/cache/jscpd.json
vendored
Normal file
7
.pi-lens/cache/jscpd.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"clones": [],
|
||||||
|
"duplicatedLines": 0,
|
||||||
|
"totalLines": 0,
|
||||||
|
"percentage": 0
|
||||||
|
}
|
||||||
3
.pi-lens/cache/jscpd.meta.json
vendored
Normal file
3
.pi-lens/cache/jscpd.meta.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-04-11T04:17:20.531Z"
|
||||||
|
}
|
||||||
9
.pi-lens/cache/knip.json
vendored
Normal file
9
.pi-lens/cache/knip.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"issues": [],
|
||||||
|
"unusedExports": [],
|
||||||
|
"unusedFiles": [],
|
||||||
|
"unusedDeps": [],
|
||||||
|
"unlistedDeps": [],
|
||||||
|
"summary": "Failed to parse output"
|
||||||
|
}
|
||||||
3
.pi-lens/cache/knip.meta.json
vendored
Normal file
3
.pi-lens/cache/knip.meta.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-04-11T04:17:21.374Z"
|
||||||
|
}
|
||||||
3
.pi-lens/cache/session-start-guidance.json
vendored
Normal file
3
.pi-lens/cache/session-start-guidance.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"content": "📌 pi-lens active — as you work on this project, fix any errors you encounter (including pre-existing). Prefer: lsp_navigation for definitions/references, ast_grep_search for code patterns, grep for text/TODO search."
|
||||||
|
}
|
||||||
3
.pi-lens/cache/session-start-guidance.meta.json
vendored
Normal file
3
.pi-lens/cache/session-start-guidance.meta.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-04-11T04:21:36.939Z"
|
||||||
|
}
|
||||||
3
.pi-lens/cache/todo-baseline.json
vendored
Normal file
3
.pi-lens/cache/todo-baseline.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
3
.pi-lens/cache/todo-baseline.meta.json
vendored
Normal file
3
.pi-lens/cache/todo-baseline.meta.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"timestamp": "2026-04-11T04:21:36.940Z"
|
||||||
|
}
|
||||||
6
.pi-lens/turn-state.json
Normal file
6
.pi-lens/turn-state.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"files": {},
|
||||||
|
"turnCycles": 0,
|
||||||
|
"maxCycles": 3,
|
||||||
|
"lastUpdated": "2026-04-11T04:17:22.397Z"
|
||||||
|
}
|
||||||
55
AGENTS.md
55
AGENTS.md
@@ -106,6 +106,37 @@ meta = with lib; {
|
|||||||
|
|
||||||
**Shared lib**: `portsLib = import ../../lib/ports.nix { inherit lib; }; portHelpers = portsLib.mkPortHelpers { ... };`
|
**Shared lib**: `portsLib = import ../../lib/ports.nix { inherit lib; }; portHelpers = portsLib.mkPortHelpers { ... };`
|
||||||
|
|
||||||
|
## LIBRARY FUNCTIONS
|
||||||
|
|
||||||
|
### `lib.ports`
|
||||||
|
|
||||||
|
Port management utilities. See [Port Management](#port-management).
|
||||||
|
|
||||||
|
### `lib.agents`
|
||||||
|
|
||||||
|
Harness-agnostic agent management. Reads canonical `agent.toml` from the AGENTS
|
||||||
|
flake input and renders tool-specific configs.
|
||||||
|
|
||||||
|
**Functions:**
|
||||||
|
|
||||||
|
| Function | Purpose |
|
||||||
|
|----------|--------|
|
||||||
|
| `loadCanonical { agentsInput }` | Load canonical agents from AGENTS flake |
|
||||||
|
| `renderForOpencode { pkgs, canonical, modelOverrides }` | Render to OpenCode file-based agents |
|
||||||
|
| `renderForClaudeCode { pkgs, canonical, modelOverrides }` | Render to Claude Code agents + settings.json |
|
||||||
|
| `renderForPi { pkgs, canonical }` | Render to Pi AGENTS.md + SYSTEM.md |
|
||||||
|
| `renderForTool { pkgs, agentsInput, tool, modelOverrides }` | Dispatch to correct renderer |
|
||||||
|
| `shellHookForTool { pkgs, agentsInput, tool, modelOverrides }` | Generate devShell shellHook |
|
||||||
|
|
||||||
|
### `lib.coding-rules`
|
||||||
|
|
||||||
|
Coding rules injection (renamed from `lib.opencode-rules`). The old name still works.
|
||||||
|
|
||||||
|
| Function | Purpose |
|
||||||
|
|----------|--------|
|
||||||
|
| `mkCodingRules { agents, languages, concerns, frameworks }` | Generate rules config + shellHook |
|
||||||
|
| `mkOpencodeRules` | Backward-compat alias for `mkCodingRules` |
|
||||||
|
|
||||||
## PORT MANAGEMENT
|
## PORT MANAGEMENT
|
||||||
|
|
||||||
Central port management: `config.m3ta.ports.get "service"` with host-specific via `hostOverrides`
|
Central port management: `config.m3ta.ports.get "service"` with host-specific via `hostOverrides`
|
||||||
@@ -168,3 +199,27 @@ Use `td usage -q` for subsequent reads.
|
|||||||
- `td version` - Check version
|
- `td version` - Check version
|
||||||
|
|
||||||
For full workflow details, see the [td documentation](./docs/packages/td.md).
|
For full workflow details, see the [td documentation](./docs/packages/td.md).
|
||||||
|
|
||||||
|
## MIGRATION: Agent System (OpenCode → Canonical TOML)
|
||||||
|
|
||||||
|
The agent system was migrated from embedded `agents.json` to harness-agnostic
|
||||||
|
canonical `agent.toml` + `system-prompt.md` in the AGENTS repo. Renderers in
|
||||||
|
`lib/agents.nix` generate tool-specific configs.
|
||||||
|
|
||||||
|
### What changed in this repo
|
||||||
|
|
||||||
|
- **`lib/agents.nix`**: New — 3 renderers (OpenCode, Claude Code, Pi) + dispatcher + shellHook
|
||||||
|
- **`lib/coding-rules.nix`**: Renamed from `opencode-rules.nix`, `mkCodingRules` replaces `mkOpencodeRules`
|
||||||
|
- **`modules/home-manager/coding/agents/`**: New — per-tool HM sub-modules
|
||||||
|
- **`modules/home-manager/coding/opencode.nix`**: Slimmed — no longer handles agents/skills/context
|
||||||
|
- **`flake.nix`**: Exports new `agents` HM module
|
||||||
|
|
||||||
|
### What the user must do
|
||||||
|
|
||||||
|
See `modules/home-manager/AGENTS.md` for the full migration guide. Summary:
|
||||||
|
|
||||||
|
1. Move `agentsInput`/`externalSkills` from `coding.opencode` to `coding.agents.opencode`
|
||||||
|
2. Add `modelOverrides` with previously hardcoded model strings
|
||||||
|
3. Run `home-manager switch`
|
||||||
|
4. Remove legacy `agents.json` + `prompts/*.txt` from AGENTS repo
|
||||||
|
5. Remove `lib.agentsJson` backward-compat bridge from AGENTS `flake.nix`
|
||||||
|
|||||||
@@ -32,7 +32,13 @@ modules/home-manager/
|
|||||||
│ └── zellij-ps.nix
|
│ └── zellij-ps.nix
|
||||||
└── coding/ # Development tools
|
└── coding/ # Development tools
|
||||||
├── default.nix # Aggregates coding modules
|
├── default.nix # Aggregates coding modules
|
||||||
└── editors.nix
|
├── editors.nix
|
||||||
|
├── opencode.nix # OpenCode non-agent config
|
||||||
|
└── agents/ # Per-tool agent deployment
|
||||||
|
├── default.nix
|
||||||
|
├── opencode.nix
|
||||||
|
├── claude-code.nix
|
||||||
|
└── pi.nix
|
||||||
```
|
```
|
||||||
|
|
||||||
## Importing Modules
|
## Importing Modules
|
||||||
@@ -197,6 +203,61 @@ m3ta.coding.editors = {
|
|||||||
|
|
||||||
**Documentation**: [Editors Module](../modules/home-manager/coding/editors.md)
|
**Documentation**: [Editors Module](../modules/home-manager/coding/editors.md)
|
||||||
|
|
||||||
|
### `coding.opencode`
|
||||||
|
|
||||||
|
OpenCode AI coding assistant (non-agent config: theme, formatter, plugins).
|
||||||
|
|
||||||
|
```nix
|
||||||
|
coding.opencode = {
|
||||||
|
enable = true;
|
||||||
|
ohMyOpencodeSettings = {
|
||||||
|
agents.sisyphus.model = "anthropic/claude-opus-4-5";
|
||||||
|
};
|
||||||
|
extraSettings = {
|
||||||
|
provider.anthropic.name = "Anthropic";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### `coding.agents.opencode`
|
||||||
|
|
||||||
|
OpenCode agent deployment from canonical TOML definitions.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
coding.agents.opencode = {
|
||||||
|
enable = true;
|
||||||
|
agentsInput = inputs.agents;
|
||||||
|
modelOverrides = {
|
||||||
|
chiron = "anthropic/claude-sonnet-4";
|
||||||
|
};
|
||||||
|
externalSkills = [
|
||||||
|
{ src = inputs.skills-anthropic; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### `coding.agents.claude-code`
|
||||||
|
|
||||||
|
Claude Code agent deployment from canonical TOML definitions.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
coding.agents.claude-code = {
|
||||||
|
enable = true;
|
||||||
|
agentsInput = inputs.agents;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### `coding.agents.pi`
|
||||||
|
|
||||||
|
Pi agent deployment from canonical TOML definitions.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
coding.agents.pi = {
|
||||||
|
enable = true;
|
||||||
|
agentsInput = inputs.agents;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
## Common Patterns
|
## Common Patterns
|
||||||
|
|
||||||
### Module Configuration
|
### Module Configuration
|
||||||
|
|||||||
@@ -79,6 +79,7 @@
|
|||||||
default = import ./modules/home-manager;
|
default = import ./modules/home-manager;
|
||||||
ports = import ./modules/home-manager/ports.nix;
|
ports = import ./modules/home-manager/ports.nix;
|
||||||
opencode = import ./modules/home-manager/coding/opencode.nix;
|
opencode = import ./modules/home-manager/coding/opencode.nix;
|
||||||
|
agents = import ./modules/home-manager/coding/agents;
|
||||||
zellij-ps = import ./modules/home-manager/zellij-ps.nix;
|
zellij-ps = import ./modules/home-manager/zellij-ps.nix;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
419
lib/agents.nix
Normal file
419
lib/agents.nix
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
# Harness-agnostic agent management utilities
|
||||||
|
#
|
||||||
|
# This module provides functions to load canonical agent definitions and
|
||||||
|
# render them for different AI coding tools (OpenCode, Claude Code, Pi).
|
||||||
|
#
|
||||||
|
# Usage in your configuration:
|
||||||
|
#
|
||||||
|
# let
|
||||||
|
# m3taLib = inputs.m3ta-nixpkgs.lib.${system};
|
||||||
|
# canonical = m3taLib.agents.loadCanonical { agentsInput = inputs.agents; };
|
||||||
|
#
|
||||||
|
# # Render for a specific tool
|
||||||
|
# rendered = m3taLib.agents.renderForOpencode {
|
||||||
|
# inherit pkgs canonical;
|
||||||
|
# modelOverrides = { chiron = "anthropic/claude-sonnet-4"; };
|
||||||
|
# };
|
||||||
|
# in { ... }
|
||||||
|
{lib}: let
|
||||||
|
# ── Shared helpers ─────────────────────────────────────────────
|
||||||
|
# Split a rule string on the LAST colon to get { pattern, action }.
|
||||||
|
# e.g. "rm -rf *:ask" → pattern="rm -rf *", action="ask"
|
||||||
|
# e.g. "/run/agenix/**:deny" → pattern="/run/agenix/**", action="deny"
|
||||||
|
parseRule = ruleStr: let
|
||||||
|
parts = lib.strings.splitString ":" ruleStr;
|
||||||
|
action = lib.last parts;
|
||||||
|
pattern = lib.concatStringsSep ":" (lib.init parts);
|
||||||
|
in {inherit pattern action;};
|
||||||
|
|
||||||
|
agentsLib = {
|
||||||
|
# ── loadCanonical ─────────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# Load canonical agent definitions from the AGENTS flake input.
|
||||||
|
# Returns the canonical attrset from lib.loadAgents (keyed by slug).
|
||||||
|
|
||||||
|
loadCanonical = {agentsInput}: agentsInput.lib.loadAgents;
|
||||||
|
|
||||||
|
# ── OpenCode renderer ─────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# Produces a directory of agent *.md files suitable for
|
||||||
|
# ~/.config/opencode/agents/ (system-level)
|
||||||
|
# .opencode/agents/ (project-level)
|
||||||
|
#
|
||||||
|
# Each file has YAML frontmatter (description, mode, optional model,
|
||||||
|
# optional permission) followed by the agent's systemPrompt content.
|
||||||
|
# The filename (without .md) becomes the agent name in OpenCode.
|
||||||
|
|
||||||
|
renderForOpencode = {
|
||||||
|
pkgs,
|
||||||
|
canonical,
|
||||||
|
modelOverrides ? {},
|
||||||
|
}: let
|
||||||
|
# Render one permission section to YAML lines.
|
||||||
|
# intent-only → single line: " <tool>: <intent>"
|
||||||
|
# intent+rules → nested block
|
||||||
|
renderPermSection = tool: section:
|
||||||
|
if !(section ? rules) || section.rules == []
|
||||||
|
then [" ${tool}: ${section.intent}"]
|
||||||
|
else let
|
||||||
|
parsedRules = map parseRule section.rules;
|
||||||
|
wildcardLine = " \"*\": ${section.intent}";
|
||||||
|
ruleLines = map (r: " \"${r.pattern}\": ${r.action}") parsedRules;
|
||||||
|
in
|
||||||
|
[" ${tool}:"] ++ [wildcardLine] ++ ruleLines;
|
||||||
|
|
||||||
|
renderPermBlock = permissions:
|
||||||
|
if permissions == {} || permissions == null
|
||||||
|
then []
|
||||||
|
else
|
||||||
|
["permission:"]
|
||||||
|
++ lib.concatLists (
|
||||||
|
lib.mapAttrsToList renderPermSection permissions
|
||||||
|
);
|
||||||
|
|
||||||
|
mkFrontmatter = name: agent: let
|
||||||
|
descLine = "description: \"${agent.description}.\"";
|
||||||
|
modeLine = "mode: ${agent.mode}";
|
||||||
|
modelLine =
|
||||||
|
lib.optionalString
|
||||||
|
(modelOverrides ? ${name})
|
||||||
|
"model: ${modelOverrides.${name}}\n";
|
||||||
|
permBlock = renderPermBlock (agent.permissions or {});
|
||||||
|
permLines =
|
||||||
|
if permBlock == []
|
||||||
|
then ""
|
||||||
|
else lib.concatStringsSep "\n" permBlock + "\n";
|
||||||
|
in "---\n${descLine}\n${modeLine}\n${modelLine}${permLines}---\n";
|
||||||
|
|
||||||
|
mkAgentContent = name: agent:
|
||||||
|
(mkFrontmatter name agent) + agent.systemPrompt;
|
||||||
|
|
||||||
|
mkAgentFile = name: agent:
|
||||||
|
pkgs.writeText "${name}.md" (mkAgentContent name agent);
|
||||||
|
|
||||||
|
agentFiles = lib.mapAttrs mkAgentFile canonical;
|
||||||
|
|
||||||
|
copyCommands = lib.concatStringsSep "\n" (
|
||||||
|
lib.mapAttrsToList (name: file: "cp ${file} $out/${name}.md") agentFiles
|
||||||
|
);
|
||||||
|
in
|
||||||
|
pkgs.runCommand "opencode-agents" {} ''
|
||||||
|
mkdir -p $out
|
||||||
|
${copyCommands}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# ── Claude Code renderer ──────────────────────────────────────
|
||||||
|
#
|
||||||
|
# Produces a directory containing:
|
||||||
|
# .claude/agents/<name>.md — one per agent with YAML frontmatter
|
||||||
|
# .claude/settings.json — permission rules in Claude Code DSL
|
||||||
|
#
|
||||||
|
# Claude Code requires:
|
||||||
|
# - name field: [a-z0-9-]+ (kebab-case)
|
||||||
|
# - description field: required
|
||||||
|
# - All agents are subagents (no primary/subagent distinction)
|
||||||
|
|
||||||
|
renderForClaudeCode = {
|
||||||
|
pkgs,
|
||||||
|
canonical,
|
||||||
|
modelOverrides ? {},
|
||||||
|
}: let
|
||||||
|
# Claude Code permission DSL format: "Tool(pattern)" or just "Tool"
|
||||||
|
# Canonical bash rules → "Bash(pattern)" entries
|
||||||
|
# Canonical edit rules → "Edit(pattern)" entries
|
||||||
|
renderPermAllow = permissions: let
|
||||||
|
bashRules =
|
||||||
|
if !(permissions ? bash)
|
||||||
|
then []
|
||||||
|
else if permissions.bash.intent == "allow"
|
||||||
|
then ["Bash"]
|
||||||
|
else
|
||||||
|
map
|
||||||
|
(r: let parsed = parseRule r; in "Bash(${parsed.pattern})")
|
||||||
|
(lib.filter (r: (parseRule r).action == "allow") (permissions.bash.rules or []));
|
||||||
|
editRules =
|
||||||
|
if !(permissions ? edit)
|
||||||
|
then []
|
||||||
|
else if permissions.edit.intent == "allow"
|
||||||
|
then ["Edit"]
|
||||||
|
else
|
||||||
|
map
|
||||||
|
(r: let parsed = parseRule r; in "Edit(${parsed.pattern})")
|
||||||
|
(lib.filter (r: (parseRule r).action == "allow") (permissions.edit.rules or []));
|
||||||
|
webRules =
|
||||||
|
lib.optional (permissions.webfetch.intent or "" == "allow") "WebFetch";
|
||||||
|
in
|
||||||
|
bashRules ++ editRules ++ webRules;
|
||||||
|
|
||||||
|
renderPermDeny = permissions: let
|
||||||
|
bashRules =
|
||||||
|
if !(permissions ? bash)
|
||||||
|
then []
|
||||||
|
else
|
||||||
|
map
|
||||||
|
(r: let parsed = parseRule r; in "Bash(${parsed.pattern})")
|
||||||
|
(lib.filter (r: (parseRule r).action == "deny") (permissions.bash.rules or []));
|
||||||
|
editRules =
|
||||||
|
if !(permissions ? edit)
|
||||||
|
then []
|
||||||
|
else
|
||||||
|
map
|
||||||
|
(r: let parsed = parseRule r; in "Edit(${parsed.pattern})")
|
||||||
|
(lib.filter (r: (parseRule r).action == "deny") (permissions.edit.rules or []));
|
||||||
|
in
|
||||||
|
bashRules ++ editRules;
|
||||||
|
|
||||||
|
# Build YAML frontmatter for one Claude Code agent .md file.
|
||||||
|
mkClaudeFrontmatter = name: agent: let
|
||||||
|
descLine = "description: \"${agent.description}\"";
|
||||||
|
modelLine =
|
||||||
|
lib.optionalString
|
||||||
|
(modelOverrides ? ${name})
|
||||||
|
"model: ${modelOverrides.${name}}\n";
|
||||||
|
skillsLine =
|
||||||
|
if (agent ? skills) && agent.skills != []
|
||||||
|
then "skills:\n" + lib.concatStringsSep "\n" (map (s: " - ${s}") agent.skills) + "\n"
|
||||||
|
else "";
|
||||||
|
in "---\n${descLine}\n${modelLine}${skillsLine}---\n";
|
||||||
|
|
||||||
|
mkClaudeAgentContent = name: agent:
|
||||||
|
(mkClaudeFrontmatter name agent) + agent.systemPrompt;
|
||||||
|
|
||||||
|
mkClaudeAgentFile = name: agent:
|
||||||
|
pkgs.writeText "${name}.md" (mkClaudeAgentContent name agent);
|
||||||
|
|
||||||
|
agentFiles = lib.mapAttrs mkClaudeAgentFile canonical;
|
||||||
|
|
||||||
|
# Build settings.json with permission rules aggregated from all agents.
|
||||||
|
allAllows = lib.flatten (lib.mapAttrsToList (_: agent: renderPermAllow (agent.permissions or {})) canonical);
|
||||||
|
allDenies = lib.flatten (lib.mapAttrsToList (_: agent: renderPermDeny (agent.permissions or {})) canonical);
|
||||||
|
|
||||||
|
settingsJson = builtins.toJSON {
|
||||||
|
permissions = {
|
||||||
|
allow = lib.unique (lib.sort (a: b: a < b) allAllows);
|
||||||
|
deny = lib.unique (lib.sort (a: b: a < b) allDenies);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsFile = pkgs.writeText "claude-settings.json" settingsJson;
|
||||||
|
|
||||||
|
copyAgentCommands = lib.concatStringsSep "\n" (
|
||||||
|
lib.mapAttrsToList (name: file: "cp ${file} $out/.claude/agents/${name}.md") agentFiles
|
||||||
|
);
|
||||||
|
in
|
||||||
|
pkgs.runCommand "claude-code-agents" {} ''
|
||||||
|
mkdir -p $out/.claude/agents
|
||||||
|
${copyAgentCommands}
|
||||||
|
cp ${settingsFile} $out/.claude/settings.json
|
||||||
|
'';
|
||||||
|
|
||||||
|
# ── Pi renderer ───────────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# This renderer produces:
|
||||||
|
# AGENTS.md — concatenated agent descriptions + specialist listing
|
||||||
|
# SYSTEM.md — primary agent's system prompt (replaces Pi default)
|
||||||
|
# agents/{name}.md — one per agent for pi-subagents (YAML frontmatter + prompt)
|
||||||
|
#
|
||||||
|
# The agents/ files use pi-subagents frontmatter format:
|
||||||
|
# name, description, tools, extensions, model, thinking, skill,
|
||||||
|
# output, defaultReads, defaultProgress, interactive, maxSubagentDepth
|
||||||
|
|
||||||
|
renderForPi = {
|
||||||
|
pkgs,
|
||||||
|
canonical,
|
||||||
|
modelOverrides ? {},
|
||||||
|
primaryAgent ? null,
|
||||||
|
}: let
|
||||||
|
# Find the primary agent (there should be exactly one).
|
||||||
|
primaryAgents = lib.filterAttrs (_: a: a.mode == "primary") canonical;
|
||||||
|
primaryNames = lib.attrNames primaryAgents;
|
||||||
|
primaryName =
|
||||||
|
if primaryAgent != null
|
||||||
|
then primaryAgent
|
||||||
|
else if primaryNames == []
|
||||||
|
then throw "lib.agents.renderForPi: no primary agent found"
|
||||||
|
else builtins.head primaryNames;
|
||||||
|
primary = builtins.getAttr primaryName primaryAgents;
|
||||||
|
|
||||||
|
# Subagents for the specialist listing.
|
||||||
|
subagents = lib.filterAttrs (_: a: a.mode != "primary") canonical;
|
||||||
|
|
||||||
|
# ── Permission → Pi tool mapping ──────────────────────────────
|
||||||
|
#
|
||||||
|
# Pi built-in tools: read, bash, edit, write, grep, find, ls,
|
||||||
|
# mcp, subagent, web_search, fetch_content, etc.
|
||||||
|
# Canonical tools: bash, edit, webfetch, websearch, question, external_directory
|
||||||
|
#
|
||||||
|
# We map canonical permissions to Pi's tool list.
|
||||||
|
# intent=allow → include tool; intent=deny → exclude; intent=ask → include (Pi has no ask granularity)
|
||||||
|
# When specific allow rules exist, the tool is always included (Pi can't restrict by pattern).
|
||||||
|
|
||||||
|
piToolsForAgent = agent: let
|
||||||
|
perms = agent.permissions or {};
|
||||||
|
tools = [];
|
||||||
|
# Always available: read (no permission concept in Pi)
|
||||||
|
addIf = tool: section:
|
||||||
|
if section.intent == "allow" || section.intent == "ask"
|
||||||
|
then [tool]
|
||||||
|
else [];
|
||||||
|
# bash → bash
|
||||||
|
withBash = tools ++ (addIf "bash" (perms.bash or {intent = "ask";}));
|
||||||
|
# edit → edit
|
||||||
|
withEdit = withBash ++ (addIf "edit" (perms.edit or {intent = "deny";}));
|
||||||
|
# webfetch → fetch_content
|
||||||
|
withFetch = withEdit ++ (addIf "fetch_content" (perms.webfetch or {intent = "deny";}));
|
||||||
|
# websearch → web_search
|
||||||
|
withSearch = withFetch ++ (addIf "web_search" (perms.websearch or {intent = "deny";}));
|
||||||
|
in
|
||||||
|
lib.unique (withSearch ++ ["read" "grep" "find" "ls"]);
|
||||||
|
|
||||||
|
# ── Build YAML frontmatter for pi-subagents .md files ──────────
|
||||||
|
mkPiFrontmatter = name: agent: let
|
||||||
|
tools = piToolsForAgent agent;
|
||||||
|
descLine = "description: \"${agent.description}\"";
|
||||||
|
toolsLine = "tools: ${lib.concatStringsSep ", " tools}";
|
||||||
|
model =
|
||||||
|
if modelOverrides ? ${name}
|
||||||
|
then "model: ${modelOverrides.${name}}"
|
||||||
|
else "";
|
||||||
|
skillsLine =
|
||||||
|
if (agent ? skills) && agent.skills != []
|
||||||
|
then "skill: ${lib.concatStringsSep ", " agent.skills}"
|
||||||
|
else "";
|
||||||
|
in
|
||||||
|
"---\n"
|
||||||
|
+ "name: ${name}\n"
|
||||||
|
+ "${descLine}\n"
|
||||||
|
+ "${toolsLine}\n"
|
||||||
|
+ (lib.optionalString (model != "") "${model}\n")
|
||||||
|
+ (lib.optionalString (skillsLine != "") "${skillsLine}\n")
|
||||||
|
+ "---\n";
|
||||||
|
|
||||||
|
mkPiAgentContent = name: agent:
|
||||||
|
(mkPiFrontmatter name agent) + agent.systemPrompt;
|
||||||
|
|
||||||
|
mkPiAgentFile = name: agent:
|
||||||
|
pkgs.writeText "${name}.md" (mkPiAgentContent name agent);
|
||||||
|
|
||||||
|
piAgentFiles = lib.mapAttrs mkPiAgentFile canonical;
|
||||||
|
|
||||||
|
# ── Build AGENTS.md content ───────────────────────────────────
|
||||||
|
primaryDn = primary.display_name or primaryName;
|
||||||
|
specialistEntries = let
|
||||||
|
mkEntry = name: agent: let
|
||||||
|
dn = agent.display_name or name;
|
||||||
|
in
|
||||||
|
"- **" + dn + "**: " + agent.description;
|
||||||
|
in
|
||||||
|
lib.mapAttrsToList mkEntry subagents;
|
||||||
|
agentsMd =
|
||||||
|
"# Agent Instructions\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "## "
|
||||||
|
+ primaryDn
|
||||||
|
+ "\n"
|
||||||
|
+ "\n"
|
||||||
|
+ primary.description
|
||||||
|
+ "\n"
|
||||||
|
+ "\n"
|
||||||
|
+ (
|
||||||
|
if subagents == {}
|
||||||
|
then ""
|
||||||
|
else "## Available Specialists\n\n" + lib.concatStringsSep "\n" specialistEntries + "\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
agentsMdFile = pkgs.writeText "AGENTS.md" agentsMd;
|
||||||
|
systemMdFile = pkgs.writeText "SYSTEM.md" primary.systemPrompt;
|
||||||
|
|
||||||
|
copyAgentCommands = lib.concatStringsSep "\n" (
|
||||||
|
lib.mapAttrsToList (name: file: "cp ${file} $out/agents/${name}.md") piAgentFiles
|
||||||
|
);
|
||||||
|
in
|
||||||
|
pkgs.runCommand "pi-agents" {} ''
|
||||||
|
mkdir -p $out/agents
|
||||||
|
cp ${agentsMdFile} $out/AGENTS.md
|
||||||
|
cp ${systemMdFile} $out/SYSTEM.md
|
||||||
|
${copyAgentCommands}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# ── renderForTool dispatcher ──────────────────────────────────
|
||||||
|
#
|
||||||
|
# Dispatches to the correct renderer by tool name.
|
||||||
|
# tool: "opencode" | "claude-code" | "pi"
|
||||||
|
|
||||||
|
renderForTool = {
|
||||||
|
pkgs,
|
||||||
|
agentsInput,
|
||||||
|
tool,
|
||||||
|
modelOverrides ? {},
|
||||||
|
}: let
|
||||||
|
canonical = agentsInput.lib.loadAgents;
|
||||||
|
in
|
||||||
|
if tool == "opencode"
|
||||||
|
then
|
||||||
|
agentsLib.renderForOpencode {
|
||||||
|
inherit pkgs canonical modelOverrides;
|
||||||
|
}
|
||||||
|
else if tool == "claude-code"
|
||||||
|
then
|
||||||
|
agentsLib.renderForClaudeCode {
|
||||||
|
inherit pkgs canonical modelOverrides;
|
||||||
|
}
|
||||||
|
else if tool == "pi"
|
||||||
|
then
|
||||||
|
agentsLib.renderForPi {
|
||||||
|
inherit pkgs canonical modelOverrides;
|
||||||
|
}
|
||||||
|
else throw "lib.agents.renderForTool: unknown tool '${tool}'. Must be opencode, claude-code, or pi.";
|
||||||
|
|
||||||
|
# ── shellHookForTool ──────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# Generates a shellHook string for use in devShells that symlinks
|
||||||
|
# rendered agent files into the project directory.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# devShells.default = pkgs.mkShell {
|
||||||
|
# shellHook = m3taLib.agents.shellHookForTool {
|
||||||
|
# inherit pkgs;
|
||||||
|
# agentsInput = inputs.agents;
|
||||||
|
# tool = "opencode";
|
||||||
|
# modelOverrides = { chiron = "anthropic/claude-sonnet-4"; };
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
|
||||||
|
shellHookForTool = {
|
||||||
|
pkgs,
|
||||||
|
agentsInput,
|
||||||
|
tool,
|
||||||
|
modelOverrides ? {},
|
||||||
|
}: let
|
||||||
|
rendered = agentsLib.renderForTool {
|
||||||
|
inherit pkgs agentsInput tool modelOverrides;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
if tool == "opencode"
|
||||||
|
then ''
|
||||||
|
# Agent files for OpenCode
|
||||||
|
mkdir -p .opencode/agents
|
||||||
|
ln -sfn ${rendered}/* .opencode/agents/
|
||||||
|
''
|
||||||
|
else if tool == "claude-code"
|
||||||
|
then ''
|
||||||
|
# Agent files for Claude Code
|
||||||
|
mkdir -p .claude/agents
|
||||||
|
ln -sfn ${rendered}/.claude/agents/* .claude/agents/
|
||||||
|
ln -sfn ${rendered}/.claude/settings.json .claude/settings.json
|
||||||
|
''
|
||||||
|
else if tool == "pi"
|
||||||
|
then ''
|
||||||
|
# Agent files for Pi
|
||||||
|
ln -sfn ${rendered}/AGENTS.md AGENTS.md
|
||||||
|
mkdir -p .pi
|
||||||
|
ln -sfn ${rendered}/SYSTEM.md .pi/SYSTEM.md
|
||||||
|
mkdir -p .pi/agents
|
||||||
|
ln -sfn ${rendered}/agents/* .pi/agents/
|
||||||
|
''
|
||||||
|
else throw "lib.agents.shellHookForTool: unknown tool '${tool}'";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
agentsLib
|
||||||
121
lib/coding-rules.nix
Normal file
121
lib/coding-rules.nix
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
# Opencode rules management utilities
|
||||||
|
#
|
||||||
|
# This module provides functions to configure Opencode agent rules across
|
||||||
|
# multiple projects. Rules are defined in the AGENTS repository and can be
|
||||||
|
# selectively included based on language, framework, and concerns.
|
||||||
|
#
|
||||||
|
# Usage in your configuration:
|
||||||
|
#
|
||||||
|
# # In your flake or configuration:
|
||||||
|
# let
|
||||||
|
# m3taLib = inputs.m3ta-nixpkgs.lib.${system};
|
||||||
|
#
|
||||||
|
# rules = m3taLib.coding-rules.mkCodingRules {
|
||||||
|
# agents = inputs.agents;
|
||||||
|
# languages = [ "python" "typescript" ];
|
||||||
|
# concerns = [ "coding-style" "naming" "documentation" ];
|
||||||
|
# frameworks = [ "react" "fastapi" ];
|
||||||
|
# };
|
||||||
|
# in {
|
||||||
|
# # Use in your devShell:
|
||||||
|
# devShells.default = pkgs.mkShell {
|
||||||
|
# shellHook = rules.shellHook;
|
||||||
|
# inherit (rules) instructions;
|
||||||
|
# };
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# The shellHook creates:
|
||||||
|
# - A `.opencode-rules/` symlink pointing to the AGENTS repository rules directory
|
||||||
|
# - An `opencode.json` file with a $schema reference and instructions list
|
||||||
|
#
|
||||||
|
# The instructions list contains paths relative to the project root, all prefixed
|
||||||
|
# with `.opencode-rules/`, making them portable across different project locations.
|
||||||
|
{lib}: let
|
||||||
|
# Create Opencode rules configuration from AGENTS repository
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
# agents: Path to the AGENTS repository (non-flake input)
|
||||||
|
# languages: Optional list of language-specific rules to include
|
||||||
|
# (e.g., [ "python" "typescript" "rust" ])
|
||||||
|
# concerns: Optional list of concern rules to include
|
||||||
|
# Default: [ "coding-style" "naming" "documentation" "testing" "git-workflow" "project-structure" ]
|
||||||
|
# frameworks: Optional list of framework-specific rules to include
|
||||||
|
# (e.g., [ "react" "fastapi" "django" ])
|
||||||
|
# extraInstructions: Optional list of additional instruction paths
|
||||||
|
# (for custom rules outside standard locations)
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# An attribute set containing:
|
||||||
|
# - shellHook: Bash code to create symlink and opencode.json
|
||||||
|
# - instructions: List of rule file paths (relative to project root)
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# mkCodingRules {
|
||||||
|
# agents = inputs.agents;
|
||||||
|
# languages = [ "python" ];
|
||||||
|
# frameworks = [ "fastapi" ];
|
||||||
|
# }
|
||||||
|
# # Returns:
|
||||||
|
# # {
|
||||||
|
# # shellHook = "...";
|
||||||
|
# # instructions = [
|
||||||
|
# # ".opencode-rules/concerns/coding-style.md"
|
||||||
|
# # ".opencode-rules/concerns/naming.md"
|
||||||
|
# # ".opencode-rules/concerns/documentation.md"
|
||||||
|
# # ".opencode-rules/concerns/testing.md"
|
||||||
|
# # ".opencode-rules/concerns/git-workflow.md"
|
||||||
|
# # ".opencode-rules/concerns/project-structure.md"
|
||||||
|
# # ".opencode-rules/languages/python.md"
|
||||||
|
# # ".opencode-rules/frameworks/fastapi.md"
|
||||||
|
# # ];
|
||||||
|
# # }
|
||||||
|
mkCodingRules = {
|
||||||
|
agents,
|
||||||
|
languages ? [],
|
||||||
|
concerns ? [
|
||||||
|
"coding-style"
|
||||||
|
"naming"
|
||||||
|
"documentation"
|
||||||
|
"testing"
|
||||||
|
"git-workflow"
|
||||||
|
"project-structure"
|
||||||
|
],
|
||||||
|
frameworks ? [],
|
||||||
|
extraInstructions ? [],
|
||||||
|
}: let
|
||||||
|
rulesDir = ".opencode-rules";
|
||||||
|
|
||||||
|
# Build instructions list by mapping concerns, languages, frameworks to their file paths
|
||||||
|
# All paths are relative to project root via the rulesDir symlink
|
||||||
|
instructions =
|
||||||
|
(map (c: "${rulesDir}/concerns/${c}.md") concerns)
|
||||||
|
++ (map (l: "${rulesDir}/languages/${l}.md") languages)
|
||||||
|
++ (map (f: "${rulesDir}/frameworks/${f}.md") frameworks)
|
||||||
|
++ extraInstructions;
|
||||||
|
|
||||||
|
# Generate JSON configuration for Opencode
|
||||||
|
opencodeConfig = {
|
||||||
|
"$schema" = "https://opencode.ai/config.json";
|
||||||
|
inherit instructions;
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
inherit instructions;
|
||||||
|
|
||||||
|
# Shell hook to set up rules in the project
|
||||||
|
# Creates a symlink to the AGENTS rules directory and generates opencode.json
|
||||||
|
shellHook = ''
|
||||||
|
# Create/update symlink to AGENTS rules directory
|
||||||
|
ln -sfn ${agents}/rules ${rulesDir}
|
||||||
|
|
||||||
|
# Generate opencode.json configuration file
|
||||||
|
cat > opencode.json <<'OPENCODE_EOF'
|
||||||
|
${builtins.toJSON opencodeConfig}
|
||||||
|
OPENCODE_EOF
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Backward-compat alias
|
||||||
|
mkOpencodeRules = mkCodingRules;
|
||||||
|
in {
|
||||||
|
inherit mkCodingRules mkOpencodeRules;
|
||||||
|
}
|
||||||
@@ -7,6 +7,12 @@
|
|||||||
# Port management utilities
|
# Port management utilities
|
||||||
ports = import ./ports.nix {inherit lib;};
|
ports = import ./ports.nix {inherit lib;};
|
||||||
|
|
||||||
# OpenCode rules injection utilities
|
# Coding rules injection utilities (renamed from opencode-rules)
|
||||||
opencode-rules = import ./opencode-rules.nix {inherit lib;};
|
coding-rules = import ./coding-rules.nix {inherit lib;};
|
||||||
|
|
||||||
|
# Backward-compat alias: opencode-rules → coding-rules
|
||||||
|
opencode-rules = import ./coding-rules.nix {inherit lib;};
|
||||||
|
|
||||||
|
# Agent configuration management utilities
|
||||||
|
agents = import ./agents.nix {inherit lib;};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ home-manager/
|
|||||||
│ └── zellij-ps.nix
|
│ └── zellij-ps.nix
|
||||||
└── coding/ # Development tools
|
└── coding/ # Development tools
|
||||||
├── default.nix # Category aggregator
|
├── default.nix # Category aggregator
|
||||||
└── editors.nix # Neovim + Zed configs
|
├── editors.nix # Neovim + Zed configs
|
||||||
|
├── opencode.nix # OpenCode non-agent config (theme, plugins, formatter)
|
||||||
|
└── agents/ # Per-tool agent deployment (canonical TOML → rendered)
|
||||||
|
├── default.nix
|
||||||
|
├── opencode.nix # File-based agents + skills + context
|
||||||
|
├── claude-code.nix # Claude Code agents + settings.json
|
||||||
|
└── pi.nix # Pi AGENTS.md + SYSTEM.md
|
||||||
```
|
```
|
||||||
|
|
||||||
## Where to Look
|
## Where to Look
|
||||||
@@ -24,11 +30,16 @@ home-manager/
|
|||||||
| Add coding module | `coding/<name>.nix`, import in `coding/default.nix` |
|
| Add coding module | `coding/<name>.nix`, import in `coding/default.nix` |
|
||||||
| Add new category | Create `<category>/default.nix`, import in root `default.nix` |
|
| Add new category | Create `<category>/default.nix`, import in root `default.nix` |
|
||||||
| Module with host ports | Import `../../lib/ports.nix`, use `mkPortHelpers` |
|
| Module with host ports | Import `../../lib/ports.nix`, use `mkPortHelpers` |
|
||||||
|
| Add agent renderer | `coding/agents/<tool>.nix`, import in `coding/agents/default.nix` |
|
||||||
|
|
||||||
## Option Namespaces
|
## Option Namespaces
|
||||||
|
|
||||||
- `cli.*` - CLI tools (e.g., `cli.zellij-ps.enable`)
|
- `cli.*` - CLI tools (e.g., `cli.zellij-ps.enable`)
|
||||||
- `coding.editors.*` - Editor configs (e.g., `coding.editors.neovim.enable`)
|
- `coding.editors.*` - Editor configs (e.g., `coding.editors.neovim.enable`)
|
||||||
|
- `coding.opencode.*` - OpenCode non-agent config (theme, plugins, formatter)
|
||||||
|
- `coding.agents.opencode.*` - OpenCode agent deployment (file-based agents)
|
||||||
|
- `coding.agents.claude-code.*` - Claude Code agent deployment
|
||||||
|
- `coding.agents.pi.*` - Pi agent deployment
|
||||||
- `m3ta.ports.*` - Port management (shared with NixOS)
|
- `m3ta.ports.*` - Port management (shared with NixOS)
|
||||||
|
|
||||||
## Patterns
|
## Patterns
|
||||||
@@ -72,3 +83,153 @@ config = mkMerge [
|
|||||||
| `generateEnvVars` | Available | Not available |
|
| `generateEnvVars` | Available | Not available |
|
||||||
| Output file | `~/.config/m3ta/ports.json` | `/etc/m3ta/ports.json` |
|
| Output file | `~/.config/m3ta/ports.json` | `/etc/m3ta/ports.json` |
|
||||||
| Package access | `pkgs.*` via overlay | `pkgs.*` via overlay |
|
| Package access | `pkgs.*` via overlay | `pkgs.*` via overlay |
|
||||||
|
|
||||||
|
## Agent Modules
|
||||||
|
|
||||||
|
Agent definitions are stored as canonical `agent.toml` + `system-prompt.md` in the
|
||||||
|
[AGENTS repo](https://code.m3ta.dev/m3tam3re/AGENTS). Renderers in `lib/agents.nix`
|
||||||
|
transform these into tool-specific configs. Each tool has its own HM sub-module
|
||||||
|
under `coding/agents/`.
|
||||||
|
|
||||||
|
### OpenCode (`coding.agents.opencode`)
|
||||||
|
|
||||||
|
Renders file-based agents to `~/.config/opencode/agents/*.md`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
coding.agents.opencode = {
|
||||||
|
enable = true;
|
||||||
|
agentsInput = inputs.agents;
|
||||||
|
modelOverrides = {
|
||||||
|
chiron = "anthropic/claude-sonnet-4";
|
||||||
|
};
|
||||||
|
externalSkills = [
|
||||||
|
{ src = inputs.skills-anthropic; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options:** `enable`, `agentsInput`, `modelOverrides`, `externalSkills`
|
||||||
|
|
||||||
|
### Claude Code (`coding.agents.claude-code`)
|
||||||
|
|
||||||
|
Renders agents to `~/.claude/agents/*.md` + `~/.claude/settings.json`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
coding.agents.claude-code = {
|
||||||
|
enable = true;
|
||||||
|
agentsInput = inputs.agents;
|
||||||
|
modelOverrides = {};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options:** `enable`, `agentsInput`, `modelOverrides`
|
||||||
|
|
||||||
|
### Pi (`coding.agents.pi`)
|
||||||
|
|
||||||
|
Renders `AGENTS.md` + `SYSTEM.md` to `~/.pi/agent/`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
coding.agents.pi = {
|
||||||
|
enable = true;
|
||||||
|
agentsInput = inputs.agents;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options:** `enable`, `agentsInput`
|
||||||
|
|
||||||
|
### Project-level usage
|
||||||
|
|
||||||
|
For per-project agent setup via `flake.nix` + `direnv`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
m3taLib.agents.shellHookForTool {
|
||||||
|
inherit pkgs;
|
||||||
|
agentsInput = inputs.agents;
|
||||||
|
tool = "opencode";
|
||||||
|
modelOverrides = { chiron = "anthropic/claude-sonnet-4"; };
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration Guide (OpenCode agents)
|
||||||
|
|
||||||
|
The agent system was migrated from embedded `agents.json` to file-based canonical
|
||||||
|
`agent.toml` definitions. Here is how to migrate your home-manager config.
|
||||||
|
|
||||||
|
### What changed
|
||||||
|
|
||||||
|
| Before | After |
|
||||||
|
|--------|-------|
|
||||||
|
| `coding.opencode.agentsInput` | `coding.agents.opencode.agentsInput` |
|
||||||
|
| `coding.opencode.externalSkills` | `coding.agents.opencode.externalSkills` |
|
||||||
|
| Agents embedded in `config.json` | File-based `~/.config/opencode/agents/*.md` |
|
||||||
|
| Model hardcoded in `agents.json` | Per-machine `modelOverrides` |
|
||||||
|
| `mkOpencodeRules` | `mkCodingRules` (old name still works) |
|
||||||
|
|
||||||
|
### Migration steps
|
||||||
|
|
||||||
|
**1. Update home-manager config:**
|
||||||
|
|
||||||
|
Move `agentsInput` and `externalSkills` from `coding.opencode` to `coding.agents.opencode`.
|
||||||
|
Add `modelOverrides` with the models previously hardcoded in agents.json:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# BEFORE (legacy):
|
||||||
|
coding.opencode = {
|
||||||
|
enable = true;
|
||||||
|
agentsInput = inputs.agents;
|
||||||
|
externalSkills = [{ src = inputs.skills-anthropic; }];
|
||||||
|
ohMyOpencodeSettings = { ... };
|
||||||
|
};
|
||||||
|
|
||||||
|
# AFTER (new):
|
||||||
|
coding.opencode = {
|
||||||
|
enable = true;
|
||||||
|
ohMyOpencodeSettings = { ... };
|
||||||
|
};
|
||||||
|
|
||||||
|
coding.agents.opencode = {
|
||||||
|
enable = true;
|
||||||
|
agentsInput = inputs.agents;
|
||||||
|
externalSkills = [{ src = inputs.skills-anthropic; }];
|
||||||
|
modelOverrides = {
|
||||||
|
chiron = "zai-coding-plan/glm-5";
|
||||||
|
"chiron-forge" = "zai-coding-plan/glm-5";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Run `home-manager switch`:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
home-manager switch --flake .
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Verify agents are deployed:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls ~/.config/opencode/agents/
|
||||||
|
# Should show: chiron.md chiron-forge.md hermes.md athena.md apollo.md calliope.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. Remove legacy files from AGENTS repo** (after confirming everything works):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/m3tam3re/p/AI/AGENTS
|
||||||
|
rm agents/agents.json
|
||||||
|
rm prompts/chiron.txt prompts/chiron-forge.txt prompts/hermes.txt \
|
||||||
|
prompts/athena.txt prompts/apollo.txt prompts/calliope.txt
|
||||||
|
rmdir prompts/ # if empty
|
||||||
|
# Also remove lib.agentsJson from flake.nix
|
||||||
|
```
|
||||||
|
|
||||||
|
**5. Final cleanup:** After legacy files are removed from AGENTS repo,
|
||||||
|
remove `lib.agentsJson` from the AGENTS `flake.nix` (it's only needed for
|
||||||
|
backward compatibility during the transition).
|
||||||
|
|
||||||
|
### Key advantage of the new system
|
||||||
|
|
||||||
|
Prompt changes no longer require `home-manager switch`. Since agents are
|
||||||
|
deployed as file-based `~/.config/opencode/agents/*.md` (symlinks to Nix store),
|
||||||
|
you only need to edit the `system-prompt.md` in the AGENTS repo, commit, update
|
||||||
|
the flake lock, and run `home-manager switch`. Or for local development, edit
|
||||||
|
the file directly and restart the tool.
|
||||||
|
|||||||
90
modules/home-manager/coding/agents/claude-code.nix
Normal file
90
modules/home-manager/coding/agents/claude-code.nix
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
with lib; let
|
||||||
|
cfg = config.coding.agents.claude-code;
|
||||||
|
mcpCfg = config.programs.mcp or null;
|
||||||
|
in {
|
||||||
|
options.coding.agents.claude-code = {
|
||||||
|
enable = mkEnableOption "Claude Code agent management via canonical agent.toml definitions";
|
||||||
|
|
||||||
|
agentsInput = mkOption {
|
||||||
|
type = types.nullOr types.anything;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
The `agents` flake input (your personal AGENTS repo).
|
||||||
|
When set, agents are rendered from canonical agent.toml files
|
||||||
|
and symlinked to ~/.claude/agents/.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
modelOverrides = mkOption {
|
||||||
|
type = types.attrsOf types.str;
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
Per-agent model overrides. Maps agent slug to model alias or ID.
|
||||||
|
Example: { chiron = "claude-sonnet-4-20250514"; }
|
||||||
|
'';
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
chiron = "claude-sonnet-4-20250514";
|
||||||
|
"chiron-forge" = "claude-sonnet-4-20250514";
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
mcpServers = mkOption {
|
||||||
|
type = types.attrsOf types.anything;
|
||||||
|
default = if mcpCfg != null then mcpCfg.servers else {};
|
||||||
|
defaultText = literalExpression "config.programs.mcp.servers";
|
||||||
|
description = ''
|
||||||
|
MCP server configurations for Claude Code.
|
||||||
|
Merged into ~/.claude/settings.json alongside permissions.
|
||||||
|
Automatically inherits from config.programs.mcp.servers.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable (let
|
||||||
|
agentsLib = (import ../../../../lib {inherit lib;}).agents;
|
||||||
|
|
||||||
|
# Rendered agents + permissions (only if agentsInput is set)
|
||||||
|
rendered = mkIf (cfg.agentsInput != null) (
|
||||||
|
agentsLib.renderForClaudeCode {
|
||||||
|
inherit pkgs;
|
||||||
|
canonical = cfg.agentsInput.lib.loadAgents;
|
||||||
|
modelOverrides = cfg.modelOverrides;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
# Merge MCP servers into the rendered settings.json.
|
||||||
|
# The renderer produces { permissions: { allow, deny } }.
|
||||||
|
# We add mcpServers on top.
|
||||||
|
settingsJson =
|
||||||
|
if cfg.agentsInput != null
|
||||||
|
then let
|
||||||
|
renderedSettings = builtins.fromJSON (builtins.readFile "${rendered}/.claude/settings.json");
|
||||||
|
withMcp =
|
||||||
|
if cfg.mcpServers != {}
|
||||||
|
then renderedSettings // {mcpServers = cfg.mcpServers;}
|
||||||
|
else renderedSettings;
|
||||||
|
in
|
||||||
|
pkgs.writeText "claude-settings.json" (builtins.toJSON withMcp)
|
||||||
|
else if cfg.mcpServers != {}
|
||||||
|
then pkgs.writeText "claude-settings.json" (builtins.toJSON {mcpServers = cfg.mcpServers;})
|
||||||
|
else null;
|
||||||
|
in {
|
||||||
|
# Rendered agent files symlinked to ~/.claude/agents/
|
||||||
|
home.file.".claude/agents" = mkIf (cfg.agentsInput != null) {
|
||||||
|
source = "${rendered}/.claude/agents";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Rendered settings.json with permissions + MCP servers
|
||||||
|
home.file.".claude/settings.json" = mkIf (settingsJson != null) {
|
||||||
|
source = "${settingsJson}";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
10
modules/home-manager/coding/agents/default.nix
Normal file
10
modules/home-manager/coding/agents/default.nix
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Per-tool agent sub-modules
|
||||||
|
# Each module handles rendering canonical agent.toml definitions
|
||||||
|
# for a specific AI coding tool.
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./opencode.nix
|
||||||
|
./claude-code.nix
|
||||||
|
./pi.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
113
modules/home-manager/coding/agents/opencode.nix
Normal file
113
modules/home-manager/coding/agents/opencode.nix
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
with lib; let
|
||||||
|
cfg = config.coding.agents.opencode;
|
||||||
|
in {
|
||||||
|
options.coding.agents.opencode = {
|
||||||
|
enable = mkEnableOption "OpenCode agent management via canonical agent.toml definitions";
|
||||||
|
|
||||||
|
agentsInput = mkOption {
|
||||||
|
type = types.nullOr types.anything;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
The `agents` flake input (your personal AGENTS repo).
|
||||||
|
When set, agents are rendered from canonical agent.toml files
|
||||||
|
and symlinked to ~/.config/opencode/agents/.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
modelOverrides = mkOption {
|
||||||
|
type = types.attrsOf types.str;
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
Per-agent model overrides. Maps agent slug to model string.
|
||||||
|
Example: { chiron = "anthropic/claude-sonnet-4"; }
|
||||||
|
'';
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
chiron = "anthropic/claude-sonnet-4";
|
||||||
|
"chiron-forge" = "anthropic/claude-sonnet-4";
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
externalSkills = mkOption {
|
||||||
|
type = types.listOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
src = mkOption {
|
||||||
|
type = types.anything;
|
||||||
|
description = "Flake input pointing to a skills repository root.";
|
||||||
|
};
|
||||||
|
skillsDir = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "skills";
|
||||||
|
description = ''
|
||||||
|
Subdirectory inside src that contains skill folders.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
selectSkills = mkOption {
|
||||||
|
type = types.nullOr (types.listOf types.str);
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
List of skill names to cherry-pick from this source.
|
||||||
|
null means include every skill found in skillsDir.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
External skill sources passed to mkOpencodeSkills.
|
||||||
|
Each entry maps directly to an element of the externalSkills
|
||||||
|
list accepted by the AGENTS flake's lib.mkOpencodeSkills.
|
||||||
|
'';
|
||||||
|
example = literalExpression ''
|
||||||
|
[
|
||||||
|
{ src = inputs.skills-anthropic; selectSkills = [ "claude-api" ]; }
|
||||||
|
{ src = inputs.skills-vercel; }
|
||||||
|
]
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# Rendered agent files symlinked to ~/.config/opencode/agents/
|
||||||
|
xdg.configFile."opencode/agents" = mkIf (cfg.agentsInput != null) {
|
||||||
|
source = (import ../../../../lib {inherit lib;}).agents.renderForOpencode {
|
||||||
|
inherit pkgs;
|
||||||
|
canonical = cfg.agentsInput.lib.loadAgents;
|
||||||
|
modelOverrides = cfg.modelOverrides;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Skills (merged from personal AGENTS repo + optional external skills)
|
||||||
|
xdg.configFile."opencode/skills" = mkIf (cfg.agentsInput != null) {
|
||||||
|
source = cfg.agentsInput.lib.mkOpencodeSkills {
|
||||||
|
inherit pkgs;
|
||||||
|
customSkills = "${cfg.agentsInput}/skills";
|
||||||
|
externalSkills =
|
||||||
|
map (
|
||||||
|
entry:
|
||||||
|
{inherit (entry) src skillsDir;}
|
||||||
|
// optionalAttrs (entry.selectSkills != null) {inherit (entry) selectSkills;}
|
||||||
|
)
|
||||||
|
cfg.externalSkills;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Static config dirs from AGENTS repo
|
||||||
|
xdg.configFile."opencode/context" = mkIf (cfg.agentsInput != null) {
|
||||||
|
source = "${cfg.agentsInput}/context";
|
||||||
|
};
|
||||||
|
xdg.configFile."opencode/commands" = mkIf (cfg.agentsInput != null) {
|
||||||
|
source = "${cfg.agentsInput}/commands";
|
||||||
|
};
|
||||||
|
xdg.configFile."opencode/prompts" = mkIf (cfg.agentsInput != null) {
|
||||||
|
source = "${cfg.agentsInput}/prompts";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
234
modules/home-manager/coding/agents/pi.nix
Normal file
234
modules/home-manager/coding/agents/pi.nix
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
with lib; let
|
||||||
|
cfg = config.coding.agents.pi;
|
||||||
|
mcpCfg = config.programs.mcp or null;
|
||||||
|
in {
|
||||||
|
options.coding.agents.pi = {
|
||||||
|
enable = mkEnableOption "Pi agent management via canonical agent.toml definitions";
|
||||||
|
|
||||||
|
mcpServers = mkOption {
|
||||||
|
type = types.attrsOf types.anything;
|
||||||
|
default = if mcpCfg != null then mcpCfg.servers else {};
|
||||||
|
defaultText = literalExpression "config.programs.mcp.servers";
|
||||||
|
description = ''
|
||||||
|
MCP server configurations for Pi (pi-mcp-adapter).
|
||||||
|
Written to ~/.pi/agent/mcp.json.
|
||||||
|
Automatically inherits from config.programs.mcp.servers.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
agentsInput = mkOption {
|
||||||
|
type = types.nullOr types.anything;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
The `agents` flake input (your personal AGENTS repo).
|
||||||
|
When set, the primary agent's system prompt is rendered as SYSTEM.md,
|
||||||
|
all agents are listed in AGENTS.md, and subagent .md files are deployed.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
modelOverrides = mkOption {
|
||||||
|
type = types.attrsOf types.str;
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
Per-agent model overrides for Pi subagents.
|
||||||
|
Maps agent slug to model string, e.g.:
|
||||||
|
{ chiron = "anthropic/claude-sonnet-4"; chiron-forge = "anthropic/claude-sonnet-4"; }
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
primaryAgent = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Override which canonical agent is used as primary for SYSTEM.md.
|
||||||
|
When null, the first agent with mode="primary" is used.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
freeformType = types.attrsOf types.anything;
|
||||||
|
options = {
|
||||||
|
packages = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
Pi packages to install (npm:, git:, or local paths).
|
||||||
|
These are written to ~/.pi/agent/settings.json.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultProvider = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Default LLM provider (e.g. 'anthropic', 'openai', 'zai').";
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultModel = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Default model ID.";
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultThinkingLevel = mkOption {
|
||||||
|
type = types.nullOr (types.enum ["off" "minimal" "low" "medium" "high" "xhigh"]);
|
||||||
|
default = null;
|
||||||
|
description = "Default extended thinking level.";
|
||||||
|
};
|
||||||
|
|
||||||
|
theme = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Pi theme name.";
|
||||||
|
};
|
||||||
|
|
||||||
|
hideThinkingBlock = mkOption {
|
||||||
|
type = types.nullOr types.bool;
|
||||||
|
default = null;
|
||||||
|
description = "Hide thinking blocks in output.";
|
||||||
|
};
|
||||||
|
|
||||||
|
quietStartup = mkOption {
|
||||||
|
type = types.nullOr types.bool;
|
||||||
|
default = null;
|
||||||
|
description = "Hide startup header.";
|
||||||
|
};
|
||||||
|
|
||||||
|
compaction = mkOption {
|
||||||
|
type = types.nullOr (types.submodule {
|
||||||
|
options = {
|
||||||
|
enabled = mkOption {
|
||||||
|
type = types.nullOr types.bool;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
reserveTokens = mkOption {
|
||||||
|
type = types.nullOr types.int;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
keepRecentTokens = mkOption {
|
||||||
|
type = types.nullOr types.int;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = null;
|
||||||
|
description = "Auto-compaction settings.";
|
||||||
|
};
|
||||||
|
|
||||||
|
enabledModels = mkOption {
|
||||||
|
type = types.nullOr (types.listOf types.str);
|
||||||
|
default = null;
|
||||||
|
description = "Model patterns for Ctrl+P cycling.";
|
||||||
|
};
|
||||||
|
|
||||||
|
sessionDir = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Directory where session files are stored.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extensions = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = "Local extension file paths or directories.";
|
||||||
|
};
|
||||||
|
|
||||||
|
skills = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = "Local skill file paths or directories.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
Pi settings written to ~/.pi/agent/settings.json.
|
||||||
|
Only non-null values are included in the generated JSON.
|
||||||
|
See pi docs/settings.md for all options.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable (let
|
||||||
|
# Build settings.json by filtering out null values recursively
|
||||||
|
filterNulls = attrs:
|
||||||
|
lib.filterAttrs (_: v: v != null) (
|
||||||
|
builtins.mapAttrs (_: v:
|
||||||
|
if builtins.isAttrs v
|
||||||
|
then let
|
||||||
|
filtered = filterNulls v;
|
||||||
|
in
|
||||||
|
if filtered == {} then null else filtered
|
||||||
|
else v) attrs
|
||||||
|
);
|
||||||
|
|
||||||
|
piSettings = filterNulls cfg.settings;
|
||||||
|
|
||||||
|
# Rendered agents (only computed when agentsInput is set)
|
||||||
|
rendered =
|
||||||
|
if cfg.agentsInput != null
|
||||||
|
then
|
||||||
|
(import ../../../../lib {inherit lib;}).agents.renderForPi {
|
||||||
|
inherit pkgs;
|
||||||
|
canonical = cfg.agentsInput.lib.loadAgents;
|
||||||
|
modelOverrides = cfg.modelOverrides;
|
||||||
|
primaryAgent = cfg.primaryAgent;
|
||||||
|
}
|
||||||
|
else null;
|
||||||
|
|
||||||
|
# Dynamic home.file entries for agent .md files
|
||||||
|
agentFiles =
|
||||||
|
if cfg.agentsInput != null
|
||||||
|
then
|
||||||
|
let
|
||||||
|
agentNames = builtins.attrNames cfg.agentsInput.lib.loadAgents;
|
||||||
|
in
|
||||||
|
builtins.listToAttrs (
|
||||||
|
map (name: {
|
||||||
|
name = ".pi/agent/agents/${name}.md";
|
||||||
|
value = {source = "${rendered}/agents/${name}.md";};
|
||||||
|
})
|
||||||
|
agentNames
|
||||||
|
)
|
||||||
|
else {};
|
||||||
|
in {
|
||||||
|
home.file = mkMerge [
|
||||||
|
# ── MCP servers from programs.mcp → ~/.pi/agent/mcp.json ───────
|
||||||
|
(mkIf (cfg.mcpServers != {}) {
|
||||||
|
".pi/agent/mcp.json".text = builtins.toJSON {mcpServers = cfg.mcpServers;};
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── ~/.pi/agent/settings.json ──────────────────────────────────
|
||||||
|
{
|
||||||
|
".pi/agent/settings.json".text = builtins.toJSON piSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── AGENTS.md — agent descriptions and specialist listing ──────
|
||||||
|
(mkIf (cfg.agentsInput != null) {
|
||||||
|
".pi/agent/AGENTS.md".source = "${rendered}/AGENTS.md";
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── SYSTEM.md — primary agent's system prompt ──────────────────
|
||||||
|
(mkIf (cfg.agentsInput != null) {
|
||||||
|
".pi/agent/SYSTEM.md".source = "${rendered}/SYSTEM.md";
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Agents — pi-subagents .md files ────────────────────────────
|
||||||
|
agentFiles
|
||||||
|
|
||||||
|
# ── Skills symlinked from AGENTS repo ──────────────────────────
|
||||||
|
(mkIf (cfg.agentsInput != null) {
|
||||||
|
".pi/agent/skills".source = cfg.agentsInput.lib.mkOpencodeSkills {
|
||||||
|
inherit pkgs;
|
||||||
|
customSkills = "${cfg.agentsInput}/skills";
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
imports = [
|
imports = [
|
||||||
./editors.nix
|
./editors.nix
|
||||||
./opencode.nix
|
./opencode.nix
|
||||||
|
./agents
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,54 +10,6 @@ in {
|
|||||||
options.coding.opencode = {
|
options.coding.opencode = {
|
||||||
enable = mkEnableOption "opencode AI coding assistant";
|
enable = mkEnableOption "opencode AI coding assistant";
|
||||||
|
|
||||||
agentsInput = mkOption {
|
|
||||||
type = types.nullOr types.anything;
|
|
||||||
default = null;
|
|
||||||
description = ''
|
|
||||||
The `agents` flake input (your personal AGENTS repo).
|
|
||||||
When set, skills, context, commands, prompts and the agents.json
|
|
||||||
are all symlinked from this input.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
externalSkills = mkOption {
|
|
||||||
type = types.listOf (types.submodule {
|
|
||||||
options = {
|
|
||||||
src = mkOption {
|
|
||||||
type = types.anything;
|
|
||||||
description = "Flake input pointing to a skills repository root.";
|
|
||||||
};
|
|
||||||
skillsDir = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "skills";
|
|
||||||
description = ''
|
|
||||||
Subdirectory inside src that contains skill folders.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
selectSkills = mkOption {
|
|
||||||
type = types.nullOr (types.listOf types.str);
|
|
||||||
default = null;
|
|
||||||
description = ''
|
|
||||||
List of skill names to cherry-pick from this source.
|
|
||||||
null means include every skill found in skillsDir.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
default = [];
|
|
||||||
description = ''
|
|
||||||
External skill sources passed to mkOpencodeSkills.
|
|
||||||
Each entry maps directly to an element of the externalSkills
|
|
||||||
list accepted by the AGENTS flake's lib.mkOpencodeSkills.
|
|
||||||
'';
|
|
||||||
example = literalExpression ''
|
|
||||||
[
|
|
||||||
{ src = inputs.skills-anthropic; selectSkills = [ "claude-api" ]; }
|
|
||||||
{ src = inputs.skills-vercel; }
|
|
||||||
]
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
ohMyOpencodeSettings = mkOption {
|
ohMyOpencodeSettings = mkOption {
|
||||||
type = types.attrs;
|
type = types.attrs;
|
||||||
default = {};
|
default = {};
|
||||||
@@ -103,33 +55,6 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
# --- Skills (merged from personal AGENTS repo + optional external skills) ---
|
|
||||||
xdg.configFile."opencode/skills" = mkIf (cfg.agentsInput != null) {
|
|
||||||
source = cfg.agentsInput.lib.mkOpencodeSkills {
|
|
||||||
inherit pkgs;
|
|
||||||
customSkills = "${cfg.agentsInput}/skills";
|
|
||||||
externalSkills =
|
|
||||||
map (
|
|
||||||
entry:
|
|
||||||
{inherit (entry) src skillsDir;}
|
|
||||||
// optionalAttrs (entry.selectSkills != null) {inherit (entry) selectSkills;}
|
|
||||||
)
|
|
||||||
cfg.externalSkills;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# --- Static config dirs from AGENTS repo ---
|
|
||||||
xdg.configFile."opencode/context" = mkIf (cfg.agentsInput != null) {
|
|
||||||
source = "${cfg.agentsInput}/context";
|
|
||||||
};
|
|
||||||
xdg.configFile."opencode/commands" = mkIf (cfg.agentsInput != null) {
|
|
||||||
source = "${cfg.agentsInput}/commands";
|
|
||||||
};
|
|
||||||
xdg.configFile."opencode/prompts" = mkIf (cfg.agentsInput != null) {
|
|
||||||
source = "${cfg.agentsInput}/prompts";
|
|
||||||
};
|
|
||||||
|
|
||||||
# --- Core opencode program settings ---
|
|
||||||
programs.opencode = {
|
programs.opencode = {
|
||||||
enable = true;
|
enable = true;
|
||||||
enableMcpIntegration = true;
|
enableMcpIntegration = true;
|
||||||
@@ -144,17 +69,10 @@ in {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
# Load agents.json from AGENTS repo when available
|
|
||||||
(mkIf (cfg.agentsInput != null) {
|
|
||||||
agent = builtins.fromJSON (builtins.readFile "${cfg.agentsInput}/agents/agents.json");
|
|
||||||
})
|
|
||||||
# Machine/org-specific provider config
|
|
||||||
cfg.extraSettings
|
cfg.extraSettings
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
# --- oh-my-opencode plugin config ---
|
|
||||||
# Base defaults (no models — those must be set per machine via ohMyOpencodeSettings)
|
|
||||||
home.file.".config/opencode/oh-my-opencode.json".text = builtins.toJSON (
|
home.file.".config/opencode/oh-my-opencode.json".text = builtins.toJSON (
|
||||||
recursiveUpdate
|
recursiveUpdate
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user