diff --git a/flake.lock b/flake.lock index f1b3fa8..b147994 100644 --- a/flake.lock +++ b/flake.lock @@ -104,27 +104,6 @@ "type": "github" } }, - "crush": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1753380422, - "narHash": "sha256-olzhQJVVBfH+ooeTcNF9jK/T1iHac3+lu/Stu9Sqhlg=", - "ref": "taciturnaxoltol/flake", - "rev": "0ccf3d9e6935f7ac9e213707583dcb3e18effc0b", - "revCount": 944, - "type": "git", - "url": "ssh://git@github.com/charmbracelet/crush" - }, - "original": { - "ref": "taciturnaxoltol/flake", - "type": "git", - "url": "ssh://git@github.com/charmbracelet/crush" - } - }, "ctfd-alerts": { "inputs": { "nixpkgs": [ @@ -259,6 +238,27 @@ "type": "github" } }, + "flake-parts_3": { + "inputs": { + "nixpkgs-lib": [ + "nur", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1733312601, + "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems_2" @@ -883,6 +883,27 @@ "type": "github" } }, + "nur": { + "inputs": { + "flake-parts": "flake-parts_3", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1754684884, + "narHash": "sha256-GH+UMIOJj7u/bW55dOOpD8HpVpc9WfU61iweM2nM68A=", + "owner": "nix-community", + "repo": "NUR", + "rev": "a7f9761c9dd71359cd9a6529078302a83e6deaac", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "NUR", + "type": "github" + } + }, "nuschtosSearch": { "inputs": { "flake-utils": "flake-utils_7", @@ -933,7 +954,6 @@ "catppuccin": "catppuccin", "catppuccin-vsc": "catppuccin-vsc", "claude-desktop": "claude-desktop", - "crush": "crush", "ctfd-alerts": "ctfd-alerts", "disko": "disko", "flare": "flare", @@ -948,6 +968,7 @@ "nixpkgs": "nixpkgs_5", "nixpkgs-unstable": "nixpkgs-unstable", "nixvim": "nixvim", + "nur": "nur", "spicetify-nix": "spicetify-nix", "terminal-wakatime": "terminal-wakatime", "zed": "zed" diff --git a/flake.nix b/flake.nix index 179dc76..01fc577 100644 --- a/flake.nix +++ b/flake.nix @@ -77,17 +77,17 @@ inputs.nixpkgs.follows = "nixpkgs"; }; - crush = { - url = "git+ssh://git@github.com/charmbracelet/crush?ref=taciturnaxoltol/flake"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - flare = { url = "github:ByteAtATime/flare/feat/nix"; inputs.nixpkgs.follows = "nixpkgs"; }; import-tree.url = "github:vic/import-tree"; + + nur = { + url = "github:nix-community/NUR"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = @@ -98,6 +98,7 @@ lix-module, agenix, home-manager, + nur, ... }@inputs: let @@ -138,6 +139,7 @@ unstable-overlays { nixpkgs.hostPlatform = "x86_64-linux"; } ./nixos/machines/moonlark/configuration.nix + nur.modules.nixos.default ]; }; }; diff --git a/home-manager/modules/apps/_crush-options.nix b/home-manager/modules/apps/_crush-options.nix new file mode 100644 index 0000000..ca9ff77 --- /dev/null +++ b/home-manager/modules/apps/_crush-options.nix @@ -0,0 +1,301 @@ +{ lib }: +lib.mkOption { + type = lib.types.submodule { + options = { + providers = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + name = lib.mkOption { + type = lib.types.str; + description = "Human-readable name for the provider"; + }; + base_url = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Base URL for the provider's API"; + }; + type = lib.mkOption { + type = lib.types.enum [ + "openai" + "anthropic" + "gemini" + "azure" + "vertexai" + ]; + default = "openai"; + description = "Provider type that determines the API format"; + }; + api_key = lib.mkOption { + type = lib.types.str; + default = ""; + description = "API key for authentication with the provider"; + }; + disable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether this provider is disabled"; + }; + system_prompt_prefix = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Custom prefix to add to system prompts for this provider"; + }; + extra_headers = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + description = "Additional HTTP headers to send with requests"; + }; + extra_body = lib.mkOption { + type = lib.types.attrsOf lib.types.anything; + default = { }; + description = "Additional fields to include in request bodies"; + }; + models = lib.mkOption { + type = lib.types.listOf ( + lib.types.submodule { + options = { + id = lib.mkOption { + type = lib.types.str; + description = "Model ID"; + }; + name = lib.mkOption { + type = lib.types.str; + description = "Model display name"; + }; + cost_per_1m_in = lib.mkOption { + type = lib.types.number; + default = 0; + }; + cost_per_1m_out = lib.mkOption { + type = lib.types.number; + default = 0; + }; + cost_per_1m_in_cached = lib.mkOption { + type = lib.types.number; + default = 0; + }; + cost_per_1m_out_cached = lib.mkOption { + type = lib.types.number; + default = 0; + }; + context_window = lib.mkOption { + type = lib.types.int; + default = 128000; + }; + default_max_tokens = lib.mkOption { + type = lib.types.int; + default = 8192; + }; + can_reason = lib.mkOption { + type = lib.types.bool; + default = false; + }; + has_reasoning_efforts = lib.mkOption { + type = lib.types.bool; + default = false; + }; + default_reasoning_effort = lib.mkOption { + type = lib.types.str; + default = ""; + }; + supports_attachments = lib.mkOption { + type = lib.types.bool; + default = false; + }; + }; + } + ); + default = [ ]; + description = "List of models available from this provider"; + }; + }; + } + ); + default = { }; + description = "AI provider configurations"; + }; + + lsp = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + command = lib.mkOption { + type = lib.types.str; + description = "Command to execute for the LSP server"; + }; + args = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Arguments to pass to the LSP server command"; + }; + options = lib.mkOption { + type = lib.types.attrsOf lib.types.anything; + default = { }; + description = "LSP server-specific configuration options"; + }; + enabled = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether this LSP server is disabled"; + }; + }; + } + ); + default = { }; + description = "Language Server Protocol configurations"; + }; + + mcp = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + command = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Command to execute for stdio MCP servers"; + }; + env = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + description = "Environment variables to set for the MCP server"; + }; + args = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Arguments to pass to the MCP server command"; + }; + type = lib.mkOption { + type = lib.types.enum [ + "stdio" + "sse" + "http" + ]; + default = "stdio"; + description = "Type of MCP connection"; + }; + url = lib.mkOption { + type = lib.types.str; + default = ""; + description = "URL for HTTP or SSE MCP servers"; + }; + disabled = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether this MCP server is disabled"; + }; + headers = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + description = "HTTP headers for HTTP/SSE MCP servers"; + }; + }; + } + ); + default = { }; + description = "Model Context Protocol server configurations"; + }; + + options = lib.mkOption { + type = lib.types.submodule { + options = { + context_paths = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Paths to files containing context information for the AI"; + }; + tui = lib.mkOption { + type = lib.types.submodule { + options = { + compact_mode = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Enable compact mode for the TUI interface"; + }; + }; + }; + default = { }; + description = "Terminal user interface options"; + }; + debug = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Enable debug logging"; + }; + debug_lsp = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Enable debug logging for LSP servers"; + }; + disable_auto_summarize = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Disable automatic conversation summarization"; + }; + data_directory = lib.mkOption { + type = lib.types.str; + default = ".crush"; + description = "Directory for storing application data (relative to working directory)"; + }; + }; + }; + default = { }; + description = "General application options"; + }; + + permissions = lib.mkOption { + type = lib.types.submodule { + options = { + allowed_tools = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "List of tools that don't require permission prompts"; + }; + }; + }; + default = { }; + description = "Permission settings for tool usage"; + }; + + models = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + model = lib.mkOption { + type = lib.types.str; + description = "The model ID as used by the provider API"; + }; + provider = lib.mkOption { + type = lib.types.str; + description = "The model provider ID that matches a key in the providers config"; + }; + reasoning_effort = lib.mkOption { + type = lib.types.enum [ + "low" + "medium" + "high" + ]; + default = ""; + description = "Reasoning effort level for OpenAI models that support it"; + }; + max_tokens = lib.mkOption { + type = lib.types.int; + default = 0; + description = "Maximum number of tokens for model responses"; + }; + think = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Enable thinking mode for Anthropic models that support reasoning"; + }; + }; + } + ); + default = { }; + description = "Model configurations"; + }; + }; + }; + default = { }; + description = "Crush configuration options"; +} diff --git a/home-manager/modules/apps/crush-module.nix b/home-manager/modules/apps/crush-module.nix new file mode 100644 index 0000000..c2ff664 --- /dev/null +++ b/home-manager/modules/apps/crush-module.nix @@ -0,0 +1,28 @@ +{ + config, + lib, + pkgs, + inputs, + ... +}: +{ + imports = [ + inputs.nur.modules.homeManager.default + ]; + + options.programs.crush = { + enable = lib.mkEnableOption "Enable crush"; + settings = import ./_crush-options.nix { inherit lib; }; + }; + + config = lib.mkIf config.programs.crush.enable { + home.packages = [ pkgs.nur.repos.charmbracelet.crush ]; + home.file.".config/crush/crush.json" = lib.mkIf (config.programs.crush.settings != { }) { + text = builtins.toJSON config.programs.crush.settings; + }; + + # Optionally, add config file sources if needed + xdg.configFile."crush/copilot.sh".source = ../../dots/copilot.sh; + xdg.configFile."crush/anthropic.sh".source = ../../dots/anthropic.sh; + }; +} diff --git a/home-manager/modules/apps/crush.nix b/home-manager/modules/apps/crush.nix index b645292..3e32065 100644 --- a/home-manager/modules/apps/crush.nix +++ b/home-manager/modules/apps/crush.nix @@ -1,14 +1,9 @@ { lib, config, - inputs, ... }: { - imports = [ - inputs.crush.homeManagerModules.default - ]; - options.dots.apps.crush.enable = lib.mkEnableOption "Enable Crush config"; config = lib.mkIf config.dots.apps.crush.enable { programs.crush = { @@ -39,16 +34,6 @@ ]; }; }; - models = { - large = { - model = "claude-3.7-sonnet"; - provider = "copilot"; - }; - small = { - model = "gemini-2.0-flash-001"; - provider = "copilot"; - }; - }; providers = { copilot = { name = "Copilot"; @@ -63,7 +48,7 @@ models = [ { id = "gpt-4.1"; - model = "Copilot: GPT 4.1"; + name = "Copilot: GPT 4.1"; cost_per_1m_in = 0; cost_per_1m_out = 0; cost_per_1m_in_cached = 0; @@ -76,7 +61,7 @@ } { id = "gpt-4o"; - model = "Copilot: GPT 4o"; + name = "Copilot: GPT 4o"; cost_per_1m_in = 0; cost_per_1m_out = 0; cost_per_1m_in_cached = 0; @@ -89,7 +74,7 @@ } { id = "claude-sonnet-4"; - model = "Copilot: Claude Sonnet 4"; + name = "Copilot: Claude Sonnet 4"; cost_per_1m_in = 0; cost_per_1m_out = 0; cost_per_1m_in_cached = 0; @@ -102,7 +87,7 @@ } { id = "gemini-2.5-pro"; - model = "Gemini 2.5 Pro"; + name = "Gemini 2.5 Pro"; cost_per_1m_in = 0; cost_per_1m_out = 0; cost_per_1m_in_cached = 0; @@ -128,11 +113,11 @@ models = [ { id = "claude-opus-4-20250514"; - model = "Claude Opus 4"; - cost_per_1m_in = 15000; - cost_per_1m_out = 75000; - cost_per_1m_in_cached = 1125; - cost_per_1m_out_cached = 75000; + name = "Claude Opus 4"; + cost_per_1m_in = 15.0; + cost_per_1m_out = 75.0; + cost_per_1m_in_cached = 1.5; + cost_per_1m_out_cached = 75.0; context_window = 200000; default_max_tokens = 50000; can_reason = true; @@ -141,7 +126,7 @@ } { id = "claude-sonnet-4-20250514"; - model = "Claude Sonnet 4"; + name = "Claude Sonnet 4"; cost_per_1m_in = 3000; cost_per_1m_out = 15000; cost_per_1m_in_cached = 225; @@ -154,11 +139,11 @@ } { id = "claude-3-7-sonnet-20250219"; - model = "Claude 3.7 Sonnet"; - cost_per_1m_in = 2500; - cost_per_1m_out = 12000; - cost_per_1m_in_cached = 187; - cost_per_1m_out_cached = 12000; + name = "Claude 3.7 Sonnet"; + cost_per_1m_in = 2.5; + cost_per_1m_out = 12.0; + cost_per_1m_in_cached = 0.187; + cost_per_1m_out_cached = 12.0; context_window = 200000; default_max_tokens = 128000; can_reason = true; @@ -167,11 +152,11 @@ } { id = "claude-3-5-sonnet-20241022"; - model = "Claude 3.5 Sonnet (Latest)"; - cost_per_1m_in = 3000; - cost_per_1m_out = 15000; - cost_per_1m_in_cached = 225; - cost_per_1m_out_cached = 15000; + name = "Claude 3.5 Sonnet (Latest)"; + cost_per_1m_in = 3.0; + cost_per_1m_out = 15.0; + cost_per_1m_in_cached = 0.225; + cost_per_1m_out_cached = 15.0; context_window = 200000; default_max_tokens = 8192; can_reason = false; @@ -180,11 +165,11 @@ } { id = "claude-3-5-haiku-20241022"; - model = "Claude 3.5 Haiku"; - cost_per_1m_in = 800; - cost_per_1m_out = 4000; - cost_per_1m_in_cached = 60; - cost_per_1m_out_cached = 4000; + name = "Claude 3.5 Haiku"; + cost_per_1m_in = 0.8; + cost_per_1m_out = 4.0; + cost_per_1m_in_cached = 0.06; + cost_per_1m_out_cached = 4.0; context_window = 200000; default_max_tokens = 8192; can_reason = false;