From 1cfa305fba94468f665de1bd1b62dddf2e0cb012 Mon Sep 17 00:00:00 2001 From: Tim Kleinschmidt Date: Fri, 2 Jan 2026 17:45:21 +0100 Subject: [PATCH] opencode: add agent skills support Adds support for OpenCode Agent Skills by managing skill definitions under "/opencode/skill//SKILL.md" via `programs.opencode.skills`. Documentation: https://opencode.ai/docs/skills/ --- modules/programs/opencode.nix | 67 +++++++++++++++++++ tests/modules/programs/opencode/default.nix | 4 ++ .../programs/opencode/git-release-SKILL.md | 10 +++ .../programs/opencode/pdf-processing-SKILL.md | 9 +++ .../opencode/skill-dir/data-analysis/SKILL.md | 9 +++ .../skill-dir/data-analysis/notes.txt | 1 + .../opencode/skills-bulk-directory.nix | 15 +++++ .../opencode/skills-bulk/git-release/SKILL.md | 6 ++ .../skills-bulk/pdf-processing/SKILL.md | 6 ++ .../programs/opencode/skills-directory.nix | 15 +++++ .../programs/opencode/skills-inline.nix | 25 +++++++ .../modules/programs/opencode/skills-path.nix | 14 ++++ 12 files changed, 181 insertions(+) create mode 100644 tests/modules/programs/opencode/git-release-SKILL.md create mode 100644 tests/modules/programs/opencode/pdf-processing-SKILL.md create mode 100644 tests/modules/programs/opencode/skill-dir/data-analysis/SKILL.md create mode 100644 tests/modules/programs/opencode/skill-dir/data-analysis/notes.txt create mode 100644 tests/modules/programs/opencode/skills-bulk-directory.nix create mode 100644 tests/modules/programs/opencode/skills-bulk/git-release/SKILL.md create mode 100644 tests/modules/programs/opencode/skills-bulk/pdf-processing/SKILL.md create mode 100644 tests/modules/programs/opencode/skills-directory.nix create mode 100644 tests/modules/programs/opencode/skills-inline.nix create mode 100644 tests/modules/programs/opencode/skills-path.nix diff --git a/modules/programs/opencode.nix b/modules/programs/opencode.nix index 3c56d2b6..8a72377a 100644 --- a/modules/programs/opencode.nix +++ b/modules/programs/opencode.nix @@ -183,6 +183,49 @@ in ''; }; + skills = lib.mkOption { + type = lib.types.either (lib.types.attrsOf (lib.types.either lib.types.lines lib.types.path)) lib.types.path; + default = { }; + description = '' + Custom agent skills for opencode. + + This option can either be: + - An attribute set defining skills + - A path to a directory containing multiple skill folders + + If an attribute set is used, the attribute name becomes the skill directory name, + and the value is either: + - Inline content as a string (creates `opencode/skill//SKILL.md`) + - A path to a file (creates `opencode/skill//SKILL.md`) + - A path to a directory (creates `opencode/skill//` with all files) + + If a path is used, it is expected to contain one folder per skill name, each + containing a {file}`SKILL.md`. The directory is symlinked to + {file}`$XDG_CONFIG_HOME/opencode/skill/`. + + See for the documentation. + ''; + example = lib.literalExpression '' + { + git-release = ''' + --- + name: git-release + description: Create consistent releases and changelogs + --- + + ## What I do + + - Draft release notes from merged PRs + - Propose a version bump + - Provide a copy-pasteable `gh release create` command + '''; + + # A skill can also be a directory containing SKILL.md and other files. + data-analysis = ./skills/data-analysis; + } + ''; + }; + themes = mkOption { type = lib.types.attrsOf (lib.types.either jsonFormat.type lib.types.path); default = { }; @@ -199,6 +242,13 @@ in }; config = mkIf cfg.enable { + assertions = [ + { + assertion = !lib.isPath cfg.skills || lib.pathIsDirectory cfg.skills; + message = "`programs.opencode.skills` must be a directory when set to a path"; + } + ]; + home.packages = mkIf (cfg.package != null) [ cfg.package ]; xdg.configFile = { @@ -227,6 +277,11 @@ in text = cfg.rules; }) ); + + "opencode/skill" = mkIf (lib.isPath cfg.skills) { + source = cfg.skills; + recursive = true; + }; } // lib.mapAttrs' ( name: content: @@ -240,6 +295,18 @@ in if lib.isPath content then { source = content; } else { text = content; } ) ) cfg.agents + // lib.mapAttrs' ( + name: content: + if lib.isPath content && lib.pathIsDirectory content then + lib.nameValuePair "opencode/skill/${name}" { + source = content; + recursive = true; + } + else + lib.nameValuePair "opencode/skill/${name}/SKILL.md" ( + if lib.isPath content then { source = content; } else { text = content; } + ) + ) (if builtins.isAttrs cfg.skills then cfg.skills else { }) // lib.mapAttrs' ( name: content: lib.nameValuePair "opencode/themes/${name}.json" ( diff --git a/tests/modules/programs/opencode/default.nix b/tests/modules/programs/opencode/default.nix index 74bfc6a8..8a865230 100644 --- a/tests/modules/programs/opencode/default.nix +++ b/tests/modules/programs/opencode/default.nix @@ -9,6 +9,10 @@ opencode-agents-path = ./agents-path.nix; opencode-commands-path = ./commands-path.nix; opencode-mixed-content = ./mixed-content.nix; + opencode-skills-inline = ./skills-inline.nix; + opencode-skills-path = ./skills-path.nix; + opencode-skills-directory = ./skills-directory.nix; + opencode-skills-bulk-directory = ./skills-bulk-directory.nix; opencode-themes-inline = ./themes-inline.nix; opencode-themes-path = ./themes-path.nix; opencode-mcp-integration = ./mcp-integration.nix; diff --git a/tests/modules/programs/opencode/git-release-SKILL.md b/tests/modules/programs/opencode/git-release-SKILL.md new file mode 100644 index 00000000..98e51823 --- /dev/null +++ b/tests/modules/programs/opencode/git-release-SKILL.md @@ -0,0 +1,10 @@ +--- +name: git-release +description: Create consistent releases and changelogs +--- + +## What I do + +- Draft release notes from merged PRs +- Propose a version bump +- Provide a copy-pasteable `gh release create` command diff --git a/tests/modules/programs/opencode/pdf-processing-SKILL.md b/tests/modules/programs/opencode/pdf-processing-SKILL.md new file mode 100644 index 00000000..39eacc5b --- /dev/null +++ b/tests/modules/programs/opencode/pdf-processing-SKILL.md @@ -0,0 +1,9 @@ +--- +name: pdf-processing +description: Extract text and tables from PDF files +--- + +## What I do + +- Extract text from PDFs +- Identify tables and structured data diff --git a/tests/modules/programs/opencode/skill-dir/data-analysis/SKILL.md b/tests/modules/programs/opencode/skill-dir/data-analysis/SKILL.md new file mode 100644 index 00000000..d31bdb26 --- /dev/null +++ b/tests/modules/programs/opencode/skill-dir/data-analysis/SKILL.md @@ -0,0 +1,9 @@ +--- +name: data-analysis +description: Help analyze datasets and results +--- + +## What I do + +- Summarize datasets +- Suggest charts and metrics diff --git a/tests/modules/programs/opencode/skill-dir/data-analysis/notes.txt b/tests/modules/programs/opencode/skill-dir/data-analysis/notes.txt new file mode 100644 index 00000000..5ce20c14 --- /dev/null +++ b/tests/modules/programs/opencode/skill-dir/data-analysis/notes.txt @@ -0,0 +1 @@ +extra fixture file diff --git a/tests/modules/programs/opencode/skills-bulk-directory.nix b/tests/modules/programs/opencode/skills-bulk-directory.nix new file mode 100644 index 00000000..248cf26e --- /dev/null +++ b/tests/modules/programs/opencode/skills-bulk-directory.nix @@ -0,0 +1,15 @@ +{ + programs.opencode = { + enable = true; + skills = ./skills-bulk; + }; + + nmt.script = '' + assertFileExists home-files/.config/opencode/skill/git-release/SKILL.md + assertFileExists home-files/.config/opencode/skill/pdf-processing/SKILL.md + assertFileContent home-files/.config/opencode/skill/git-release/SKILL.md \ + ${./skills-bulk/git-release/SKILL.md} + assertFileContent home-files/.config/opencode/skill/pdf-processing/SKILL.md \ + ${./skills-bulk/pdf-processing/SKILL.md} + ''; +} diff --git a/tests/modules/programs/opencode/skills-bulk/git-release/SKILL.md b/tests/modules/programs/opencode/skills-bulk/git-release/SKILL.md new file mode 100644 index 00000000..7bebce7f --- /dev/null +++ b/tests/modules/programs/opencode/skills-bulk/git-release/SKILL.md @@ -0,0 +1,6 @@ +--- +name: git-release +description: Create consistent releases and changelogs +--- + +This is the bulk skillsDir fixture for git-release. diff --git a/tests/modules/programs/opencode/skills-bulk/pdf-processing/SKILL.md b/tests/modules/programs/opencode/skills-bulk/pdf-processing/SKILL.md new file mode 100644 index 00000000..832e7c9a --- /dev/null +++ b/tests/modules/programs/opencode/skills-bulk/pdf-processing/SKILL.md @@ -0,0 +1,6 @@ +--- +name: pdf-processing +description: Extract text and tables from PDF files +--- + +This is the bulk skillsDir fixture for pdf-processing. diff --git a/tests/modules/programs/opencode/skills-directory.nix b/tests/modules/programs/opencode/skills-directory.nix new file mode 100644 index 00000000..a21f8c43 --- /dev/null +++ b/tests/modules/programs/opencode/skills-directory.nix @@ -0,0 +1,15 @@ +{ + programs.opencode = { + enable = true; + skills = { + data-analysis = ./skill-dir/data-analysis; + }; + }; + + nmt.script = '' + assertFileExists home-files/.config/opencode/skill/data-analysis/SKILL.md + assertFileExists home-files/.config/opencode/skill/data-analysis/notes.txt + assertFileContent home-files/.config/opencode/skill/data-analysis/SKILL.md \ + ${./skill-dir/data-analysis/SKILL.md} + ''; +} diff --git a/tests/modules/programs/opencode/skills-inline.nix b/tests/modules/programs/opencode/skills-inline.nix new file mode 100644 index 00000000..a34a27d7 --- /dev/null +++ b/tests/modules/programs/opencode/skills-inline.nix @@ -0,0 +1,25 @@ +{ + programs.opencode = { + enable = true; + skills = { + git-release = '' + --- + name: git-release + description: Create consistent releases and changelogs + --- + + ## What I do + + - Draft release notes from merged PRs + - Propose a version bump + - Provide a copy-pasteable `gh release create` command + ''; + }; + }; + + nmt.script = '' + assertFileExists home-files/.config/opencode/skill/git-release/SKILL.md + assertFileContent home-files/.config/opencode/skill/git-release/SKILL.md \ + ${./git-release-SKILL.md} + ''; +} diff --git a/tests/modules/programs/opencode/skills-path.nix b/tests/modules/programs/opencode/skills-path.nix new file mode 100644 index 00000000..b26cdbda --- /dev/null +++ b/tests/modules/programs/opencode/skills-path.nix @@ -0,0 +1,14 @@ +{ + programs.opencode = { + enable = true; + skills = { + pdf-processing = ./pdf-processing-SKILL.md; + }; + }; + + nmt.script = '' + assertFileExists home-files/.config/opencode/skill/pdf-processing/SKILL.md + assertFileContent home-files/.config/opencode/skill/pdf-processing/SKILL.md \ + ${./pdf-processing-SKILL.md} + ''; +}