nix: shrink daemon install with @vercel/nft tracing (#966)

* nix: share npmDeps FOD between paseo and paseo-desktop

The daemon and desktop derivations share package-lock.json, so their
npmDeps FODs contain byte-identical content. But Nix names the FOD
store path with the consuming pname prefix, so paseo-<v>-npm-deps and
paseo-desktop-<v>-npm-deps resolve to different store paths despite
identical bytes. Building both runs prefetch-npm-deps twice in
parallel; on a clean store this downloads the entire registry
(~1-2 GB of tarballs) twice over the wire.

Wire paseo through as a callPackage arg of the desktop drv and
inherit (paseo) npmDeps. One FOD, one fetch, one store path.

Override path becomes paseo.override { npmDepsHash = "..."; } — the
desktop drv picks up the overridden npmDeps transitively. Downstream
flakes that previously did paseo-desktop.override { npmDepsHash }
need to either drop the desktop-side override (recommended) or pass
the overridden daemon through as paseo-desktop.override { paseo }.

* nix: trace daemon runtime closure with @vercel/nft

The daemon Nix installPhase used to `cp -a node_modules $out/lib/paseo/`,
shipping every package in the hoisted root — Expo, React Native, Metro,
Electron, ML stacks, ~1700 third-party deps the daemon never `require()`s
at runtime. Result: ~2.6 GB store path for a daemon whose actual runtime
closure is a tiny fraction of that.

Replace it with static module-graph tracing via @vercel/nft (the same
library Vercel and Next.js use for serverless bundling). The new
installPhase runs `node scripts/trace-daemon.mjs`, which:

1. Calls nodeFileTrace on three entry points — cli/dist/index.js,
   server/dist/scripts/supervisor-entrypoint.js, and the forked
   server/dist/server/terminal/terminal-worker-process.js. The worker
   needs to be a separate entry because nft does not follow fork()
   process boundaries.
2. Unions the trace result with an explicit list of non-JS runtime
   inputs nft does not detect: shell-integration assets read via
   readFileSync, the Silero VAD ONNX model loaded by the sherpa
   provider, .env.example, the CLI shebang script, and node-pty's
   compiled prebuilt binary for the host platform.
3. Ignores cross-platform native variants we explicitly do not ship
   (sherpa-onnx-*, @mariozechner/clipboard-*) and node-fetch's
   optional `encoding` peer.

installPhase consumes the file list, copies each entry into
$out/lib/paseo/ preserving its repo-relative path, and wraps two bin
entries (paseo, paseo-server) via makeWrapper.

Verified end-to-end:
- daemon $out drops from ~2.6 GB to ~131 MB (≈95% reduction)
- paseo --version, paseo --help work from the new $out
- paseo-server boots, HTTP server reaches "Server listening" within
  ~40 ms, no missing-module errors in the bootstrap log
- speech features degrade gracefully when sherpa-onnx-* / model files
  are absent (the documented behaviour for the Nix build, unchanged)

@vercel/nft added to root devDependencies (1.5.0); used only at
build time by the Nix derivation.

* fix(nix): gitignore result

* fix(nix): refresh npm deps hash post-rebase

The conflict resolution during rebase kept the trace-daemon hash, but
the merged package-lock.json combines upstream's lockfile updates with
the @vercel/nft addition, so the hash needed to be recomputed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matan Bendix Shenhav
2026-05-15 14:08:06 +02:00
committed by GitHub
parent 5ea68dcdfa
commit e309d82b41
8 changed files with 477 additions and 70 deletions

1
.gitignore vendored
View File

@@ -6,6 +6,7 @@ build/
dist/ dist/
.next/ .next/
out/ out/
result
# Environment variables # Environment variables
.env .env

View File

@@ -36,7 +36,7 @@
paseo = paseo; paseo = paseo;
} }
// nixpkgs.lib.optionalAttrs isLinux { // nixpkgs.lib.optionalAttrs isLinux {
desktop = pkgs.callPackage ./nix/desktop-package.nix { }; desktop = pkgs.callPackage ./nix/desktop-package.nix { inherit paseo; };
} }
); );

