From c596038adc7cdbd64988e61fee856d533b58c25d Mon Sep 17 00:00:00 2001 From: "Simen A. W. Olsen" Date: Sun, 25 Oct 2026 12:41:28 +0100 Subject: feat: skills, a package manager for agent skills --- nix/home-manager.nix | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ nix/package.nix | 45 ++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 nix/home-manager.nix create mode 100644 nix/package.nix (limited to 'nix') diff --git a/nix/home-manager.nix b/nix/home-manager.nix new file mode 100644 index 0000000..53db658 --- /dev/null +++ b/nix/home-manager.nix @@ -0,0 +1,98 @@ +self: +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.skills; + lock = lib.importJSON cfg.lockFile; + + fetch = + src: + builtins.fetchTree ( + { + type = "git"; + inherit (src) url rev; + allRefs = true; + } + # With a narHash Nix reuses what `skills` already put in the store, so + # private sources work even when root evaluates this without SSH keys. + // lib.optionalAttrs (src ? narHash) { inherit (src) narHash; } + ); + + skills = lib.concatMapAttrs ( + _: src: + let + tree = fetch src; + in + lib.mapAttrs (_: path: "${tree}/${path}") src.skills + ) lock.sources; + + linkInto = dir: lib.mapAttrs' (name: path: lib.nameValuePair "${dir}/${name}" { source = path; }) skills; +in +{ + options.programs.skills = { + enable = lib.mkEnableOption "agent skills from a skills lock file"; + + package = lib.mkOption { + type = lib.types.package; + default = self.packages.${pkgs.stdenv.hostPlatform.system}.default; + defaultText = lib.literalExpression "skills.packages.\${system}.default"; + description = "The skills CLI."; + }; + + lockFile = lib.mkOption { + type = lib.types.path; + example = lib.literalExpression "./skills.lock"; + description = "The skills.lock whose skills to install."; + }; + + manifest = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "/home/me/dotfiles/skills.json"; + description = '' + Absolute path of the writable skills.json next to {option}`lockFile`, + so that `skills add` and `skills update` edit your configuration. + ''; + }; + + agents = lib.mkOption { + type = lib.types.listOf ( + lib.types.enum [ + "claude" + "codex" + "opencode" + ] + ); + default = [ "claude" ]; + description = "Agents to install the skills into."; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = lock.version or null == 1; + message = "programs.skills.lockFile has an unsupported version; upgrade skills."; + } + ]; + + home.packages = [ cfg.package ]; + + home.sessionVariables = { + SKILLS_MANAGED_BY = "home-manager"; + } + // lib.optionalAttrs (cfg.manifest != null) { SKILLS_MANIFEST = cfg.manifest; }; + + home.file = lib.mkMerge [ + (lib.mkIf (lib.elem "claude" cfg.agents) (linkInto ".claude/skills")) + (lib.mkIf (lib.elem "codex" cfg.agents) (linkInto ".codex/skills")) + ]; + + xdg.configFile = lib.mkIf (lib.elem "opencode" cfg.agents) (linkInto "opencode/skills"); + }; +} diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 0000000..414f173 --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,45 @@ +{ + lib, + stdenv, + buildGoModule, + git, + makeWrapper, + installShellFiles, + version, + src, +}: + +buildGoModule { + pname = "skills"; + inherit version src; + + vendorHash = "sha256-7K17JaXFsjf163g5PXCb5ng2gYdotnZ2IDKk8KFjNj0="; + + ldflags = [ + "-s" + "-w" + "-X main.version=${version}" + ]; + + nativeBuildInputs = [ + makeWrapper + installShellFiles + ]; + + postInstall = '' + wrapProgram $out/bin/skills --prefix PATH : ${lib.makeBinPath [ git ]} + '' + + lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) '' + installShellCompletion --cmd skills \ + --bash <($out/bin/skills completion bash) \ + --fish <($out/bin/skills completion fish) \ + --zsh <($out/bin/skills completion zsh) + ''; + + meta = { + description = "Install and update agent skills from git repositories"; + homepage = "https://git.simenandre.no/skills/about/"; + license = lib.licenses.mit; + mainProgram = "skills"; + }; +} -- cgit v1.3.1