npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

shilo-cli

v0.7.0

Published

CLI para sincronizar carpetas locales con repositorios de Shilo: push, pull, status, log y diff.

Readme

shilo-cli

CLI para subir carpetas locales a repositorios de Shilo, con una experiencia tipo git: shilo login una vez y después shilo push.

Estado: Fase 5 (colaboración asíncrona). Disponibles login, whoami, logout, init, push, pull, status, log, diff. Ciclo completo: shilo statusshilo pull → trabajar local → shilo push.

Requisitos

  • Node.js >= 18 (usa fetch y crypto nativos; sin dependencias de runtime).

Instalación (local, durante desarrollo)

cd cli
npm link          # expone el comando `shilo` global
# o, sin link:
node bin/shilo.js <comando>

Uso

shilo login                 # pregunta email y contraseña (oculta)
shilo login --email [email protected]
shilo whoami                # muestra la sesión activa
shilo logout                # borra las credenciales locales

Subir una carpeta (flujo tipo git)

cd mi-carpeta
shilo init --project <uuid>       # ata la carpeta al repo (escribe .shilo.json)
shilo push                        # sube: crea/actualiza + commit
shilo push -m "mi mensaje"        # con mensaje de commit
shilo push --dry-run              # muestra qué haría sin subir
shilo push --card ABC-123         # agrega "[card: ABC-123]" al mensaje del commit
shilo push --yes                  # sin pedir confirmación
shilo push --verbose              # lista archivo por archivo en vez de resumir
shilo push --concurrency 4        # requests en paralelo (1-32, default 8)
shilo push ./otra-carpeta --project <uuid>   # sin init, proyecto explícito

Sólo se suben archivos de texto; binarios, oversize (>25 MB) y nombres con caracteres no permitidos se saltan con aviso. Los archivos sin cambios no se re-suben.

Los nombres aceptan espacios, acentos y Unicode (Notas Metrónomo.txt, 日本語.md): sólo se rechaza lo que rompe una ruta o no es portable — / \ : * ? " < > |, bytes de control, ./.., nombres de dispositivo de Windows (CON, NUL, COM1…) y el punto final. Los nombres se normalizan a NFC, igual que el backend, para que el mismo archivo subido desde macOS y desde Windows sea uno solo.

Guardar es proponer: el push manda todo lo que cambió como una propuesta que parte de la versión que esta carpeta vio por última vez (base en .shilo.json, que pull y push van anotando). El servidor la aplica solo si combina limpio con la versión actual. Si otra persona cambió alguno de los mismos archivos desde esa base, la propuesta queda abierta y no se pisa nada: el CLI dice qué archivos son, tus cambios quedan guardados en esa propuesta para que el dueño decida, y vos podés traer la versión actual con shilo pull, ajustar y volver a push. Un push nuevo reemplaza tus propuestas abiertas anteriores.

El texto viaja inline (hasta 500 archivos o 4 MB por propuesta; más que eso son varias propuestas de la misma base); los binarios y los textos de más de 1 MB se suben por URL firmada y entran por su identificador de contenido. Contra un servidor anterior a las propuestas, el CLI avisa y cae al camino viejo (escribir + commit), que sí puede pisar.

Qué se ignora

push respeta .gitignore y .shiloignore, incluidos los de subcarpetas (una regla de src/.gitignore sólo alcanza a src/). .shiloignore se lee último, así que podés re-incluir con ! algo que git ignora. Por defecto ya se saltean .git/, node_modules/, .shilo/, .vercel/, .DS_Store y Thumbs.db.

También se poda cualquier carpeta que tenga forma de repo git (HEAD + objects/

  • refs/), aunque no se llame .git: un .git-ORIGINAL-BACKUP/ trae todo el historial, incluidos secretos de commits ya borrados del árbol de trabajo.

