@@ 0,0 1,84 @@
+{ 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
+ {
+ _module.args = {
+ inherit pkgs;
+ agentEnvLib = { inherit safeName; };
+ };
+ 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 ];
+ };
+ };
+}