npm install
un gestor de paquetes Node.js
que no te entregará un RAT.
npm, pnpm y bun ejecutan los lifecycle scripts con acceso completo al host.
Un postinstall malicioso y se llevaron tu ~/.ssh.
stil aísla cada script dentro de una microVM Hull, bloquea versiones
recién publicadas, consulta el advisory bulk de npm en paralelo
al install, y aún así corre ~9× más rápido que bun en un
Next.js fresh.
// Benchmarks
Instalación en caliente — lockfile congelado, store con caché
197 paquetes · React 18 + Vite 6 + framer-motion + jspdf · Mac M-series
stil es ~12× más rápido que bun y ~62× más rápido que npm.
Instalación en caliente — Next.js 16 fresh, sin lockfile
352 paquetes · Next 16.2 + React 19 + Tailwind 4 + TypeScript · resolución BFS + audit DB · Mac M-series
stil ~9× más rápido que bun y ~21× más rápido que npm — y de paso reporta el advisory GHSA-qx2v-qp2m-jg93 que viene en una de las deps transitivas.
TanStack Router fresh — 222 paquetes, sin lockfile
React 19 + Vite 8 + TanStack Router · audit DB paralelo activo
stil ~8× más rápido que bun — overhead del audit ~12 ms gracias a la paralelización con la fase de link.
Monorepo — 3 workspaces, lockfile v2 congelado
apps/web depende de packages/ui y packages/utils vía workspace:*
los workspaces se resuelven a symlinks sin tocar el registry; el resto sale del store con clonefile(2).
// Esta misma página corre en stil
Esta landing está construida con Vite + TypeScript en una carpeta llamada
landing/. Para servirla durante desarrollo y compilarla para
producción usamos stil mismo — es nuestro mejor test bed.
$ cd landing/ $ stil install # resuelve vite + transitive + binarios nativos de rollup 186 packages planned $ stil run dev # vite arranca en milisegundos > dev > vite VITE v6.4.2 ready in 117 ms $ stil run build # tsc + vite build, dist/ listo para deploy ✓ built in 1.6s
// Caso real — migración npm → stil
Una app Next.js 15 en producción (reservas con seatsio, ~515 paquetes).
Un único stil install migra el proyecto y dispara el audit DB
integrado, que destapa 44 advisories que el equipo no conocía — incluido
un RCE crítico en next antes del próximo deploy.
$ rm -rf node_modules $ stil install # lee package.json, ignora package-lock.json 515 packages installed in 1.30s # warm, audit en paralelo con link --- security advisories (44) --- [critical] next@15.3.2: RCE in React flight protocol [high] axios@1.11.0: prototype pollution + header injection + DoS (×6) [high] xlsx@0.18.5: prototype pollution + ReDoS (×2) [high] next@15.3.2: DoS + middleware bypass + SSRF (×8) summary: 1 critical, 17 high, 22 moderate, 4 low
Tres bumps guiados por lo que mostró el audit y los críticos desaparecen:
$ stil add 'next@^15' # 15.3.2 → 15.5.18, parcha el RCE $ stil add 'axios@^1.12' # 1.11.0 → 1.16.0, parcha 6 high $ stil add 'seatsio@^87' # 85 → 87, sube axios nested a 1.15 $ stil install summary: 0 critical, 6 high, 9 moderate, 1 low # −64% advisories $ stil run build ▲ Next.js 15.5.18 ✓ Compiled successfully in 4.7s # 43 páginas, sin breaking changes
De 44 advisories detectados a 16 en una sola sesión —
−64%, incluyendo el RCE crítico, sin tocar el código de la app.
Las 6 high restantes son irreducibles sin acción de upstream
(xlsx sin parche en npm, axios nested vía seatsio).
// Por qué stil existe
Lifecycle scripts aislados
Los scripts corren dentro de una microVM Hull con seccomp KILL_PROCESS sobre el perfil node (32 syscalls). Sin red, sin ~/.ssh, sin ~/.aws. Cualquier intento de escape mata el proceso. Por defecto se omiten directamente.
Política de edad de 7 días
Las versiones recién publicadas se bloquean por defecto — los mantenedores y los advisories necesitan tiempo para detectar un compromiso. El resolver hace fallback automático a la última versión que cumple el constraint y la política de edad. Incluso si tu package.json pinea "latest", stil retrocede a la última estable que pasa.
Advisory bulk en cada install
Tras resolver el plan, stil POST-ea la lista (nombre, versión) al bulk endpoint de npm. Reporta cualquier advisory con severidad y URL — y --audit-level=high aborta el install con exit 1 si hay CVE crítico. El POST corre en paralelo con la fase de link, así que el overhead final es ~12 ms.
Store con verificación de integridad
sha512 / sha256 / sha1 verificados antes de que el tarball toque el disco. Almacenado una vez, hardlinkeado en cada proyecto — clonefile(2) en macOS, ~200 syscalls en lugar de los ~10k típicos.
Fetch HTTP/2 multiplexado + cache con ETag
Handle Multi de libcurl, 8 conexiones paralelas a npmjs, decenas de streams por conexión. Caché persistente de packuments en ~/.stil-cache/ con TTL de 24h y revalidación If-None-Match — entradas viejas se confirman con un 304 casi sin payload en lugar de re-descargarse.
Drop-in para Vite / Next / Astro
Resuelve optionalDependencies con filtros de os / cpu: los binarios nativos de plataforma (@rollup/rollup-darwin-arm64, esbuild, etc.) caen en disco y vite build funciona sin tocar nada.
Zig, no JavaScript
Un único binario estático de ~1 MB (~10 MB en Linux). Sin runtime de Node.js, sin bootstrap de npm, sin overhead de arranque. Boot en milisegundos, no medio segundo como tarda npm en cargarse.
Deploy en un comando
stil mentat init autodetecta el stack (Next.js, Vite, Java, Node) y genera el up.yaml de Mentat listo para mt up. Lee los nombres de tus env vars de .env y los deja como <SET_ME> — nunca copia secrets al manifiesto.
// Monorepos & workspaces
Workspaces nativos al estilo npm/yarn. Cada paquete consume al otro vía
workspace:* y stil resuelve la dependencia a un symlink local
— sin round-trip al registry, sin entry duplicado en el store.
# package.json del monorepo { "name": "myrepo", "private": true, "workspaces": ["packages/*", "apps/*"] } # packages/ui/package.json { "name": "@myrepo/ui", "dependencies": { "@myrepo/utils": "workspace:*", "react": "^18" } } $ stil install # crea symlinks + hardlinks monorepo: 3 workspaces discovered 4 packages planned $ cd packages/utils && stil install # desde un sub-workspace, encuentra el root solo stil: in workspace 'utils', running install from monorepo root '/path/to/myrepo' $ stil run dev --filter '@myrepo/*' # corre dev en cada workspace que matchee [apps/web] > dev > vite
// Instalar
macOS — Apple Silicon o Intel
$ curl -fsSL https://stil.dev/install.sh | bash # o desde source: $ scripts/build.sh # Zig 0.15.2 en ~/.local/zig15 $ cp zig-out/bin/stil ~/.local/bin/stil $ ln -sf stil ~/.local/bin/stilr # `stilr dev` ≡ `stil run dev`
Linux — x86_64-musl (ELF estático, ~10 MB)
$ curl -fsSL https://stil.dev/install.sh | bash # o desde source: $ TARGET=x86_64-linux-musl scripts/build.sh # v0.2 ships con backend HTTP de Zig stdlib. # Path libcurl vendoreado (paridad macOS) viene en v0.2.1.
Uso diario
$ stil install # respeta política de 7 días $ stil install --frozen-lockfile # CI: salta el BFS, instala lo del lockfile $ stil add react@^18 -D # devDependency $ stil add chalk -w '@my/utils' # a un workspace específico $ stil update # sube todo a la última válida $ stil run dev # scripts.dev del package.json $ stil run dev --filter '@my/*' # corre en cada workspace $ stil mentat init # genera up.yaml de deploy según el stack
Tip multi-volumen (macOS)
# clonefile(2) y los hardlinks no cruzan volúmenes APFS. # Si tus proyectos viven en /Volumes/Foo, apuntá el store ahí también: $ export STIL_STORE=/Volumes/Foo/.stil-store
// vs otros gestores
| Característica | npm | pnpm | bun | stil |
|---|---|---|---|---|
| Lifecycle scripts aislados | ✗ | ✗ | ✗ | ✓ Hull microVM |
| Política de edad por defecto | ✗ | ✗ | ✗ | ✓ 7 días |
| Advisory DB en cada install | ~ npm audit (aparte) | ✗ | ✗ | ✓ paralelo |
| Store content-addressed | ✗ | ✓ | ✓ | ✓ |
| Fetch HTTP/2 multiplexado | ✗ | ~ | ✓ | ✓ |
| Workspaces / monorepos | ✓ | ✓ | ✓ | ✓ |
| Registries privados / auth | ✓ | ✓ | ✓ | v0.3 |
| Instalación en caliente (197 pkg, frozen) | 13.0 s | ~3 s | 2.43 s | 0.21 s |
| Next.js 16 fresh (352 pkg, sin lockfile) | 14.5 s | — | 6.20 s | 0.68 s |