From 7a0fa96129fa886b0191ba623faf589ae890f6b5 Mon Sep 17 00:00:00 2001 From: William Date: Tue, 16 Dec 2025 10:18:38 -0300 Subject: [PATCH] claude-desktop package --- flake.nix | 2 +- hosts/modules/ai.nix | 13 ++- overlays.nix | 1 + packages.nix | 11 +- packages/claude-desktop.nix | 221 ++++++++++++++++++++++++++++++++++++ 5 files changed, 239 insertions(+), 9 deletions(-) create mode 100644 packages/claude-desktop.nix diff --git a/flake.nix b/flake.nix index d65e9d8..20210f2 100644 --- a/flake.nix +++ b/flake.nix @@ -50,7 +50,7 @@ inputs.nixpkgs.follows = "nixpkgs"; }; - nix-ai-tools.url = "github:numtide/nix-ai-tools"; + nix-ai-tools.url = "github:numtide/llm-agents.nix"; vicinae.url = "github:vicinaehq/vicinae"; }; diff --git a/hosts/modules/ai.nix b/hosts/modules/ai.nix index e2dd9d2..b80ac38 100644 --- a/hosts/modules/ai.nix +++ b/hosts/modules/ai.nix @@ -1,10 +1,11 @@ { inputs, pkgs, ... }: { - environment.systemPackages = with inputs.nix-ai-tools.packages.${pkgs.system}; [ - claude-desktop - claude-code - claudebox - opencode - ]; + environment.systemPackages = + (with pkgs; [claude-desktop]) ++ + (with inputs.nix-ai-tools.packages.${pkgs.system}; [ + claude-code + claudebox + opencode + ]); } diff --git a/overlays.nix b/overlays.nix index eab4edf..b8f807f 100644 --- a/overlays.nix +++ b/overlays.nix @@ -4,6 +4,7 @@ flake.overlays = { default = final: prev: { base16-schemes = inputs.self.packages.${final.system}.base16-schemes; + claude-desktop = inputs.self.packages.${final.system}.claude-desktop; fastfetch = inputs.self.packages.${final.system}.fastfetch; hm-cli = inputs.self.packages.${final.system}.hm-cli; kwrite = inputs.self.packages.${final.system}.kwrite; diff --git a/packages.nix b/packages.nix index 46979f8..bf0319e 100644 --- a/packages.nix +++ b/packages.nix @@ -1,11 +1,18 @@ -{ ... }: +{ inputs, ... }: { perSystem = - { pkgs, system, ... }: + { system, ... }: + let + pkgs = import inputs.nixpkgs { + inherit system; + config.allowUnfree = true; + }; + in { packages = { base16-schemes = pkgs.callPackage ./packages/base16-schemes.nix { }; + claude-desktop = pkgs.callPackage ./packages/claude-desktop.nix { }; fastfetch = pkgs.callPackage ./packages/fastfetch.nix { }; hm-cli = pkgs.callPackage ./packages/hm-cli.nix { }; kwrite = pkgs.callPackage ./packages/kwrite.nix { }; diff --git a/packages/claude-desktop.nix b/packages/claude-desktop.nix new file mode 100644 index 0000000..e72fc37 --- /dev/null +++ b/packages/claude-desktop.nix @@ -0,0 +1,221 @@ +{ + lib, + stdenv, + fetchurl, + makeWrapper, + makeDesktopItem, + copyDesktopItems, + p7zip, + unzip, + electron, + nodejs, + asar, + graphicsmagick, +}: + +let + pname = "claude-desktop"; + version = "1.0.1768"; # Updated based on extracted nupkg + + srcs.x86_64-linux = fetchurl { + url = "https://downloads.claude.ai/releases/win32/x64/1.0.1768/Claude-67d01376d0e9d08b328455f6db9e63b0d603506a.exe"; + hash = "sha256-x76Qav38ya3ObpWIq3dDowo79LgvVquMfaZeH8M1LUk=;"; + }; + + src = + srcs.${stdenv.hostPlatform.system} or (throw "Unsupported system: ${stdenv.hostPlatform.system}"); + + # Stub implementation for claude-native module + claudeNativeStub = '' + // Stub implementation of claude-native using KeyboardKey enum values + const KeyboardKey = { + Backspace: 43, Tab: 280, Enter: 261, Shift: 272, Control: 61, Alt: 40, + CapsLock: 56, Escape: 85, Space: 276, PageUp: 251, PageDown: 250, + End: 83, Home: 154, LeftArrow: 175, UpArrow: 282, RightArrow: 262, + DownArrow: 81, Delete: 79, Meta: 187 + }; + Object.freeze(KeyboardKey); + module.exports = { + getWindowsVersion: () => "10.0.0", + setWindowEffect: () => {}, + removeWindowEffect: () => {}, + getIsMaximized: () => false, + flashFrame: () => {}, + clearFlashFrame: () => {}, + showNotification: () => {}, + setProgressBar: () => {}, + clearProgressBar: () => {}, + setOverlayIcon: () => {}, + clearOverlayIcon: () => {}, + KeyboardKey + }; + ''; + +in +stdenv.mkDerivation rec { + inherit pname version src; + + nativeBuildInputs = [ + makeWrapper + copyDesktopItems + p7zip + unzip + nodejs + graphicsmagick + ]; + + buildInputs = [ + electron + ]; + + desktopItems = [ + (makeDesktopItem { + name = "claude-desktop"; + desktopName = "Claude"; + comment = "AI assistant from Anthropic"; + exec = "claude-desktop %u"; + icon = "claude-desktop"; + categories = [ + "Network" + "Chat" + "Office" + ]; + mimeTypes = [ "x-scheme-handler/claude" ]; + startupNotify = true; + startupWMClass = "Claude"; + }) + ]; + + unpackPhase = '' + runHook preUnpack + + # Extract the Windows installer - use -y to auto-overwrite + 7z x -y $src -o./extracted + + # The installer contains a NuGet package + if [ -f ./extracted/AnthropicClaude-*-full.nupkg ]; then + echo "Found NuGet package, extracting..." + # NuGet packages are just zip files + unzip -q ./extracted/AnthropicClaude-*-full.nupkg -d ./nupkg + + # Extract app.asar to modify it + if [ -f ./nupkg/lib/net45/resources/app.asar ]; then + echo "Extracting app.asar..." + ${asar}/bin/asar extract ./nupkg/lib/net45/resources/app.asar ./app + + # Also copy the unpacked resources + if [ -d ./nupkg/lib/net45/resources/app.asar.unpacked ]; then + cp -r ./nupkg/lib/net45/resources/app.asar.unpacked/* ./app/ + fi + + # Copy additional resources + mkdir -p ./app/resources + mkdir -p ./app/resources/i18n + cp ./nupkg/lib/net45/resources/Tray* ./app/resources/ || true + cp ./nupkg/lib/net45/resources/*-*.json ./app/resources/i18n/ || true + fi + else + echo "NuGet package not found" + ls -la ./extracted/ + exit 1 + fi + + runHook postUnpack + ''; + + buildPhase = '' + runHook preBuild + + # Replace the Windows-specific claude-native module with a stub + if [ -d ./app/node_modules/claude-native ]; then + echo "Replacing claude-native module with Linux stub..." + rm -rf ./app/node_modules/claude-native/*.node + cat > ./app/node_modules/claude-native/index.js << 'EOF' + ${claudeNativeStub} + EOF + fi + + # Fix the title bar detection (from aaddrick script) + echo "Fixing title bar detection..." + SEARCH_BASE="./app/.vite/renderer/main_window/assets" + if [ -d "$SEARCH_BASE" ]; then + TARGET_FILE=$(find "$SEARCH_BASE" -type f -name "MainWindowPage-*.js" | head -1) + if [ -n "$TARGET_FILE" ]; then + echo "Found target file: $TARGET_FILE" + # Replace patterns like 'if(!VAR1 && VAR2)' with 'if(VAR1 && VAR2)' + sed -i -E 's/if\(!([a-zA-Z]+)[[:space:]]*&&[[:space:]]*([a-zA-Z]+)\)/if(\1 \&\& \2)/g' "$TARGET_FILE" + echo "Title bar fix applied" + fi + fi + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/lib/claude-desktop + + # Repack the modified app as app.asar + cd ./app + ${asar}/bin/asar pack . ../app.asar + cd .. + + # Copy resources + mkdir -p $out/lib/claude-desktop/resources + cp ./app.asar $out/lib/claude-desktop/resources/ + + # Create app.asar.unpacked directory with the stub + mkdir -p $out/lib/claude-desktop/resources/app.asar.unpacked/node_modules/claude-native + cat > $out/lib/claude-desktop/resources/app.asar.unpacked/node_modules/claude-native/index.js << 'EOF' + ${claudeNativeStub} + EOF + + # Copy other resources + if [ -d ./nupkg/lib/net45/resources ]; then + cp ./nupkg/lib/net45/resources/*.png $out/lib/claude-desktop/resources/ 2>/dev/null || true + cp ./nupkg/lib/net45/resources/*.ico $out/lib/claude-desktop/resources/ 2>/dev/null || true + cp ./nupkg/lib/net45/resources/*.json $out/lib/claude-desktop/resources/ 2>/dev/null || true + fi + + # Create wrapper script + makeWrapper ${electron}/bin/electron $out/bin/claude-desktop \ + --add-flags "$out/lib/claude-desktop/resources/app.asar" \ + --set DISABLE_AUTOUPDATER 1 \ + --set NODE_ENV production + + # Extract and install icons in multiple sizes + if [ -f ./extracted/setupIcon.ico ]; then + echo "Converting and installing icons..." + # Count frames in the ICO file and extract each one + frame_count=$(gm identify ./extracted/setupIcon.ico | wc -l) + for i in $(seq 0 $((frame_count - 1))); do + gm convert "./extracted/setupIcon.ico[$i]" "./extracted/setupIcon-$i.png" 2>/dev/null || true + done + + # Loop through converted icons and install them by size + for img in ./extracted/setupIcon-*.png; do + if [ -f "$img" ]; then + size=$(gm identify -format "%wx%h" "$img") + # Skip smallest icons (16x16 and 32x32) as they're too low quality + if [ "$size" != "16x16" ] && [ "$size" != "32x32" ]; then + mkdir -p "$out/share/icons/hicolor/$size/apps" + cp "$img" "$out/share/icons/hicolor/$size/apps/claude-desktop.png" + fi + fi + done + fi + + runHook postInstall + ''; + + meta = with lib; { + description = "Claude Desktop - AI assistant from Anthropic"; + homepage = "https://claude.ai"; + license = licenses.unfree; + sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ]; + maintainers = with maintainers; [ ]; + platforms = [ "x86_64-linux" ]; + mainProgram = "claude-desktop"; + }; +}