feat: add node-pty Nix derivation for terminal support

Build node-pty from source using buildNpmPackage so Claude Desktop's
terminal features (Claude Code) work on NixOS. The derivation handles
three issues with the upstream package:

- Strips macOS-only fsevents reference from package-lock.json so
  npm ci doesn't fail on lock/json mismatch
- Runs both tsc (TypeScript to JS) and node-gyp rebuild (native addon)
- Copies build/Release/pty.node into output since npmInstallHook
  doesn't include native addons

Also wires node-pty into claude-desktop.nix via --node-pty-dir flag
and updates the flake overlay to expose it.

Co-Authored-By: Claude <claude@anthropic.com>
This commit is contained in:
Alexis Williams
2026-02-26 19:39:15 -08:00
parent 55f7b27ac3
commit caa58cae8d
4 changed files with 67 additions and 7 deletions

View File

@@ -17,6 +17,7 @@ cleanup_action='yes'
perform_cleanup=false
test_flags_mode=false
local_exe_path=''
node_pty_dir=''
original_user=''
original_home=''
project_root=''
@@ -192,7 +193,7 @@ parse_arguments() {
while (( $# > 0 )); do
case "$1" in
-b|--build|-c|--clean|-e|--exe|-r|--release-tag|-s|--source-dir)
-b|--build|-c|--clean|-e|--exe|-r|--release-tag|-s|--source-dir|--node-pty-dir)
if [[ -z ${2:-} || $2 == -* ]]; then
echo "Error: Argument for $1 is missing" >&2
exit 1
@@ -203,6 +204,7 @@ parse_arguments() {
-e|--exe) local_exe_path="$2" ;;
-r|--release-tag) release_tag="$2" ;;
-s|--source-dir) source_dir="$2" ;;
--node-pty-dir) node_pty_dir="$2" ;;
esac
shift 2
;;
@@ -217,6 +219,7 @@ parse_arguments() {
echo ' --clean: Specify whether to clean intermediate build files (yes or no). Default: yes'
echo ' --exe: Use a local Claude installer exe instead of downloading'
echo ' --source-dir: Path to repo root for scripts/ and assets (default: project root)'
echo ' --node-pty-dir: Path to pre-built node-pty package (skips npm install)'
echo ' --release-tag: Release tag (e.g., v1.3.2+claude1.1.799) to append wrapper version to package'
echo ' --test-flags: Parse flags, print results, and exit without building.'
exit 0

View File

@@ -11,7 +11,10 @@
systems = [ "x86_64-linux" "aarch64-linux" ];
perSystem = { pkgs, ... }: let
claude-desktop = pkgs.callPackage ./nix/claude-desktop.nix { };
node-pty = pkgs.callPackage ./nix/node-pty.nix { };
claude-desktop = pkgs.callPackage ./nix/claude-desktop.nix {
inherit node-pty;
};
in {
packages = {
inherit claude-desktop;
@@ -23,8 +26,12 @@
};
flake = {
overlays.default = final: prev: {
claude-desktop = final.callPackage ./nix/claude-desktop.nix { };
overlays.default = final: prev: let
node-pty = final.callPackage ./nix/node-pty.nix { };
in {
claude-desktop = final.callPackage ./nix/claude-desktop.nix {
inherit node-pty;
};
claude-desktop-fhs = final.callPackage ./nix/fhs.nix {
claude-desktop = final.claude-desktop;
};

View File

@@ -13,6 +13,7 @@
python3,
bash,
getent,
node-pty,
}:
let
pname = "claude-desktop";
@@ -78,6 +79,7 @@ stdenvNoCC.mkDerivation {
bash ${sourceRoot}/build.sh \
--exe "$(pwd)/Claude-Setup.exe" \
--source-dir "${sourceRoot}" \
--node-pty-dir "${node-pty}/lib/node_modules/node-pty" \
--build nix \
--clean no
@@ -92,9 +94,6 @@ stdenvNoCC.mkDerivation {
cp build/electron-app/app.asar $out/lib/claude-desktop/resources/
cp -r build/electron-app/app.asar.unpacked $out/lib/claude-desktop/resources/
# TODO: node-pty is not in nixpkgs; terminal features (Claude Code)
# require it. A future improvement could build it with buildNpmPackage.
# Install icons
for size in 16 24 32 48 64 256; do
icon_dir=$out/share/icons/hicolor/"$size"x"$size"/apps

51
nix/node-pty.nix Normal file
View File

@@ -0,0 +1,51 @@
{
lib,
buildNpmPackage,
fetchFromGitHub,
python3,
node-gyp,
}:
buildNpmPackage rec {
pname = "node-pty";
version = "1.1.0";
src = fetchFromGitHub {
owner = "microsoft";
repo = "node-pty";
rev = "v${version}";
hash = "sha256-R0QxTw3tNJvW4aEi+GOF0iZhGgI42HTYJih90CdF18I=";
};
npmDepsHash = "sha256-HRv/4NO7CHkPs7ld8lx61n2cty0EhmWVrpH/1Vqh+Nk=";
# node-gyp needs python3 for native compilation
nativeBuildInputs = [ python3 node-gyp ];
# chokidar (dev dep) has an optional dep on fsevents (macOS-only).
# The Nix npm deps fetcher excludes it, so npm ci sees a lock/json
# mismatch. Strip the reference from the lock file to fix the sync.
postPatch = ''
sed -i '/"fsevents"/d' package-lock.json
'';
# Default npmBuildHook only runs "npm run build" (tsc), but we also
# need the native addon. Run both explicitly.
buildPhase = ''
runHook preBuild
npm run build
node-gyp rebuild
runHook postBuild
'';
# npmInstallHook doesn't copy the native addon — do it ourselves
postInstall = ''
cp -r build $out/lib/node_modules/node-pty/
'';
meta = with lib; {
description = "Fork pseudoterminals in Node.JS";
homepage = "https://github.com/microsoft/node-pty";
license = licenses.mit;
platforms = platforms.linux;
};
}