Instalar el CLI

Una sola línea, detecta OS + arquitectura, baja el binario correspondiente desde GitHub Releases y crea los enlaces multicall stilr y stilx:

$ curl -fsSL https://stil.dev/install.sh | bash

Variables de entorno reconocidas por el instalador:

  • STIL_VERSION — fija una etiqueta concreta (default: latest).
  • STIL_INSTALL_DIR — dónde dejar el binario (default: ~/.local/bin).
  • STIL_RELEASE_BASE_URL — base alternativa para el download.

stil install

Lee el package.json del directorio actual y resuelve + descarga + linkea todas sus dependencias.

$ stil install [--no-dev] [--frozen-lockfile] [--min-age D]
              [--allow-scripts] [--jobs N] [--solver greedy|pubgrub]
  • --no-dev / --production / -P — omite devDependencies.
  • --frozen-lockfile — instala exactamente lo del lockfile, sin BFS. Modo CI.
  • --min-age D — días mínimos desde la publicación (default: 7). 0 para desactivar.
  • --allow-scripts — opt-in a ejecutar lifecycle scripts dentro del sandbox Hull.
  • --jobs N — workers paralelos para el BFS (default: min(8, cpu_count)).
  • --solver pubgrub — usa el resolver CDCL en lugar del BFS greedy. Experimental.

Si el cwd vive dentro de un monorepo (un ancestro tiene "workspaces" en su package.json), stil hace chdir al root automáticamente y reporta:

stil: in workspace 'utils', running install from monorepo root '/path/to/myrepo'

stil add

Añade un paquete al package.json apropiado y corre install. Si el spec viene como tag (latest, next) o versión exacta, stil resuelve a versión concreta y registra ^X.Y.Z.

$ stil add <pkg>[@spec] [-D|-O|--peer] [-w <ws>]
  • -D / --save-dev — devDependencies.
  • -O / --save-optional — optionalDependencies.
  • --peer — peerDependencies.
  • -w <ws> / --workspace <ws> — escribe al package.json del workspace nombrado en lugar del root.

stil remove

Quita una dependencia del package.json y reinstala.

$ stil remove <pkg>
$ stil rm <pkg>                        # alias

stil update

Re-resuelve dependencias top-level a la última versión que cumpla constraint + política de edad. Las constraints en package.json NO se modifican (no hay auto-bump de ^X). Imprime un diff nombre: oldVer → newVer.

$ stil update                          # todas las top-level
$ stil update <pkg>                    # sólo una
$ stil up <pkg>                        # alias
$ stil upgrade <pkg>                   # alias

stil run

Ejecuta un script de package.json con node_modules/.bin (local + root del monorepo) prependido al PATH. Sin argumentos lista los scripts disponibles.

$ stil run                              # lista scripts
$ stil run dev                          # corre scripts.dev
$ stil run build -- --mode=production  # reenvía args después de --
$ stil run dev --filter '@my/*'        # corre en cada workspace que matchee
$ stilr dev                             # `stilr` es el multicall de `stil run`

El glob de --filter soporta * y ?. En modo filter, los workspaces sin el script se omiten silenciosamente.

stil why

Muestra el árbol inverso de dependencias para un paquete: quién lo trajo y por qué.

$ stil why react

stil audit

Camina el lockfile y verifica integridad sha512 + edad de cada paquete.

$ stil audit

stil outdated

Compara las versiones instaladas vs la última publicada del registry.

$ stil outdated

stil store

Gestión del content-addressed store local.

$ stil store path                       # imprime el path actual
$ stil store stats                      # tamaño + cantidad de entries
$ stil store prune                      # borra entries no referenciadas

Workspaces

El root del monorepo declara workspaces como un array de globs en package.json:

{
  "name": "myrepo",
  "private": true,
  "workspaces": ["packages/*", "apps/*", "tools/build"]
}