View File

@@ -9,10 +9,11 @@
makeDesktopItem, makeDesktopItem,
electron, electron,
libuv, libuv,
# Shares the daemon's npm-deps hash — same package-lock.json, same fetcher. # Reuse the daemon's prebuilt npm-deps FOD. Same lockfile, same content —
# Override via `.override { npmDepsHash = "..."; }` if your nixpkgs computes a # without this, the desktop drv produces a separately-named store path
# different value. # (`paseo-desktop-<v>-npm-deps`) and refetches the entire registry. Override
npmDepsHash ? lib.fileContents ./npm-deps.hash, # the upstream hash via `paseo.override { npmDepsHash = "..."; }`.
paseo,
}: }:
buildNpmPackage rec { buildNpmPackage rec {
@@ -21,7 +22,8 @@ buildNpmPackage rec {
src = lib.cleanSourceWith { src = lib.cleanSourceWith {
src = ./..; src = ./..;
filter = path: type: filter =
path: type:
let let
baseName = builtins.baseNameOf path; baseName = builtins.baseNameOf path;
relPath = lib.removePrefix (toString ./..) path; relPath = lib.removePrefix (toString ./..) path;
@@ -42,7 +44,7 @@ buildNpmPackage rec {
}; };
nodejs = nodejs_22; nodejs = nodejs_22;
inherit npmDepsHash; inherit (paseo) npmDeps;
# Prevent onnxruntime-node's install script from running during automatic # Prevent onnxruntime-node's install script from running during automatic
# npm rebuild. We manually rebuild only node-pty in buildPhase. # npm rebuild. We manually rebuild only node-pty in buildPhase.

View File

@@ -1 +1 @@
sha256-vBudtcS/3V5tbtAug++V9F5BZknsTFdsMyv914gfy9Q= sha256-SRax+Frqf0OhDhfWICSlhOnHBGsoHiXQc6P+zo5VjN8=

View File

@@ -88,47 +88,25 @@ buildNpmPackage rec {
installPhase = '' installPhase = ''
runHook preInstall runHook preInstall
# Compute the daemon's runtime closure by static module-graph tracing
# (@vercel/nft from supervisor-entrypoint.js, cli/dist/index.js, and the
# forked terminal-worker-process.js) plus an explicit list of non-JS
# assets read at runtime. The trace script is the single source of
# truth for what the daemon needs at $out auditable in plain JS, no
# npm hoisting / .bin / workspace-symlink footguns.
mkdir -p $out/lib/paseo mkdir -p $out/lib/paseo
node scripts/trace-daemon.mjs > daemon-files.txt
# Copy root package metadata while IFS= read -r path; do
[ -z "$path" ] && continue
mkdir -p "$out/lib/paseo/$(dirname "$path")"
cp -a "$path" "$out/lib/paseo/$path"
done < daemon-files.txt
# Root package.json lets node resolve the workspace layout when the
# CLI/server bin starts from $out.
cp package.json $out/lib/paseo/ cp package.json $out/lib/paseo/
# Copy node_modules (preserving workspace symlinks)
cp -a node_modules $out/lib/paseo/
# Auto-detect which @getpaseo/* packages were built by build:daemon
# (they'll have a dist/ directory). Copy those and remove the rest.
for link in $out/lib/paseo/node_modules/@getpaseo/*; do
name=$(basename "$link")
if [ -d "packages/$name/dist" ]; then
mkdir -p "$out/lib/paseo/packages/$name"
cp "packages/$name/package.json" "$out/lib/paseo/packages/$name/"
cp -a "packages/$name/dist" "$out/lib/paseo/packages/$name/"
if [ -d "packages/$name/node_modules" ]; then
cp -a "packages/$name/node_modules" "$out/lib/paseo/packages/$name/"
fi
else
rm -f "$link"
fi
done
# Copy CLI bin entry
mkdir -p $out/lib/paseo/packages/cli/bin
cp packages/cli/bin/paseo $out/lib/paseo/packages/cli/bin/
# Copy extra server files referenced at runtime
for f in agent-prompt.md .env.example; do
if [ -f packages/server/$f ]; then
cp packages/server/$f $out/lib/paseo/packages/server/
fi
done
# Copy server scripts (including supervisor-entrypoint) needed by CLI
if [ -d packages/server/dist/scripts ]; then
mkdir -p $out/lib/paseo/packages/server/dist/scripts
cp -a packages/server/dist/scripts/* $out/lib/paseo/packages/server/dist/scripts/
fi
# Create wrapper for the server entry point (for systemd / direct use) # Create wrapper for the server entry point (for systemd / direct use)
mkdir -p $out/bin mkdir -p $out/bin
makeWrapper ${nodejs}/bin/node $out/bin/paseo-server \ makeWrapper ${nodejs}/bin/node $out/bin/paseo-server \

376
package-lock.json generated
View File

@@ -22,6 +22,7 @@
"devDependencies": { "devDependencies": {
"@types/ws": "^8.5.14", "@types/ws": "^8.5.14",
"@typescript/native-preview": "7.0.0-dev.20260423.1", "@typescript/native-preview": "7.0.0-dev.20260423.1",
"@vercel/nft": "^1.5.0",
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"get-port-cli": "^3.0.0", "get-port-cli": "^3.0.0",
@@ -8583,6 +8584,102 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/@mapbox/node-pre-gyp": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz",
"integrity": "sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"consola": "^3.2.3",
"detect-libc": "^2.0.0",
"https-proxy-agent": "^7.0.5",
"node-fetch": "^2.6.7",
"nopt": "^8.0.0",
"semver": "^7.5.3",
"tar": "^7.4.0"
},
"bin": {
"node-pre-gyp": "bin/node-pre-gyp"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/chownr": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/minizlib": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"minipass": "^7.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/tar": {
"version": "7.5.15",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz",
"integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.1.0",
"yallist": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/@mariozechner/clipboard": { "node_modules/@mariozechner/clipboard": {
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.3.tgz", "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.3.tgz",
@@ -11561,6 +11658,36 @@
"integrity": "sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w==", "integrity": "sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@rollup/pluginutils": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
"integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/pluginutils/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.59.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
@@ -11572,7 +11699,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.59.0", "version": "4.59.0",
@@ -11585,7 +11713,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.59.0", "version": "4.59.0",
@@ -11598,7 +11727,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.59.0", "version": "4.59.0",
@@ -11611,7 +11741,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-freebsd-arm64": { "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.59.0", "version": "4.59.0",
@@ -11624,7 +11755,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-freebsd-x64": { "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.59.0", "version": "4.59.0",
@@ -11637,7 +11769,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.59.0", "version": "4.59.0",
@@ -11650,7 +11783,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.59.0", "version": "4.59.0",
@@ -11663,7 +11797,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.59.0", "version": "4.59.0",
@@ -11676,7 +11811,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.59.0", "version": "4.59.0",
@@ -11689,7 +11825,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-linux-loong64-gnu": { "node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.59.0", "version": "4.59.0",
@@ -11702,7 +11839,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-linux-loong64-musl": { "node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.59.0", "version": "4.59.0",
@@ -11715,7 +11853,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-linux-ppc64-gnu": { "node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.59.0", "version": "4.59.0",
@@ -11728,7 +11867,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-linux-ppc64-musl": { "node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.59.0", "version": "4.59.0",
@@ -11741,7 +11881,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.59.0", "version": "4.59.0",
@@ -11754,7 +11895,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-linux-riscv64-musl": { "node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.59.0", "version": "4.59.0",
@@ -11767,7 +11909,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.59.0", "version": "4.59.0",
@@ -11780,7 +11923,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.59.0", "version": "4.59.0",
@@ -11793,7 +11937,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.59.0", "version": "4.59.0",
@@ -11806,7 +11951,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-openbsd-x64": { "node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.59.0", "version": "4.59.0",
@@ -11819,7 +11965,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"openbsd" "openbsd"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-openharmony-arm64": { "node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.59.0", "version": "4.59.0",
@@ -11832,7 +11979,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"openharmony" "openharmony"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.59.0", "version": "4.59.0",
@@ -11845,7 +11993,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.59.0", "version": "4.59.0",
@@ -11858,7 +12007,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-win32-x64-gnu": { "node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.59.0", "version": "4.59.0",
@@ -11871,7 +12021,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
] ],
"peer": true
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.59.0", "version": "4.59.0",
@@ -11884,7 +12035,8 @@
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
] ],
"peer": true
}, },
"node_modules/@rtsao/scc": { "node_modules/@rtsao/scc": {
"version": "1.1.0", "version": "1.1.0",
@@ -15288,6 +15440,124 @@
"wonka": "^6.3.2" "wonka": "^6.3.2"
} }
}, },
"node_modules/@vercel/nft": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.5.0.tgz",
"integrity": "sha512-IWTDeIoWhQ7ZtRO/JRKH+jhmeQvZYhtGPmzw/QGDY+wDCQqfm25P9yIdoAFagu4fWsK4IwZXDFIjrmp5rRm/sA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@mapbox/node-pre-gyp": "^2.0.0",
"@rollup/pluginutils": "^5.1.3",
"acorn": "^8.6.0",
"acorn-import-attributes": "^1.9.5",
"async-sema": "^3.1.1",
"bindings": "^1.4.0",
"estree-walker": "2.0.2",
"glob": "^13.0.0",
"graceful-fs": "^4.2.9",
"node-gyp-build": "^4.2.2",
"picomatch": "^4.0.2",
"resolve-from": "^5.0.0"
},
"bin": {
"nft": "out/cli.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@vercel/nft/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/@vercel/nft/node_modules/brace-expansion": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/@vercel/nft/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true,
"license": "MIT"
},
"node_modules/@vercel/nft/node_modules/glob": {
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"minimatch": "^10.2.2",
"minipass": "^7.1.3",
"path-scurry": "^2.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@vercel/nft/node_modules/lru-cache": {
"version": "11.3.6",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz",
"integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@vercel/nft/node_modules/minimatch": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.5"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@vercel/nft/node_modules/path-scurry": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@vercel/oidc": { "node_modules/@vercel/oidc": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.3.tgz",
@@ -15665,6 +15935,16 @@
"acorn-walk": "^8.0.2" "acorn-walk": "^8.0.2"
} }
}, },
"node_modules/acorn-import-attributes": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
"integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"acorn": "^8"
}
},
"node_modules/acorn-jsx": { "node_modules/acorn-jsx": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -16473,6 +16753,13 @@
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/async-sema": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz",
"integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==",
"dev": true,
"license": "MIT"
},
"node_modules/asynckit": { "node_modules/asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -16945,6 +17232,16 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"file-uri-to-path": "1.0.0"
}
},
"node_modules/blake3-wasm": { "node_modules/blake3-wasm": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz",
@@ -18252,6 +18549,16 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/consola": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^14.18.0 || >=16.10.0"
}
},
"node_modules/content-disposition": { "node_modules/content-disposition": {
"version": "0.5.4", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -23955,6 +24262,13 @@
"url": "https://github.com/sindresorhus/file-type?sponsor=1" "url": "https://github.com/sindresorhus/file-type?sponsor=1"
} }
}, },
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true,
"license": "MIT"
},
"node_modules/filelist": { "node_modules/filelist": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
@@ -30824,6 +31138,18 @@
"node": "^18.17.0 || >=20.5.0" "node": "^18.17.0 || >=20.5.0"
} }
}, },
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"dev": true,
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/node-gyp/node_modules/chownr": { "node_modules/node-gyp/node_modules/chownr": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",

View File

@@ -93,6 +93,7 @@
"devDependencies": { "devDependencies": {
"@types/ws": "^8.5.14", "@types/ws": "^8.5.14",
"@typescript/native-preview": "7.0.0-dev.20260423.1", "@typescript/native-preview": "7.0.0-dev.20260423.1",
"@vercel/nft": "^1.5.0",
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"get-port-cli": "^3.0.0", "get-port-cli": "^3.0.0",

99
scripts/trace-daemon.mjs Normal file
View File

@@ -0,0 +1,99 @@
#!/usr/bin/env node
// Emit the set of files the daemon and CLI need at runtime, computed by
// static module-graph tracing (@vercel/nft) from the three entry points.
// Used by nix/package.nix's installPhase to materialize $out/lib/paseo
// with only the bytes the daemon actually loads — no Expo, RN, Metro,
// Electron, ML stacks, or other non-daemon workspace bloat.
//
// Output: newline-separated repo-relative file paths on stdout. The Nix
// installPhase copies each path to $out/lib/paseo/<path>, preserving the
// directory structure node's module resolution expects.
//
// Run from the repo root, after `npm run build:daemon`. Requires
// node_modules populated (the Nix build invokes this post-configHook).
import { nodeFileTrace } from "@vercel/nft";
import { glob } from "node:fs/promises";
import path from "node:path";
const REPO_ROOT = path.resolve(import.meta.dirname, "..");
// Three entry points. The terminal worker is forked into its own Node
// process and has its own require tree (node-pty, etc.) — nft does not
// follow fork boundaries, so it must be traced separately.
const entries = [
"packages/cli/dist/index.js",
"packages/server/dist/scripts/supervisor-entrypoint.js",
"packages/server/dist/server/terminal/terminal-worker-process.js",
];
// Files read at runtime via fs APIs rather than `require`. nft only
// traces the module graph; data files have to be listed explicitly.
const additionalInputs = [
// Shell integration scripts loaded by the terminal manager
"packages/server/dist/server/terminal/shell-integration/**",
// Silero VAD ONNX model (sherpa speech provider)
"packages/server/dist/server/server/speech/providers/local/sherpa/assets/silero_vad.onnx",
// Server runtime config files (read by path, not require)
"packages/server/.env.example",
// CLI shebang script wrapping dist/index.js
"packages/cli/bin/paseo",
// node-pty's compiled native addon. nft can't trace it because
// node-pty loads it via `require(path.join(__dirname, 'prebuilds/<plat>/pty.node'))`
// with a runtime-computed platform suffix. Pin to the host platform —
// the Nix derivation builds for one platform at a time and ships only
// its own binaries.
`node_modules/node-pty/prebuilds/${process.platform}-${process.arch}/**`,
];
// Trace.
const { fileList, warnings } = await nodeFileTrace(entries, {
base: REPO_ROOT,
// Tolerate the conditional / dynamic patterns we already audited:
// sherpa-onnx-${platform}-${arch} package resolution (sherpa is
// intentionally not built in the Nix sandbox; voice features degrade
// gracefully when unavailable), and a handful of test-only requires
// that get tree-shaken out by tsc.
ignore: [
// Cross-platform native packages for the sherpa speech runtime;
// unsupported in the Nix build, lazily loaded when present.
"sherpa-onnx-*/**",
// Platform-specific clipboard variants; only the host's variant
// is needed at runtime, and the package's index.js resolver picks
// the right one dynamically.
"@mariozechner/clipboard-*/**",
// node-fetch optional peer for non-UTF-8 charset decoding; not
// loaded in our usage.
"encoding/**",
// Tests are stripped during the daemon build; nft sometimes still
// tries to walk into them via index files. Belt and suspenders.
"**/*.test.js",
"**/*.e2e.test.js",
],
});
// Surface non-trivial trace warnings so the Nix build log captures them.
for (const w of warnings) {
// Drop the "Failed to resolve dependency" noise for things we
// explicitly ignore above.
const msg = w.message ?? String(w);
if (/sherpa-onnx-/.test(msg)) continue;
console.error("trace warning:", msg);
}
// Expand globs in additionalInputs.
const expanded = new Set(fileList);
for (const pattern of additionalInputs) {
if (pattern.includes("*")) {
for await (const file of glob(pattern, { cwd: REPO_ROOT })) {
expanded.add(file);
}
} else {
expanded.add(pattern);
}
}
// Emit sorted, deduplicated.
for (const p of [...expanded].sort()) {
console.log(p);
}