
npm install -g cortex-sync@latest
Las últimas tres versiones fueron seguridad y escalabilidad. Esta es la fase que el roadmap tenía marcada como "DX y confiabilidad" — nada urgente, solo pulir cosas del día a día: un comando de diagnóstico, un modo de solo-lectura para sync, reintentos cuando la API de GitHub falla, y un par de cosas menores que quedaron anotadas hace rato.
cortex doctorHasta ahora, si algo estaba mal configurado, te enterabas corriendo cortex sync y viendo qué error tiraba. cortex doctor corre los mismos chequeos sin tocar nada:
$ cortex doctor \ \
✓ config: Config found at ~/.cortex/config.json \ \
✓ config-permissions: Config file mode is 600 \ \
✓ storage: local is reachable \ \
✓ passphrase: CORTEX_PASSPHRASE is set \ \
✓ tools: All configured tools are present
All checks passed.
Revisa que el archivo de config exista y tenga permisos 600 (guarda el token), que el backend configurado sea alcanzable (para GitHub: valida el token contra /user y después prueba el repo; para local: que la carpeta destino exista), si CORTEX_PASSPHRASE está seteada, y si las herramientas configuradas (claude-code, etc.) siguen presentes en esta máquina.
cortex sync --dry-runImprime el diff — qué se agregaría, qué cambió, qué se borraría — sin cifrar, subir, ni tocar el manifiesto local. Una excepción honesta: si es el primer contacto con un proyecto que nunca se sincronizó, igual crea el archivo de salt del proyecto (sin cifrar, idempotente) — lo necesito de todos modos para poder descifrar un manifiesto remoto existente y calcular el diff bien. Es el único write que un dry-run puede llegar a hacer, y lo reutiliza el próximo sync real.
Un 403 por rate limit, o un 503 pasajero de GitHub, tiraba abajo el comando entero — había que volver a correrlo a mano. Ahora fetchWithRetry reintenta con backoff exponencial (300ms, 600ms, 1200ms) en 429, 5xx, y el 403 de rate limit específicamente:
function isRetryableStatus(res: Response): boolean { \ \
if (res.status === 429) return true; \ \
if (res.status >= 500) return true; \ \
// GitHub reusa 403 tanto para rate limit como para error de permisos — \ \
// solo el de rate limit vale la pena reintentar. \ \
if (res.status === 403 && res.headers.get('x-ratelimit-remaining') === '0') return true; \ \
return false; \ \
}
Lo importante es lo que no reintenta: un 404 o un 401 no van a cambiar de resultado por esperar y volver a intentar — reintentarlos solo hace perder tiempo antes de mostrar el error real.
El primero, mirando mi propio código: mcp.ts reportaba version: '0.1.0' fijo, sin importar qué versión estuviera realmente publicada — un resto de cuando escribí el server por primera vez. cli.ts ya leía la versión real desde package.json; le copié el mismo patrón a mcp.ts, y me llevé una sorpresa al verificarlo: la ruta relativa correcta no es la misma en ambos archivos.
cli.ts vive en src/cli.ts (un nivel bajo la raíz) y usa require('../package.json'). mcp.ts vive en src/commands/mcp.ts (dos niveles), así que por profundidad de archivo le correspondería require('../../package.json') — y esa fue mi primera versión. Falló. tsup empaqueta todo en un solo dist/cli.js, así que en tiempo de ejecución la ruta se resuelve relativa a ese archivo (un nivel bajo la raíz), no a donde vivía mcp.ts en el código fuente. Tuve que probarlo contra el bundle real para encontrar la ruta que efectivamente funciona — la misma que ya usaba cli.ts, por casualidad de que ambos quedan a la misma profundidad después de empaquetar.
El segundo, ya estaba anotado en el roadmap: el script GIT_ASKPASS que uso en Windows escribía el token PAT directo en el cuerpo de un .cmd, sin escapar nada. %, &, |, <, >, ^, " son todos caracteres especiales para cmd.exe — la rama de Unix ya escapaba comillas simples correctamente, la de Windows no escapaba nada. Nunca se explotó ni se reportó; lo arreglé porque estaba en la lista, no porque algo hubiera fallado. La solución es más simple que enumerar caracteres a escapar: el token ahora viaja por una variable de entorno (CORTEX_ASKPASS_TOKEN) en vez de escribirse en el archivo — así el contenido del script es siempre el mismo texto fijo, sin importar qué tenga el token.
No había linter configurado. Lo agregué (typescript-eslint, flat config) y lo metí en el CI, antes de typecheck/test/build. La corrida encontró cuatro cosas reales al aplicarlo por primera vez: un catch que descartaba el error original en vez de encadenarlo con cause, un import sin usar en un test, y el script de postinstall (que es CommonJS puro, corre antes del build) necesitando su propia configuración de globals — nada grave, pero es exactamente el tipo de cosas que un linter agarra antes de que alguien lo note a mano.
npm install -g cortex-sync@latest # → 0.7.0
211 tests en 35 archivos, todos en verde, más una corrida real del binario compilado (cortex doctor contra una config real, cortex sync --dry-run confirmando que no sube nada).
GitHub: SebastiaWeb/cortex-cli