2026-04-10 16:47:55 +02:00
|
|
|
# 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
|
2026-04-13 16:52:47 +02:00
|
|
|
# rendered = m3taLib.agents.renderForOpencode {
|
|
|
|
|
# inherit pkgs canonical;
|
|
|
|
|
# modelOverrides = { chiron = "anthropic/claude-sonnet-4"; };
|
2026-04-10 16:47:55 +02:00
|
|
|
# };
|
|
|
|
|
# in { ... }
|
|
|
|
|
{lib}: let
|
2026-04-13 16:52:47 +02:00
|
|
|
# ── 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;};
|
|
|
|
|
|
2026-04-22 18:50:31 +02:00
|
|
|
# ── Shared renderer primitives ──────────────────────────────────
|
|
|
|
|
# Render agent files from canonical definitions into a directory.
|
|
|
|
|
# Each agent gets a "<name>.md" file containing mkContent name agent.
|
|
|
|
|
#
|
|
|
|
|
# Args:
|
|
|
|
|
# pkgs — Nixpkgs package set with linkFarm
|
|
|
|
|
# canonical — Attribute set of agent definitions (keyed by slug)
|
|
|
|
|
# mkContent — Function: name: agent → string (file content)
|
|
|
|
|
# name — Derivation name (e.g. "opencode-agents")
|
|
|
|
|
#
|
|
|
|
|
# Returns:
|
|
|
|
|
# A store path containing all agent *.md files.
|
|
|
|
|
renderAgentFiles = pkgs: canonical: mkContent: name:
|
|
|
|
|
pkgs.linkFarm name (
|
|
|
|
|
lib.mapAttrsToList (n: a: {
|
|
|
|
|
name = "${n}.md";
|
|
|
|
|
path = pkgs.writeText "${n}.md" (mkContent n a);
|
|
|
|
|
})
|
|
|
|
|
canonical
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-10 16:47:55 +02:00
|
|
|
agentsLib = {
|
2026-04-13 16:52:47 +02:00
|
|
|
# ── loadCanonical ─────────────────────────────────────────────
|
|
|
|
|
#
|
2026-04-10 16:47:55 +02:00
|
|
|
# Load canonical agent definitions from the AGENTS flake input.
|
|
|
|
|
# Returns the canonical attrset from lib.loadAgents (keyed by slug).
|
2026-04-13 16:52:47 +02:00
|
|
|
|
2026-04-10 16:47:55 +02:00
|
|
|
loadCanonical = {agentsInput}: agentsInput.lib.loadAgents;
|
|
|
|
|
|
2026-04-13 16:52:47 +02:00
|
|
|
# ── OpenCode renderer ─────────────────────────────────────────
|
|
|
|
|
#
|
|
|
|
|
# Produces a directory of agent *.md files suitable for
|
|
|
|
|
# ~/.config/opencode/agents/ (system-level)
|
|
|
|
|
# .opencode/agents/ (project-level)
|
|
|
|
|
#
|
2026-04-10 17:10:46 +02:00
|
|
|
# Each file has YAML frontmatter (description, mode, optional model,
|
|
|
|
|
# optional permission) followed by the agent's systemPrompt content.
|
2026-04-13 16:52:47 +02:00
|
|
|
# The filename (without .md) becomes the agent name in OpenCode.
|
|
|
|
|
|
2026-04-10 16:47:55 +02:00
|
|
|
renderForOpencode = {
|
|
|
|
|
pkgs,
|
|
|
|
|
canonical,
|
|
|
|
|
modelOverrides ? {},
|
2026-04-10 17:10:46 +02:00
|
|
|
}: let
|
2026-04-13 16:52:47 +02:00
|
|
|
# Render one permission section to YAML lines.
|
2026-04-10 17:10:46 +02:00
|
|
|
# intent-only → single line: " <tool>: <intent>"
|
2026-04-13 16:52:47 +02:00
|
|
|
# intent+rules → nested block
|
2026-04-10 17:10:46 +02:00
|
|
|
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;
|
|
|
|
|
in
|
2026-04-22 18:50:31 +02:00
|
|
|
renderAgentFiles pkgs canonical mkAgentContent "opencode-agents";
|
2026-04-10 16:47:55 +02:00
|
|
|
|
2026-04-13 16:52:47 +02:00
|
|
|
# ── 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)
|
|
|
|
|
|
2026-04-10 16:47:55 +02:00
|
|
|
renderForClaudeCode = {
|
|
|
|
|
pkgs,
|
|
|
|
|
canonical,
|
|
|
|
|
modelOverrides ? {},
|
2026-04-13 16:52:47 +02:00
|
|
|
}: 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;
|
|
|
|
|
|
2026-04-22 18:50:31 +02:00
|
|
|
agentFiles = renderAgentFiles pkgs canonical mkClaudeAgentContent "claude-code-agent-files";
|
2026-04-13 16:52:47 +02:00
|
|
|
|
|
|
|
|
# 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;
|
|
|
|
|
in
|
|
|
|
|
pkgs.runCommand "claude-code-agents" {} ''
|
|
|
|
|
mkdir -p $out/.claude/agents
|
2026-04-22 18:50:31 +02:00
|
|
|
cp -r ${agentFiles}/* $out/.claude/agents/
|
2026-04-13 16:52:47 +02:00
|
|
|
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
|
2026-04-10 16:47:55 +02:00
|
|
|
|
|
|
|
|
renderForPi = {
|
|
|
|
|
pkgs,
|
|
|
|
|
canonical,
|
2026-04-13 16:52:47 +02:00
|
|
|
modelOverrides ? {},
|
|
|
|
|
primaryAgent ? null,
|
2026-04-21 20:24:38 +02:00
|
|
|
codingRules ? null,
|
2026-04-13 16:52:47 +02:00
|
|
|
}: let
|
2026-04-21 20:24:38 +02:00
|
|
|
# Import coding-rules lib for concatRulesMd when codingRules is provided
|
2026-04-22 18:50:31 +02:00
|
|
|
codingRulesLib = import ./coding-rules.nix {inherit lib;};
|
2026-04-13 16:52:47 +02:00
|
|
|
# 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;
|
2026-04-10 16:47:55 +02:00
|
|
|
|
2026-04-13 16:52:47 +02:00
|
|
|
# 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;
|
|
|
|
|
|
2026-04-22 18:50:31 +02:00
|
|
|
piAgentFiles = renderAgentFiles pkgs canonical mkPiAgentContent "pi-agent-files";
|
2026-04-13 16:52:47 +02:00
|
|
|
|
|
|
|
|
# ── 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;
|
2026-04-21 20:24:38 +02:00
|
|
|
# ── Coding rules section (optional) ────────────────────────
|
|
|
|
|
# When codingRules is provided, append selected rules to AGENTS.md.
|
|
|
|
|
# codingRules attrset: { agents, languages, concerns, frameworks }
|
|
|
|
|
codingRulesSection =
|
|
|
|
|
if codingRules != null
|
|
|
|
|
then let
|
|
|
|
|
section = codingRulesLib.mkRulesMdSection codingRules;
|
|
|
|
|
in
|
2026-04-22 18:50:31 +02:00
|
|
|
if section != ""
|
|
|
|
|
then "\n" + section
|
|
|
|
|
else ""
|
2026-04-21 20:24:38 +02:00
|
|
|
else "";
|
|
|
|
|
|
2026-04-13 16:52:47 +02:00
|
|
|
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"
|
2026-04-21 20:24:38 +02:00
|
|
|
)
|
|
|
|
|
+ codingRulesSection;
|
2026-04-13 16:52:47 +02:00
|
|
|
|
|
|
|
|
agentsMdFile = pkgs.writeText "AGENTS.md" agentsMd;
|
|
|
|
|
systemMdFile = pkgs.writeText "SYSTEM.md" primary.systemPrompt;
|
|
|
|
|
in
|
|
|
|
|
pkgs.runCommand "pi-agents" {} ''
|
|
|
|
|
mkdir -p $out/agents
|
|
|
|
|
cp ${agentsMdFile} $out/AGENTS.md
|
|
|
|
|
cp ${systemMdFile} $out/SYSTEM.md
|
2026-04-22 18:50:31 +02:00
|
|
|
cp -r ${piAgentFiles}/* $out/agents/
|
2026-04-13 16:52:47 +02:00
|
|
|
'';
|
|
|
|
|
|
|
|
|
|
# ── renderForTool dispatcher ──────────────────────────────────
|
|
|
|
|
#
|
|
|
|
|
# Dispatches to the correct renderer by tool name.
|
2026-04-10 16:47:55 +02:00
|
|
|
# tool: "opencode" | "claude-code" | "pi"
|
2026-04-13 16:52:47 +02:00
|
|
|
|
2026-04-10 16:47:55 +02:00
|
|
|
renderForTool = {
|
|
|
|
|
pkgs,
|
|
|
|
|
agentsInput,
|
|
|
|
|
tool,
|
|
|
|
|
modelOverrides ? {},
|
2026-04-21 20:24:38 +02:00
|
|
|
codingRules ? null,
|
2026-04-10 16:47:55 +02:00
|
|
|
}: 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"
|
2026-04-13 16:52:47 +02:00
|
|
|
then
|
|
|
|
|
agentsLib.renderForPi {
|
2026-04-21 20:24:38 +02:00
|
|
|
inherit pkgs canonical modelOverrides codingRules;
|
2026-04-13 16:52:47 +02:00
|
|
|
}
|
2026-04-10 16:47:55 +02:00
|
|
|
else throw "lib.agents.renderForTool: unknown tool '${tool}'. Must be opencode, claude-code, or pi.";
|
2026-04-13 16:52:47 +02:00
|
|
|
|
|
|
|
|
# ── 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 ? {},
|
2026-04-21 20:24:38 +02:00
|
|
|
codingRules ? null,
|
2026-04-13 16:52:47 +02:00
|
|
|
}: let
|
|
|
|
|
rendered = agentsLib.renderForTool {
|
2026-04-21 20:24:38 +02:00
|
|
|
inherit pkgs agentsInput tool modelOverrides codingRules;
|
2026-04-13 16:52:47 +02:00
|
|
|
};
|
|
|
|
|
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}'";
|
2026-04-10 16:47:55 +02:00
|
|
|
};
|
|
|
|
|
in
|
|
|
|
|
agentsLib
|