Limitación conocida: la negación ! se evalúa por regla, no por directorio. Como en git, ! no puede re-incluir un archivo si su carpeta padre ya está excluida (dist/ + !dist/keep.txt no funciona); excluí el contenido en vez de la carpeta (dist/* + !dist/keep.txt).

Lo salteado se reporta agrupado por motivo (binario, ilegible, symlink, nombre inválido, profundidad) con unos pocos ejemplos; si una carpeta concentra muchos descartes, el CLI sugiere la línea de .shiloignore que la cubre. Con --verbose ves la lista completa.

Escaneo de secretos

Antes de tocar la red, push revisa nombres sospechosos (.env, id_rsa, *.pem, credenciales de service account…) y los primeros 8 KB de cada archivo buscando patrones de claves (AWS, JWT de Supabase, tokens de GitHub, claves privadas PEM…). Si encuentra algo, no sube nada y sale con código 1, indicando archivo y línea — nunca el valor detectado. Salidas: agregarlo a .shiloignore, sacar el secreto, o --allow-secrets si es un falso positivo.

Índice local

push guarda un índice en .shilo/index.json (tamaño + mtime + hash de lo último subido). Los archivos que no cambiaron se resuelven sin bajar el contenido remoto, que es lo que hace rápido el segundo push de una carpeta grande. Se puede ignorar con --no-index para recomparar todo contra el servidor. Agregá .shilo/ a tu .gitignore (el CLI ya no lo sube).

--dry-run lista qué se crearía y qué se actualizaría, sin tocar la red más que para leer el árbol. Ya no hace falta adivinar si vas a pisar a alguien: eso lo decide el servidor con la base exacta, y si pasa, no pisa.

¿Estoy desactualizado? (status)

shilo status            # compara local vs remoto SIN bajar contenido
shilo status --short    # sólo rutas
shilo status --json     # para scripts

Es el comando barato: usa sólo listados de carpeta (una request por carpeta, cero descargas de contenido) y compara por ruta + tamaño. Informa qué tiene el remoto que vos no (con autor y fecha del último commit de cada archivo), qué tenés local sin subir, y marca aparte los archivos que cambiaron de los dos lados — el caso peligroso. Exit code 2 = estás atrasado respecto del remoto, útil en scripts.

Compensación consciente: dos archivos del mismo tamaño se reportan como iguales aunque difieran byte a byte. shilo diff y shilo push sí comparan contenido.

La comparación con "desde la última vez que sincronicé" se apoya en lastSync, que push y los pull completos escriben en .shilo.json. Si nunca sincronizaste desde esta carpeta, no hay línea de base y los cambios remotos se reportan igual.

Historial (log)

shilo log                       # últimos commits del proyecto
shilo log src/app.js            # sólo los commits que tocaron ese archivo
shilo log -n 50 --files         # más commits, listando archivos de cada uno
shilo log --json

La paginación es por commit, no por archivo: si un archivo no aparece, subí -n.

Diferencias (diff)

shilo diff                # resumen de todo lo que difiere (sólo metadata)
shilo diff src/app.js     # diff de contenido línea a línea
shilo diff src/app.js --context 6
shilo diff --stat

El diff usa el remoto como base (---) y tu copia local como destino (+++), o sea que las líneas + son lo que un push agregaría. Exit code 1 = hay diferencias.

Bajar un proyecto (pull)

shilo pull ./mi-carpeta --project <uuid>   # baja el repo a una carpeta local
shilo pull --dry-run                        # muestra qué cambiaría localmente, sin bajar nada
shilo pull --yes                            # sin pedir confirmación
shilo pull --force                          # sobrescribe también los archivos que difieren

pull recorre el árbol remoto (carpeta por carpeta), baja los archivos de texto y deja .shilo.json listo para un push posterior sin --project.

Protección del trabajo local sin guardar: no pisa un archivo local que difiera del remoto salvo con --force. Sin --force no aborta el pull entero — baja lo que es seguro, deja afuera los conflictivos y sale con código 1 para que decidas (mirá shilo diff <archivo>). Los archivos que sólo existen localmente nunca se tocan: el pull no borra. --dry-run muestra el impacto sobre tu disco (nuevo / sobrescribe / se conserva), no el árbol remoto crudo.

También bloquea nombres que intenten escribir fuera de la carpeta destino (path traversal) y saltea binarios con aviso (volverían corruptos). Espejo de push: --project pisa a .shilo.json. Con shilo init --pull se ata y baja en un solo paso.

Servidor alternativo

shilo login --base-url https://mi-shilo.vercel.app

Credenciales

Se guardan en ~/.shilo/config.json (override con SHILO_CONFIG_DIR), cifradas con AES-256-CBC y con permisos 0600.

  • Por defecto la clave de cifrado se deriva de la máquina (hostname + usuario). Esto es ofuscación en reposo, no secreto fuerte.
  • Para cifrado fuerte, exportá SHILO_ENCRYPTION_KEY (una passphrase propia) antes de shilo login; necesitarás la misma variable para futuras ejecuciones.

Se almacena el JWT, el refresh token, la expiración y el email; no se guarda la contraseña.

Tokens de acceso personal (PAT)

Un token pasado como --token abc123 queda en el historial del shell y en la lista de procesos (ps lo muestra a cualquier usuario de la máquina). El CLI lo acepta —es la forma que se copia desde la web— pero avisa. Alternativas:

cat pat.txt | shilo login --token -     # lee el token de stdin
SHILO_TOKEN=<pat> shilo init --project <uuid>   # variable de entorno (lo natural en CI)

Variables de entorno

| Variable | Efecto | | ----------------------- | ------------------------------------------------------------- | | SHILO_CONFIG_DIR | Directorio de config (default ~/.shilo). | | SHILO_ENCRYPTION_KEY | Passphrase para cifrar credenciales (recomendado). | | SHILO_TOKEN | Token de acceso personal (evita pasarlo por --token). | | SHILO_CONCURRENCY | Requests en paralelo del push (1-32, default 8). | | NO_COLOR | Desactiva colores ANSI. |

Roadmap

  • Fase 1 — auth (hecho): login, whoami, logout + config cifrada.
  • Fase 2 — motor (hecho): cliente de archivos (list/getMeta/create/write/ ensurePath/commit) + recorrido de árbol (.shiloignore) + detección de binarios.
  • Fase 3 — push (hecho): shilo init + shilo push (crea/actualiza + commit, detección de cambios, .shiloignore, --dry-run).
  • Fase 4 — pull (hecho): shilo pull (recorrido del árbol remoto con pool, guarda anti path-traversal, salto de binarios, colisiones, binding .shilo.json, --dry-run). Cierra el ciclo pull → trabajar local → push.
  • Fase 5 — colaboración asíncrona (hecho): status (comparación barata local vs remoto + exit code 2), log (historial por proyecto o archivo), diff (comparación de contenido + exit code 1), atribución de autor en push --dry-run, pull que resuelve conflictos por archivo en vez de abortar, lastSync en .shilo.json y push --card <id> para cruzar commits con un tablero externo (Metrónomo).
  • Fase 6 — robustez del push (hecho): .gitignore (incluidos los anidados), poda de repos git por forma, escaneo de secretos con corte del push, preflight 100 % offline antes de tocar la red, planificación con un recorrido del árbol remoto (una request por carpeta en vez de una por archivo), índice local .shilo/index.json, lectura perezosa del contenido, reintentos con backoff ante 429/5xx, concurrencia adaptativa, barra de progreso y confirmación real en init --push.
  • Fase 7 — propuestas (hecho): push deja de escribir archivo por archivo y manda una propuesta con la base exacta; el servidor la combina con la versión actual y nunca pisa un archivo que otra persona cambió. base en .shilo.json, registerBlob para binarios, camino viejo sólo como reserva.

Exit codes

| Comando | Código | Significado | | -------- | ------ | -------------------------------------------- | | status | 2 | El remoto tiene cambios que no tenés. | | diff | 1 | Hay diferencias entre local y remoto. | | pull | 1 | Quedaron conflictos sin bajar (o fallas). | | push | 1 | Se detectaron posibles secretos (nada subido). | | cualquiera | 1 | Error. |