diff --git a/modules/home-manager/coding/opencode.nix b/modules/home-manager/coding/opencode.nix new file mode 100644 index 0000000..6420abb --- /dev/null +++ b/modules/home-manager/coding/opencode.nix @@ -0,0 +1,156 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.coding.opencode; +in { + options.coding.opencode = { + enable = mkEnableOption "opencode AI coding assistant"; + + agentsInput = mkOption { + type = types.nullOr types.anything; + default = null; + description = '' + The `agents` flake input (your personal AGENTS repo). + When set, skills, context, commands, prompts and the agents.json + are all symlinked from this input. + ''; + }; + + externalSkills = mkOption { + type = types.listOf (types.submodule { + options = { + src = mkOption { + type = types.anything; + description = "Flake input pointing to a skills repository root."; + }; + skillsDir = mkOption { + type = types.str; + default = "skills"; + description = '' + Subdirectory inside src that contains skill folders. + ''; + }; + selectSkills = mkOption { + type = types.nullOr (types.listOf types.str); + default = null; + description = '' + List of skill names to cherry-pick from this source. + null means include every skill found in skillsDir. + ''; + }; + }; + }); + default = []; + description = '' + External skill sources passed to mkOpencodeSkills. + Each entry maps directly to an element of the externalSkills + list accepted by the AGENTS flake's lib.mkOpencodeSkills. + ''; + example = literalExpression '' + [ + { src = inputs.skills-anthropic; selectSkills = [ "claude-api" ]; } + { src = inputs.skills-vercel; } + ] + ''; + }; + + ohMyOpencodeSettings = mkOption { + type = types.attrs; + default = {}; + description = '' + Attributes merged (via recursiveUpdate) on top of the default + oh-my-opencode.json. Use this to set provider-specific model + assignments per machine. + ''; + example = literalExpression '' + { + agents.sisyphus.model = "anthropic/claude-opus-4-5"; + categories.ultrabrain.model = "anthropic/claude-opus-4-5"; + } + ''; + }; + + extraSettings = mkOption { + type = types.attrs; + default = {}; + description = '' + Extra opencode settings merged (via mkMerge) into + programs.opencode.settings. Use this to add provider + configuration that is specific to a machine or organisation. + ''; + example = literalExpression '' + { + provider.anthropic = { + name = "Anthropic"; + models."claude-opus-4-5" = { limit.context = 200000; }; + }; + } + ''; + }; + }; + + config = mkIf cfg.enable { + # --- Skills (merged from personal AGENTS repo + optional external skills) --- + xdg.configFile."opencode/skills" = mkIf (cfg.agentsInput != null) { + source = cfg.agentsInput.lib.mkOpencodeSkills { + inherit pkgs; + customSkills = "${cfg.agentsInput}/skills"; + externalSkills = map (entry: + {inherit (entry) src skillsDir;} + // optionalAttrs (entry.selectSkills != null) {inherit (entry) selectSkills;} + ) cfg.externalSkills; + }; + }; + + # --- Static config dirs from AGENTS repo --- + xdg.configFile."opencode/context" = mkIf (cfg.agentsInput != null) { + source = "${cfg.agentsInput}/context"; + }; + xdg.configFile."opencode/commands" = mkIf (cfg.agentsInput != null) { + source = "${cfg.agentsInput}/commands"; + }; + xdg.configFile."opencode/prompts" = mkIf (cfg.agentsInput != null) { + source = "${cfg.agentsInput}/prompts"; + }; + + # --- Core opencode program settings --- + programs.opencode = { + enable = true; + enableMcpIntegration = true; + settings = mkMerge [ + { + theme = "opencode"; + plugin = ["oh-my-opencode"]; + formatter = { + alejandra = { + command = ["alejandra" "-q" "-"]; + extensions = [".nix"]; + }; + }; + } + # Load agents.json from AGENTS repo when available + (mkIf (cfg.agentsInput != null) { + agent = builtins.fromJSON (builtins.readFile "${cfg.agentsInput}/agents/agents.json"); + }) + # Machine/org-specific provider config + cfg.extraSettings + ]; + }; + + # --- oh-my-opencode plugin config --- + # Base defaults (no models — those must be set per machine via ohMyOpencodeSettings) + home.file.".config/opencode/oh-my-opencode.json".text = builtins.toJSON ( + recursiveUpdate + { + "$schema" = "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json"; + google_auth = false; + disabled_mcps = ["context7" "websearch"]; + } + cfg.ohMyOpencodeSettings + ); + }; +}