diff --git a/modules/darwin/home/default.nix b/modules/darwin/home/default.nix index 0a42ffa..5015249 100644 --- a/modules/darwin/home/default.nix +++ b/modules/darwin/home/default.nix @@ -62,6 +62,11 @@ format = "yaml"; key = "FIRECRAWL_API_KEY"; }; + + "kimi-api-key" = { + format = "yaml"; + key = "KIMI_API_KEY"; + }; }; }; } diff --git a/modules/home/base.nix b/modules/home/base.nix index edc33e4..86f8e03 100644 --- a/modules/home/base.nix +++ b/modules/home/base.nix @@ -37,6 +37,7 @@ rsydn.aiTools = { enable = lib.mkDefault true; zai.enable = lib.mkDefault true; + kimi.enable = lib.mkDefault true; }; rsydn.try = { diff --git a/modules/home/devtools/ai-tools.nix b/modules/home/devtools/ai-tools.nix index abf5bcf..dbe1728 100644 --- a/modules/home/devtools/ai-tools.nix +++ b/modules/home/devtools/ai-tools.nix @@ -54,6 +54,37 @@ let else [ ]; + # Kimi Code API wrapper for Claude Code + kimiWrapperPackages = + let + kimiCfg = cfg.kimi; + in + if kimiCfg.enable then + let + claudeExe = lib.getExe llmPkgsPinned.claude-code; + commandName = kimiCfg.commandName; + baseUrl = kimiCfg.baseUrl; + tokenEnvVar = kimiCfg.tokenEnvVar; + in + [ + (pkgs.writeShellApplication { + name = commandName; + text = '' + if [ -z "''${${tokenEnvVar}:-}" ]; then + echo "${commandName}: environment variable ${tokenEnvVar} is not set" >&2 + exit 1 + fi + + export ANTHROPIC_BASE_URL=${escapeShellArg baseUrl} + export ANTHROPIC_API_KEY="''${${tokenEnvVar}}" + + exec ${claudeExe} "$@" + ''; + }) + ] + else + [ ]; + in { options.rsydn.aiTools = { @@ -95,6 +126,36 @@ in description = "Configuration for the Claude wrapper that targets the Z.AI gateway."; }; + kimi = mkOption { + type = types.submodule { + options = { + enable = mkEnableOption "Expose the Kimi Code API wrapper command for Claude."; + commandName = mkOption { + type = types.str; + default = "kimi"; + description = "Name of the wrapper command that launches Claude via Kimi."; + }; + baseUrl = mkOption { + type = types.str; + default = "https://api.kimi.com/coding/"; + description = "Kimi Code API base URL used for the Anthropic client."; + }; + tokenEnvVar = mkOption { + type = types.str; + default = "KIMI_API_KEY"; + description = "Environment variable that stores the Kimi API token."; + }; + }; + }; + default = { + enable = false; + commandName = "kimi"; + baseUrl = "https://api.kimi.com/coding/"; + tokenEnvVar = "KIMI_API_KEY"; + }; + description = "Configuration for the Claude wrapper that targets the Kimi Code API."; + }; + extraPackages = mkOption { type = types.listOf types.package; default = [ ]; @@ -112,6 +173,7 @@ in llmPkgs.codex ] ++ zaiWrapperPackages + ++ kimiWrapperPackages ++ cfg.extraPackages; }; } diff --git a/modules/home/secrets.nix b/modules/home/secrets.nix index 354b9f5..696d88c 100644 --- a/modules/home/secrets.nix +++ b/modules/home/secrets.nix @@ -74,6 +74,11 @@ in generateKey = true; }; + # Ensure the LaunchAgent on macOS can find `getconf` (needed by + # sops-install-secrets to resolve DARWIN_USER_TEMP_DIR). + # Without this, age.plugins produces an empty PATH. + environment.PATH = lib.mkForce "/usr/bin"; + secrets = lib.mapAttrs sanitizeSecret cfg.secrets; } // (lib.optionalAttrs (cfg.defaultSopsFile != null) { diff --git a/modules/home/shell/nushell.nix b/modules/home/shell/nushell.nix index b75c680..f357410 100644 --- a/modules/home/shell/nushell.nix +++ b/modules/home/shell/nushell.nix @@ -198,5 +198,6 @@ in load-secret "fal-api-key" "FAL_API_KEY" load-secret "groq-api-key" "GROQ_API_KEY" load-secret "firecrawl-api-key" "FIRECRAWL_API_KEY" + load-secret "kimi-api-key" "KIMI_API_KEY" ''; } diff --git a/secrets/local-ai-tokens.sops.yaml b/secrets/local-ai-tokens.sops.yaml index 296a850..dbbf15a 100644 --- a/secrets/local-ai-tokens.sops.yaml +++ b/secrets/local-ai-tokens.sops.yaml @@ -9,6 +9,7 @@ EXA_API_KEY: ENC[AES256_GCM,data:rMNonp76DeS1VtZuvs45Fwhj2aIVfoNWENHp3J9OxajK8uY ANTHROPIC_API_KEY: ENC[AES256_GCM,data:aBgfgHQ8cyJ4TUq4O6y218flTgDWcAXatY0/xbaD3AVvirCaEUXfsPP+0ySlVxlzx8hj9gMizlY2gJkNarsgwHCikOtmh/ZCxbQMbwogByqdV3C4myY7UwMgZdYzZGaQBL0JWR6ZZjf9ZK2T,iv:QB2tYhBtOU45Ho65FwQhD2H6gv7VobDXWgV/S9qu/yc=,tag:TTkEyjiTHYA1ZnMMwHwk4g==,type:str] OPENCODE_API_KEY: ENC[AES256_GCM,data:2aPaiODhpFObObRN+/HiEVsvMds1n6MhrmvQUWo2ozRzgRHQ1dGGZYFzOZoSbV4vpjmiqKW7VUFnVU5va1xlS8HrWg==,iv:DgCF9I+wT9xUjmenfUSDrDnKD5Ai4pJGw4qKmePpZOo=,tag:YD7nO4HbDKTa3zNbGtUCOg==,type:str] BRAVE_SEARCH_API_KEY: ENC[AES256_GCM,data:7Q+8sa2Xp4amEMkGzgEZzSLDCkstL71lu3sz1iGJdg==,iv:pY4NMgIUEEqwBUonAOAVoqP0EeqgON7h23221Yoa4aM=,tag:foyjDNIMsxrKvgYmu6FJNA==,type:str] +KIMI_API_KEY: ENC[AES256_GCM,data:hkq/IY++1xGPmFMt5UMFlA/mVCOKDh+4V1Ip6Bv5ZSWTwdqwP1ghuL3HpF/r4Y+eO+PfzcZso1A8z+4SqVlXBjmiaORHIPut,iv:UWcdLW+ITk8jtc/UbprKz8zxI3mgAn1DP+tch0tse8M=,tag:brx5KyUzl1wg+fTagMUphQ==,type:str] sops: age: - recipient: age1qfgey5w87dresg99zed3d2yz62hv6c2c7u4gd5e880vjrngmx9ksgfv3sj @@ -20,7 +21,7 @@ sops: WHRrVWx2Y3FabWpBdjZ5VkpFY1BGeGsKSc4vxaHSj/6g7clPZDOPqCf3Zo+VGSRy HwLiHEohjk5/R3I5KWhFuO9aNQTNYYnUBwc6nOAcEhw+Mihq2gDtzg== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-09-30T16:59:49Z" - mac: ENC[AES256_GCM,data:HmA7khtD8oapqPPZ7AxZjxSwkqmhGb0TP6u2S6q20I/B/xittoH3EGs5bq9AaeY9wGAkxper3VlPXsBXs67pOZVJOuB/0h22DlOvrI6eS83y5ZSc+lPQgfK+/93LwbWhSe6l5EKRTGSHZMclr+raMsbjRPP/zl9Lf26fkz79XCI=,iv:73kl7xWE1slcc3YmR7lVsRYvsLW8XQ67qE9GfgBuRRU=,tag:f4LyjoGOgC7tYcFGNgpXlw==,type:str] + lastmodified: "2026-02-03T07:35:18Z" + mac: ENC[AES256_GCM,data:MWrijOPL9whg++YKwmLWT1hFu98FIDuZgis7g77mXFCIPMHn2Lz9JV5wdkUJY4ZNAlFG/FwZDG2QZQDp3aD+syN0UGVdmJmRsNcn7JITyABvXWWwJIo3kyBJj5mJMpcFooJEZpTsNp4hYr4ozrBl8/v1QyIcnY7L5NeO5DdyaXw=,iv:dCoCan6mOwDDD3dpvdFJkUzNfOttVJo3hb6vLBWap68=,tag:dWGz6szFXp+IW8QgI7z2CA==,type:str] unencrypted_suffix: _unencrypted - version: 3.10.2 + version: 3.11.0