Patrones soportados (v0.2): pkg/* (subdirectorios inmediatos con package.json) y rutas exactas. ** y otros globs recursivos no.

También se acepta el formato objeto que usa Yarn classic:

{
  "workspaces": { "packages": ["packages/*"], "nohoist": ["..."] }
}

Protocolo workspace:

Un dep entre workspaces se declara con el prefijo workspace::

{
  "dependencies": {
    "@myrepo/utils": "workspace:*",
    "@myrepo/ui":    "workspace:^",
    "@myrepo/icons": "workspace:~"
  }
}

stil resuelve estos a un symlink relativo en node_modules/<name> apuntando al directorio del workspace. Sin round-trip al registry, sin entry duplicado en el store.

Hoist + nest

Las deps regulares se hoistean al node_modules del root cuando no hay conflicto de versiones; cuando lo hay, la primera versión gana el top-level y las restantes se anidan bajo <workspace>/node_modules/. Es el comportamiento estándar tipo npm.

Sandbox & scripts

Por defecto stil NO ejecuta ningún preinstall, install, postinstall o prepare. El reporte final indica cuántos se omitieron:

  18 lifecycle scripts skipped (pass --allow-scripts to run inside Hull)

Con --allow-scripts, los scripts se ejecutan dentro de una microVM Hull con un perfil seccomp node:

  • 32 syscalls permitidas (las que un build legítimo necesita).
  • Sin connect() a red.
  • Sin acceso a ~/.ssh, ~/.aws, ~/.config.
  • Cualquier syscall fuera del perfil dispara SECCOMP_RET_KILL_PROCESS.

Para un control más fino por paquete, ver stil policy <pkg> allow|deny.

Política de edad

Por defecto, stil bloquea cualquier versión publicada hace menos de 7 días. La idea es defensa contra ataques de supply chain donde el atacante publica una versión maliciosa y los advisories aún no la marcaron.

El resolver hace fallback automático: si la última versión que satisface el constraint del package.json no cumple la edad, stil escoge la siguiente más nueva que sí. Ejemplo: si "react": "^19.2.4" apunta a 19.2.6 (3 días) y a 19.2.5 (10 días), stil instala 19.2.5.

Cambiá el umbral con --min-age D. --min-age 0 desactiva la política (útil en CI day-zero).

Lockfile v2

El archivo stil-lock.json tiene este shape (v2):

{
  "lockfileVersion": 2,
  "name": "myrepo",
  "workspaces": {                    // vacío en proyectos planos
    "packages/utils": { "name": "@my/utils", "version": "1.0.0" },
    "packages/ui":    { "name": "@my/ui",    "version": "0.4.0" }
  },
  "packages": {
    "@my/utils@1.0.0": {            // entry de workspace
      "workspace": "packages/utils"
    },
    "react@18.3.1": {                // entry del registry
      "resolved": "https://...",
      "integrity": "sha512-...",
      "published_at": 1713873600,
      "hasScripts": false
    }
  }
}

Lockfiles v1 (sin workspaces, sin campo workspace por entry) siguen cargando — el migrador los trata como flat install.

Variables de entorno

  • STIL_STORE — path del content store (default: ~/.stil-store). En macOS, ponelo en el mismo volumen APFS que tus proyectos para que clonefile(2) funcione.
  • STIL_REGISTRY — base URL del registry (default: https://registry.npmjs.org). También se puede pasar con --registry.
  • STIL_CACHE — directorio para la caché de packuments (default: ~/.stil-cache).

Troubleshooting

"AgeBlocked" detiene mi install

Eso significa que todas las versiones que satisfacen tu constraint son demasiado nuevas. Opciones: bajar el umbral con --min-age 3, desactivar con --min-age 0, o esperar unos días.

Vite/Next no encuentra binarios nativos (@rollup/rollup-darwin-arm64)

Asegurate de estar en stil v0.2.0+. Las versiones anteriores no procesaban optionalDependencies con filtros os/cpu.

Install lento en macOS y los hardlinks no aparecen

Probable cross-volume: tu proyecto está en /Volumes/X pero el store en /. clonefile(2) y los hardlinks no cruzan APFS volumes — apuntá STIL_STORE al mismo volumen.

El binario instalado se mata con exit 137 (macOS)

El xattr com.apple.provenance de macOS 26 puede matar binarios copiados con cp. Workaround: cat orig > dest && chmod +x dest. Esto recrea el archivo y limpia el atributo.