diff --git a/modules/programs/gemini-cli.nix b/modules/programs/gemini-cli.nix new file mode 100644 index 000000000..71845d759 --- /dev/null +++ b/modules/programs/gemini-cli.nix @@ -0,0 +1,117 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.programs.gemini-cli; + + jsonFormat = pkgs.formats.json { }; + tomlFormat = pkgs.formats.toml { }; +in +{ + meta.maintainers = [ lib.hm.maintainers.rrvsh ]; + + options.programs.gemini-cli = { + enable = lib.mkEnableOption "gemini-cli"; + + package = lib.mkPackageOption pkgs "gemini-cli" { nullable = true; }; + + settings = lib.mkOption { + inherit (jsonFormat) type; + default = { }; + example = lib.literalExpression '' + { + "theme": "Default", + "vimMode": true, + "preferredEditor": "nvim", + "autoAccept": true + } + ''; + description = "JSON config for gemini-cli"; + }; + + commands = + let + commandType = lib.types.submodule { + freeformType = lib.types.str; + options = { + prompt = lib.mkOption { + type = lib.types.str; + description = '' + The prompt that will be sent to the Gemini model when the command is executed. + This can be a single-line or multi-line string. + The special placeholder {{args}} will be replaced with the text the user typed after the command name. + ''; + }; + description = lib.mkOption { + type = lib.types.str; + description = '' + A brief, one-line description of what the command does. + This text will be displayed next to your command in the /help menu. + If you omit this field, a generic description will be generated from the filename. + ''; + }; + }; + }; + in + lib.mkOption { + type = lib.types.attrsOf commandType; + default = { }; + description = '' + An attribute set of custom commands that will be globally available. + The name of the attribute set will be the name of each command. + You may use subdirectories to create namespaced commands, such as `git/fix` becoming `/git:fix`. + See https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/commands.md#custom-commands for more information. + ''; + example = lib.literalExpression '' + changelog = { + prompt = + ''' + Your task is to parse the ``, ``, and `` from their input and use the `write_file` tool to correctly update the `CHANGELOG.md` file. + '''; + description = "Adds a new entry to the project's CHANGELOG.md file."; + }; + "git/fix" = { # Becomes /git:fix + prompt = "Please analyze the staged git changes and provide a code fix for the issue described here: {{args}}."; + description = "Generates a fix for a given GitHub issue."; + }; + ''; + }; + + defaultModel = lib.mkOption { + type = lib.types.str; + default = "gemini-2.5-pro"; + example = "gemini-2.5-flash"; + description = '' + The default model to use for the CLI. + Will be set as $GEMINI_MODEL. + ''; + }; + }; + + config = lib.mkIf cfg.enable ( + lib.mkMerge [ + { + home = { + packages = lib.mkIf (cfg.package != null) [ cfg.package ]; + file.".gemini/settings.json" = lib.mkIf (cfg.settings != { }) { + source = jsonFormat.generate "gemini-cli-settings.json" cfg.settings; + }; + sessionVariables.GEMINI_MODEL = cfg.defaultModel; + }; + } + { + home.file = lib.mapAttrs' ( + n: v: + lib.nameValuePair ".gemini/commands/${n}.toml" { + source = tomlFormat.generate "gemini-cli-command-${n}.toml" { + inherit (v) description prompt; + }; + } + ) cfg.commands; + } + ] + ); +} diff --git a/tests/modules/programs/gemini-cli/changelog.toml b/tests/modules/programs/gemini-cli/changelog.toml new file mode 100644 index 000000000..858eb68ac --- /dev/null +++ b/tests/modules/programs/gemini-cli/changelog.toml @@ -0,0 +1,2 @@ +description = "Adds a new entry to the project's CHANGELOG.md file." +prompt = "Your task is to parse the ``, ``, and `` from their input and use the `write_file` tool to correctly update the `CHANGELOG.md` file.\n" diff --git a/tests/modules/programs/gemini-cli/default.nix b/tests/modules/programs/gemini-cli/default.nix new file mode 100644 index 000000000..05c8682fd --- /dev/null +++ b/tests/modules/programs/gemini-cli/default.nix @@ -0,0 +1,3 @@ +{ + gemini-cli-settings = ./settings.nix; +} diff --git a/tests/modules/programs/gemini-cli/fix.toml b/tests/modules/programs/gemini-cli/fix.toml new file mode 100644 index 000000000..807be9f4c --- /dev/null +++ b/tests/modules/programs/gemini-cli/fix.toml @@ -0,0 +1,2 @@ +description = "Generates a fix for a given GitHub issue." +prompt = "Please analyze the staged git changes and provide a code fix for the issue described here: {{args}}." diff --git a/tests/modules/programs/gemini-cli/settings.json b/tests/modules/programs/gemini-cli/settings.json new file mode 100644 index 000000000..8d0604618 --- /dev/null +++ b/tests/modules/programs/gemini-cli/settings.json @@ -0,0 +1,6 @@ +{ + "autoAccept": true, + "preferredEditor": "nvim", + "theme": "Default", + "vimMode": true +} diff --git a/tests/modules/programs/gemini-cli/settings.nix b/tests/modules/programs/gemini-cli/settings.nix new file mode 100644 index 000000000..f78a14f65 --- /dev/null +++ b/tests/modules/programs/gemini-cli/settings.nix @@ -0,0 +1,32 @@ +{ + programs.gemini-cli = { + enable = true; + settings = { + theme = "Default"; + vimMode = true; + preferredEditor = "nvim"; + autoAccept = true; + }; + commands = { + changelog = { + prompt = '' + Your task is to parse the ``, ``, and `` from their input and use the `write_file` tool to correctly update the `CHANGELOG.md` file. + ''; + description = "Adds a new entry to the project's CHANGELOG.md file."; + }; + "git/fix" = { + prompt = "Please analyze the staged git changes and provide a code fix for the issue described here: {{args}}."; + description = "Generates a fix for a given GitHub issue."; + }; + }; + }; + nmt.script = '' + assertFileExists home-files/.gemini/settings.json + assertFileContent home-files/.gemini/settings.json \ + ${./settings.json} + assertFileContent home-files/.gemini/commands/changelog.toml \ + ${./changelog.toml} + assertFileContent home-files/.gemini/commands/git/fix.toml \ + ${./fix.toml} + ''; +}