{ lib, defaultPackages }:
let
safeName = name: builtins.match "[A-Za-z0-9][A-Za-z0-9._-]*" name != null;
# Package a skill directory (containing SKILL.md).
# runtimeInputs: packages put on PATH of every launcher that enables the skill
# skills: other mkSkill packages this skill depends on
mkSkill =
{
pkgs,
name,
src,
runtimeInputs ? [ ],
skills ? [ ],
}:
assert lib.assertMsg (safeName name) "mkSkill: unsafe skill name '${name}'";
pkgs.runCommand "skill-${name}"
{
passthru = {
skillName = name;
inherit runtimeInputs;
skillDeps = skills;
};
}
''
if [ ! -f ${src}/SKILL.md ]; then
echo "mkSkill: ${name}: SKILL.md is missing in ${src}" >&2
exit 1
fi
cp -r ${src} $out
chmod -R u+w $out
'';
in
{
inherit safeName mkSkill;
# Evaluate an environment from a list of modules.
mkEnvironment =
{
pkgs,
modules ? [ ],
}:
let
eval = lib.evalModules {
modules = [
../modules/core.nix
../modules/claude.nix
../modules/codex.nix
../modules/launcher.nix
../modules/companions.nix
{
_module.args = {
inherit pkgs;
agentEnvLib = { inherit safeName mkSkill; };
};
claude.package = lib.mkDefault (defaultPackages pkgs.stdenv.hostPlatform.system).claude;
codex.package = lib.mkDefault (defaultPackages pkgs.stdenv.hostPlatform.system).codex;
}
]
++ modules;
};
failed = map (a: a.message) (lib.filter (a: !a.assertion) eval.config.assertions);
checked =
if failed == [ ] then
eval.config
else
throw "agent-environments:\n${lib.concatMapStringsSep "\n" (m: "- ${m}") failed}";
in
{
config = checked;
inherit (checked.build) package launchers;
};
# A module declaring `modules.skills.<name>.enable` for a packaged skill.
mkSkillModule =
skill:
{ config, lib, ... }:
{
options.modules.skills.${skill.skillName}.enable =
lib.mkEnableOption "the ${skill.skillName} skill";
config = lib.mkIf config.modules.skills.${skill.skillName}.enable {
skills.packages = [ skill ];
};
};
}