feat(home-manager): place skills for Pi
This commit is contained in:
@@ -87,15 +87,38 @@ let
|
|||||||
programs.agents.skills = [ skillAlpha ];
|
programs.agents.skills = [ skillAlpha ];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Claude Code disabled, with a skill still selected.
|
# Pi enabled and Claude Code disabled.
|
||||||
claudeCodeOff = homeFiles {
|
piOnly = homeFiles {
|
||||||
programs.claude-code.enable = false;
|
programs.pi-coding-agent.enable = true;
|
||||||
programs.agents.skills = [ skillAlpha ];
|
programs.agents.skills = [ skillAlpha ];
|
||||||
};
|
};
|
||||||
|
|
||||||
# An empty selection with Claude Code enabled.
|
# Both supported harnesses enabled.
|
||||||
|
bothHarnesses = homeFiles {
|
||||||
|
programs.claude-code.enable = true;
|
||||||
|
programs.pi-coding-agent.enable = true;
|
||||||
|
programs.agents.skills = [ skillAlpha ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# A non-default Pi `configDir`, so the assertions can prove placement follows it
|
||||||
|
# rather than a hardcoded `.pi/agent`.
|
||||||
|
customPiConfigDir = homeFiles {
|
||||||
|
programs.pi-coding-agent.enable = true;
|
||||||
|
programs.pi-coding-agent.configDir = "/home/operator/.config/pi/agent";
|
||||||
|
programs.agents.skills = [ skillAlpha ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# No compatible harness enabled, with a skill still selected.
|
||||||
|
agentsOff = homeFiles {
|
||||||
|
programs.claude-code.enable = false;
|
||||||
|
programs.pi-coding-agent.enable = false;
|
||||||
|
programs.agents.skills = [ skillAlpha ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# An empty selection with both supported harnesses enabled.
|
||||||
emptySelection = homeFiles {
|
emptySelection = homeFiles {
|
||||||
programs.claude-code.enable = true;
|
programs.claude-code.enable = true;
|
||||||
|
programs.pi-coding-agent.enable = true;
|
||||||
programs.agents.skills = [ ];
|
programs.agents.skills = [ ];
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
@@ -107,7 +130,10 @@ pkgs.runCommandLocal "skills-home-manager-module-check"
|
|||||||
attrSetSkills
|
attrSetSkills
|
||||||
wholeDirSkills
|
wholeDirSkills
|
||||||
customConfigDir
|
customConfigDir
|
||||||
claudeCodeOff
|
piOnly
|
||||||
|
bothHarnesses
|
||||||
|
customPiConfigDir
|
||||||
|
agentsOff
|
||||||
emptySelection
|
emptySelection
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
@@ -142,13 +168,35 @@ pkgs.runCommandLocal "skills-home-manager-module-check"
|
|||||||
test ! -e "$customConfigDir/.claude/skills/sample-alpha" \
|
test ! -e "$customConfigDir/.claude/skills/sample-alpha" \
|
||||||
|| fail "skill was placed under a hardcoded .claude, ignoring configDir"
|
|| fail "skill was placed under a hardcoded .claude, ignoring configDir"
|
||||||
|
|
||||||
echo "Claude Code disabled: no skill entry is written despite a selection"
|
echo "Pi enabled: selected skills land in Pi's global skills directory"
|
||||||
test ! -e "$claudeCodeOff/.claude/skills/sample-alpha" \
|
test -f "$piOnly/.pi/agent/skills/sample-alpha/SKILL.md" \
|
||||||
|| fail "a skill was placed with claude-code disabled"
|
|| fail "selected skill did not land under Pi's configDir"
|
||||||
|
test ! -e "$piOnly/.claude/skills/sample-alpha" \
|
||||||
|
|| fail "a skill was placed for Claude Code when only Pi was enabled"
|
||||||
|
|
||||||
|
echo "both harnesses enabled: selected skills land in both global skills directories"
|
||||||
|
test -f "$bothHarnesses/.claude/skills/sample-alpha/SKILL.md" \
|
||||||
|
|| fail "selected skill did not land under Claude Code when both harnesses were enabled"
|
||||||
|
test -f "$bothHarnesses/.pi/agent/skills/sample-alpha/SKILL.md" \
|
||||||
|
|| fail "selected skill did not land under Pi when both harnesses were enabled"
|
||||||
|
|
||||||
|
echo "custom Pi configDir: placement follows configDir rather than a hardcoded .pi/agent"
|
||||||
|
test -f "$customPiConfigDir/.config/pi/agent/skills/sample-alpha/SKILL.md" \
|
||||||
|
|| fail "skill was not placed under Pi's configured configDir"
|
||||||
|
test ! -e "$customPiConfigDir/.pi/agent/skills/sample-alpha" \
|
||||||
|
|| fail "skill was placed under a hardcoded .pi/agent, ignoring configDir"
|
||||||
|
|
||||||
|
echo "agents disabled: no skill entry is written despite a selection"
|
||||||
|
test ! -e "$agentsOff/.claude/skills/sample-alpha" \
|
||||||
|
|| fail "a skill was placed with Claude Code disabled"
|
||||||
|
test ! -e "$agentsOff/.pi/agent/skills/sample-alpha" \
|
||||||
|
|| fail "a skill was placed with Pi disabled"
|
||||||
|
|
||||||
echo "empty selection: nothing is installed"
|
echo "empty selection: nothing is installed"
|
||||||
test ! -e "$emptySelection/.claude/skills/sample-alpha" \
|
test ! -e "$emptySelection/.claude/skills/sample-alpha" \
|
||||||
|| fail "an unselected skill was placed"
|
|| fail "an unselected skill was placed for Claude Code"
|
||||||
|
test ! -e "$emptySelection/.pi/agent/skills/sample-alpha" \
|
||||||
|
|| fail "an unselected skill was placed for Pi"
|
||||||
|
|
||||||
touch "$out"
|
touch "$out"
|
||||||
''
|
''
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Places operator-selected skills into Claude Code's skills directory, one per
|
# Places operator-selected skills into each enabled agent's global skills
|
||||||
# skill, for every project.
|
# directory, one per skill, for every project.
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
@@ -8,6 +8,18 @@
|
|||||||
let
|
let
|
||||||
cfg = config.programs.agents;
|
cfg = config.programs.agents;
|
||||||
claudeCode = config.programs.claude-code;
|
claudeCode = config.programs.claude-code;
|
||||||
|
pi = config.programs.pi-coding-agent;
|
||||||
|
|
||||||
|
skillFiles = configDir:
|
||||||
|
lib.listToAttrs (
|
||||||
|
map (skill: {
|
||||||
|
name = "${configDir}/skills/${skill.skillName}";
|
||||||
|
value = {
|
||||||
|
source = skill;
|
||||||
|
recursive = true;
|
||||||
|
};
|
||||||
|
}) cfg.skills
|
||||||
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.programs.agents.skills = lib.mkOption {
|
options.programs.agents.skills = lib.mkOption {
|
||||||
@@ -15,49 +27,42 @@ in
|
|||||||
default = [ ];
|
default = [ ];
|
||||||
example = lib.literalExpression "[ inputs.skills.packages.\${system}.some-skill ]";
|
example = lib.literalExpression "[ inputs.skills.packages.\${system}.some-skill ]";
|
||||||
description = ''
|
description = ''
|
||||||
Skills to install globally, into the `skills/` subdirectory of
|
Skills to install globally into every enabled agent harness that supports
|
||||||
{option}`programs.claude-code.configDir`, so they are active in every
|
Agent Skills.
|
||||||
project.
|
|
||||||
|
|
||||||
Selection is by derivation: pass skill derivations from the skills flake's
|
Selection is by derivation: pass skill derivations from the skills flake's
|
||||||
`packages.<system>`, each of which carries its placement name as an
|
`packages.<system>`, each of which carries its placement name as an
|
||||||
eval-time attribute. An empty list installs nothing.
|
eval-time attribute. An empty list installs nothing.
|
||||||
|
|
||||||
Every skill is written as its own recursive {option}`home.file`, so this
|
Every skill is written as its own recursive {option}`home.file`, so this
|
||||||
option composes with an operator's own {option}`programs.claude-code.skills`
|
option composes with an operator's own harness-specific skills and with
|
||||||
and with other modules placing skills under the same directory rather than
|
other modules placing skills under the same directory rather than claiming
|
||||||
claiming the tree. Placement is gated on
|
the tree. Placement is gated on each harness' enable option.
|
||||||
{option}`programs.claude-code.enable`.
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
# `agents` is an umbrella namespace with no shared enable, so the skills
|
# `agents` is an umbrella namespace with no shared enable, so the skills
|
||||||
# feature self-gates.
|
# feature self-gates.
|
||||||
# An empty list writes nothing, and files land only when Claude Code is
|
# An empty list writes nothing, and files land only when a compatible harness
|
||||||
# enabled.
|
# is enabled.
|
||||||
# Reading each skill's source realises its derivation, so gating on
|
# Reading each skill's source realises its derivation, so gating on harness
|
||||||
# `claude-code.enable` also keeps that realisation off a host that installs
|
# enablement also keeps that realisation off a host that installs nothing.
|
||||||
# nothing.
|
config = lib.mkMerge [
|
||||||
config = lib.mkIf claudeCode.enable {
|
(lib.mkIf claudeCode.enable {
|
||||||
# `configDir` comes from the Claude Code module, so skills land beside its
|
# Each name is the derivation's eval-time `skillName`, so placement reads
|
||||||
# own wherever the operator points it.
|
# no `$out` and needs no import-from-derivation.
|
||||||
# Each name is the derivation's eval-time `skillName`, so placement reads no
|
# `recursive = true` materialises `skills/<name>/` as a directory of
|
||||||
# `$out` and needs no import-from-derivation.
|
# per-file symlinks, which lets this module, the operator's own skills,
|
||||||
# `recursive = true` materialises `skills/<name>/` as a directory of per-file
|
# and self-placing tool modules share one `skills/` tree.
|
||||||
# symlinks, which is what lets this module, the operator's own skills, and
|
|
||||||
# self-placing tool modules share one `skills/` tree.
|
|
||||||
#
|
#
|
||||||
# This deliberately does not feed the single-valued
|
# This deliberately does not feed the single-valued
|
||||||
# `programs.claude-code.skills`, which would collide with an operator already
|
# `programs.claude-code.skills`, which would collide with an operator
|
||||||
# setting it.
|
# already setting it.
|
||||||
home.file = lib.listToAttrs (
|
home.file = skillFiles claudeCode.configDir;
|
||||||
map (skill: {
|
})
|
||||||
name = "${claudeCode.configDir}/skills/${skill.skillName}";
|
|
||||||
value = {
|
(lib.mkIf pi.enable {
|
||||||
source = skill;
|
home.file = skillFiles pi.configDir;
|
||||||
recursive = true;
|
})
|
||||||
};
|
];
|
||||||
}) cfg.skills
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user