shilo-cli 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,255 +1,338 @@
1
- # shilo-cli
2
-
3
- CLI para subir carpetas locales a repositorios de [Shilo](https://www.shilo.city),
4
- con una experiencia tipo git: `shilo login` una vez y después `shilo push`.
5
-
6
- > **Estado:** Fase 5 (colaboración asíncrona). Disponibles `login`, `whoami`, `logout`,
7
- > `init`, `push`, `pull`, `status`, `log`, `diff`.
8
- > Ciclo completo: `shilo status` → `shilo pull` → trabajar local → `shilo push`.
9
-
10
- ## Requisitos
11
-
12
- - Node.js **>= 18** (usa `fetch` y `crypto` nativos; sin dependencias de runtime).
13
-
14
- ## Instalación (local, durante desarrollo)
15
-
16
- ```bash
17
- cd cli
18
- npm link # expone el comando `shilo` global
19
- # o, sin link:
20
- node bin/shilo.js <comando>
21
- ```
22
-
23
- ## Uso
24
-
25
- ```bash
26
- shilo login # pregunta email y contraseña (oculta)
27
- shilo login --email you@example.com
28
- shilo whoami # muestra la sesión activa
29
- shilo logout # borra las credenciales locales
30
- ```
31
-
32
- ### Subir una carpeta (flujo tipo git)
33
-
34
- ```bash
35
- cd mi-carpeta
36
- shilo init --project <uuid> # ata la carpeta al repo (escribe .shilo.json)
37
- shilo push # sube: crea/actualiza + commit
38
- shilo push -m "mi mensaje" # con mensaje de commit
39
- shilo push --dry-run # muestra qué haría sin subir
40
- shilo push --card ABC-123 # agrega "[card: ABC-123]" al mensaje del commit
41
- shilo push --yes # sin pedir confirmación
42
- shilo push --verbose # lista archivo por archivo en vez de resumir
43
- shilo push --concurrency 4 # requests en paralelo (1-32, default 8)
44
- shilo push ./otra-carpeta --project <uuid> # sin init, proyecto explícito
45
- ```
46
-
47
- Sólo se suben archivos de **texto**; binarios, oversize (>25 MB) y nombres con
48
- caracteres no permitidos se saltan con aviso. Los archivos sin cambios no se
49
- re-suben.
50
-
51
- Los nombres aceptan **espacios, acentos y Unicode** (`Notas Metrónomo.txt`, `日本語.md`):
52
- sólo se rechaza lo que rompe una ruta o no es portable — `/ \ : * ? " < > |`, bytes de
53
- control, `.`/`..`, nombres de dispositivo de Windows (`CON`, `NUL`, `COM1`…) y el punto
54
- final. Los nombres se normalizan a NFC, igual que el backend, para que el mismo archivo
55
- subido desde macOS y desde Windows sea uno solo.
56
-
57
- Guardar es **proponer**: el push manda todo lo que cambió como una propuesta que parte
58
- de la versión que esta carpeta vio por última vez (`base` en `.shilo.json`, que
59
- `pull` y `push` van anotando). El servidor la aplica solo si combina limpio con la
60
- versión actual. Si otra persona cambió alguno de los **mismos archivos** desde esa
61
- base, la propuesta **queda abierta y no se pisa nada**: el CLI dice qué archivos son,
62
- tus cambios quedan guardados en esa propuesta para que el dueño decida, y vos podés
63
- traer la versión actual con `shilo pull`, ajustar y volver a `push`. Un push nuevo
64
- reemplaza tus propuestas abiertas anteriores.
65
-
66
- El texto viaja inline (hasta 500 archivos o 4 MB por propuesta; más que eso son varias
67
- propuestas de la misma base); los binarios y los textos de más de 1 MB se suben por URL
68
- firmada y entran por su identificador de contenido. Contra un servidor anterior a las
69
- propuestas, el CLI avisa y cae al camino viejo (escribir + commit), que sí puede pisar.
70
-
71
- #### Qué se ignora
72
-
73
- `push` respeta **`.gitignore` y `.shiloignore`**, incluidos los de subcarpetas (una
74
- regla de `src/.gitignore` sólo alcanza a `src/`). `.shiloignore` se lee último, así que
75
- podés re-incluir con `!` algo que git ignora. Por defecto ya se saltean `.git/`,
76
- `node_modules/`, `.shilo/`, `.vercel/`, `.DS_Store` y `Thumbs.db`.
77
-
78
- También se poda cualquier carpeta que **tenga forma de repo git** (`HEAD` + `objects/`
79
- + `refs/`), aunque no se llame `.git`: un `.git-ORIGINAL-BACKUP/` trae todo el
80
- historial, incluidos secretos de commits ya borrados del árbol de trabajo.
81
-
82
- > **Limitación conocida:** la negación `!` se evalúa por regla, no por directorio. Como
83
- > en git, `!` no puede re-incluir un archivo si su carpeta padre ya está excluida
84
- > (`dist/` + `!dist/keep.txt` no funciona); excluí el contenido en vez de la carpeta
85
- > (`dist/*` + `!dist/keep.txt`).
86
-
87
- Lo salteado se reporta **agrupado por motivo** (binario, ilegible, symlink, nombre
88
- inválido, profundidad) con unos pocos ejemplos; si una carpeta concentra muchos
89
- descartes, el CLI sugiere la línea de `.shiloignore` que la cubre. Con `--verbose` ves
90
- la lista completa.
91
-
92
- #### Escaneo de secretos
93
-
94
- Antes de tocar la red, `push` revisa nombres sospechosos (`.env`, `id_rsa`,
95
- `*.pem`, credenciales de service account…) y los primeros 8 KB de cada archivo
96
- buscando patrones de claves (AWS, JWT de Supabase, tokens de GitHub, claves privadas
97
- PEM…). Si encuentra algo, **no sube nada** y sale con código 1, indicando archivo y
98
- línea — nunca el valor detectado. Salidas: agregarlo a `.shiloignore`, sacar el
99
- secreto, o `--allow-secrets` si es un falso positivo.
100
-
101
- #### Índice local
102
-
103
- `push` guarda un índice en `.shilo/index.json` (tamaño + mtime + hash de lo último
104
- subido). Los archivos que no cambiaron se resuelven sin bajar el contenido remoto,
105
- que es lo que hace rápido el segundo push de una carpeta grande. Se puede ignorar con
106
- `--no-index` para recomparar todo contra el servidor. Agregá `.shilo/` a tu
107
- `.gitignore` (el CLI ya no lo sube).
108
-
109
- `--dry-run` lista qué se crearía y qué se actualizaría, sin tocar la red más que para
110
- leer el árbol. Ya no hace falta adivinar si vas a pisar a alguien: eso lo decide el
111
- servidor con la base exacta, y si pasa, no pisa.
112
-
113
- ### ¿Estoy desactualizado? (status)
114
-
115
- ```bash
116
- shilo status # compara local vs remoto SIN bajar contenido
117
- shilo status --short # sólo rutas
118
- shilo status --json # para scripts
119
- ```
120
-
121
- Es el comando barato: usa sólo listados de carpeta (una request por carpeta, cero
122
- descargas de contenido) y compara por **ruta + tamaño**. Informa qué tiene el remoto
123
- que vos no (con autor y fecha del último commit de cada archivo), qué tenés local sin
124
- subir, y marca aparte los archivos que cambiaron **de los dos lados** — el caso
125
- peligroso. **Exit code 2** = estás atrasado respecto del remoto, útil en scripts.
126
-
127
- Compensación consciente: dos archivos del mismo tamaño se reportan como iguales
128
- aunque difieran byte a byte. `shilo diff` y `shilo push` sí comparan contenido.
129
-
130
- La comparación con "desde la última vez que sincronicé" se apoya en `lastSync`, que
131
- `push` y los `pull` completos escriben en `.shilo.json`. Si nunca sincronizaste desde
132
- esta carpeta, no hay línea de base y los cambios remotos se reportan igual.
133
-
134
- ### Historial (log)
135
-
136
- ```bash
137
- shilo log # últimos commits del proyecto
138
- shilo log src/app.js # sólo los commits que tocaron ese archivo
139
- shilo log -n 50 --files # más commits, listando archivos de cada uno
140
- shilo log --json
141
- ```
142
-
143
- La paginación es **por commit**, no por archivo: si un archivo no aparece, subí `-n`.
144
-
145
- ### Diferencias (diff)
146
-
147
- ```bash
148
- shilo diff # resumen de todo lo que difiere (sólo metadata)
149
- shilo diff src/app.js # diff de contenido línea a línea
150
- shilo diff src/app.js --context 6
151
- shilo diff --stat
152
- ```
153
-
154
- El diff usa el remoto como base (`---`) y tu copia local como destino (`+++`), o sea
155
- que las líneas `+` son lo que un `push` agregaría. **Exit code 1** = hay diferencias.
156
-
157
- ### Bajar un proyecto (pull)
158
-
159
- ```bash
160
- shilo pull ./mi-carpeta --project <uuid> # baja el repo a una carpeta local
161
- shilo pull --dry-run # muestra qué cambiaría localmente, sin bajar nada
162
- shilo pull --yes # sin pedir confirmación
163
- shilo pull --force # sobrescribe también los archivos que difieren
164
- ```
165
-
166
- `pull` recorre el árbol remoto (carpeta por carpeta), baja los archivos de texto
167
- y deja `.shilo.json` listo para un `push` posterior sin `--project`.
168
-
169
- Protección del trabajo local sin guardar: **no pisa** un archivo local que difiera del
170
- remoto salvo con `--force`. Sin `--force` no aborta el pull entero — baja lo que es
171
- seguro, deja afuera los conflictivos y sale con código 1 para que decidas (mirá
172
- `shilo diff <archivo>`). Los archivos que sólo existen localmente nunca se tocan: el
173
- pull no borra. `--dry-run` muestra el impacto sobre tu disco (nuevo / sobrescribe /
174
- se conserva), no el árbol remoto crudo.
175
-
176
- También bloquea nombres que intenten escribir fuera de la carpeta destino (path
177
- traversal) y saltea binarios con aviso (volverían corruptos). Espejo de `push`:
178
- `--project` pisa a `.shilo.json`. Con `shilo init --pull` se ata y baja en un solo paso.
179
-
180
- ### Servidor alternativo
181
-
182
- ```bash
183
- shilo login --base-url https://mi-shilo.vercel.app
184
- ```
185
-
186
- ## Credenciales
187
-
188
- Se guardan en `~/.shilo/config.json` (override con `SHILO_CONFIG_DIR`), **cifradas**
189
- con AES-256-CBC y con permisos `0600`.
190
-
191
- - Por defecto la clave de cifrado se deriva de la máquina (hostname + usuario).
192
- Esto es **ofuscación en reposo**, no secreto fuerte.
193
- - Para cifrado fuerte, exportá `SHILO_ENCRYPTION_KEY` (una passphrase propia) antes
194
- de `shilo login`; necesitarás la misma variable para futuras ejecuciones.
195
-
196
- Se almacena el JWT, el refresh token, la expiración y el email; **no** se guarda la
197
- contraseña.
198
-
199
- ### Tokens de acceso personal (PAT)
200
-
201
- Un token pasado como `--token abc123` queda en el historial del shell y en la lista de
202
- procesos (`ps` lo muestra a cualquier usuario de la máquina). El CLI lo acepta —es la
203
- forma que se copia desde la web— pero avisa. Alternativas:
204
-
205
- ```bash
206
- cat pat.txt | shilo login --token - # lee el token de stdin
207
- SHILO_TOKEN=<pat> shilo init --project <uuid> # variable de entorno (lo natural en CI)
208
- ```
209
-
210
- ## Variables de entorno
211
-
212
- | Variable | Efecto |
213
- | ----------------------- | ------------------------------------------------------------- |
214
- | `SHILO_CONFIG_DIR` | Directorio de config (default `~/.shilo`). |
215
- | `SHILO_ENCRYPTION_KEY` | Passphrase para cifrar credenciales (recomendado). |
216
- | `SHILO_TOKEN` | Token de acceso personal (evita pasarlo por `--token`). |
217
- | `SHILO_CONCURRENCY` | Requests en paralelo del push (1-32, default 8). |
218
- | `NO_COLOR` | Desactiva colores ANSI. |
219
-
220
- ## Roadmap
221
-
222
- - **Fase 1 — auth** *(hecho)*: `login`, `whoami`, `logout` + config cifrada.
223
- - **Fase 2 — motor** *(hecho)*: cliente de archivos (`list`/`getMeta`/`create`/`write`/
224
- `ensurePath`/`commit`) + recorrido de árbol (`.shiloignore`) + detección de binarios.
225
- - **Fase 3 — push** *(hecho)*: `shilo init` + `shilo push` (crea/actualiza + commit,
226
- detección de cambios, `.shiloignore`, `--dry-run`).
227
- - **Fase 4 — pull** *(hecho)*: `shilo pull` (recorrido del árbol remoto con pool,
228
- guarda anti path-traversal, salto de binarios, colisiones, binding `.shilo.json`,
229
- `--dry-run`). Cierra el ciclo `pull → trabajar local → push`.
230
- - **Fase 5 — colaboración asíncrona** *(hecho)*: `status` (comparación barata local vs
231
- remoto + exit code 2), `log` (historial por proyecto o archivo), `diff` (comparación
232
- de contenido + exit code 1), atribución de autor en `push --dry-run`, `pull` que
233
- resuelve conflictos por archivo en vez de abortar, `lastSync` en `.shilo.json` y
234
- `push --card <id>` para cruzar commits con un tablero externo (Metrónomo).
235
- - **Fase 6 — robustez del push** *(hecho)*: `.gitignore` (incluidos los anidados),
236
- poda de repos git por forma, escaneo de secretos con corte del push, preflight
237
- 100 % offline antes de tocar la red, planificación con un recorrido del árbol
238
- remoto (una request por carpeta en vez de una por archivo), índice local
239
- `.shilo/index.json`, lectura perezosa del contenido, reintentos con backoff ante
240
- 429/5xx, concurrencia adaptativa, barra de progreso y confirmación real en
241
- `init --push`.
242
- - **Fase 7 — propuestas** *(hecho)*: `push` deja de escribir archivo por archivo y
243
- manda una propuesta con la base exacta; el servidor la combina con la versión actual
244
- y nunca pisa un archivo que otra persona cambió. `base` en `.shilo.json`,
245
- `registerBlob` para binarios, camino viejo sólo como reserva.
246
-
247
- ## Exit codes
248
-
249
- | Comando | Código | Significado |
250
- | -------- | ------ | -------------------------------------------- |
251
- | `status` | 2 | El remoto tiene cambios que no tenés. |
252
- | `diff` | 1 | Hay diferencias entre local y remoto. |
253
- | `pull` | 1 | Quedaron conflictos sin bajar (o fallas). |
254
- | `push` | 1 | Se detectaron posibles secretos (nada subido). |
255
- | cualquiera | 1 | Error. |
1
+ # shilo-cli
2
+
3
+ CLI para subir carpetas locales a repositorios de [Shilo](https://www.shilo.city),
4
+ con una experiencia tipo git: `shilo login` una vez y después `shilo push`.
5
+
6
+ > **Estado:** Fase 5 (colaboración asíncrona). Disponibles `login`, `whoami`, `logout`,
7
+ > `init`, `push`, `pull`, `status`, `log`, `diff`.
8
+ > Ciclo completo: `shilo status` → `shilo pull` → trabajar local → `shilo push`.
9
+
10
+ ## Requisitos
11
+
12
+ - Node.js **>= 18** (usa `fetch` y `crypto` nativos; sin dependencias de runtime).
13
+
14
+ ## Instalación (local, durante desarrollo)
15
+
16
+ ```bash
17
+ cd cli
18
+ npm link # expone el comando `shilo` global
19
+ # o, sin link:
20
+ node bin/shilo.js <comando>
21
+ ```
22
+
23
+ ## Uso
24
+
25
+ ```bash
26
+ shilo login # pregunta email y contraseña (oculta)
27
+ shilo login --email you@example.com
28
+ shilo whoami # muestra la sesión activa
29
+ shilo logout # borra las credenciales locales
30
+ ```
31
+
32
+ ### Subir una carpeta (flujo tipo git)
33
+
34
+ ```bash
35
+ cd mi-carpeta
36
+ shilo init --project <uuid> # ata la carpeta al repo (escribe .shilo.json)
37
+ shilo push # sube: crea/actualiza + commit
38
+ shilo push -m "mi mensaje" # con mensaje de commit
39
+ shilo push --deploy prod # este push despliega a producción ("[deploy: prod]")
40
+ shilo push --dry-run # muestra qué haría sin subir
41
+ shilo push --card ABC-123 # agrega "[card: ABC-123]" al mensaje del commit
42
+ shilo push --agent claude-code --model claude-opus-5 # lo escribió un agente, y queda anotado
43
+ shilo push --agent claude-code --prompt-file pedido.md # guarda además el pedido que lo originó
44
+ shilo push --yes # sin pedir confirmación
45
+ shilo push --verbose # lista archivo por archivo en vez de resumir
46
+ shilo push --concurrency 4 # requests en paralelo (1-32, default 8)
47
+ shilo push ./otra-carpeta --project <uuid> # sin init, proyecto explícito
48
+ ```
49
+
50
+ Sólo se suben archivos de **texto**; binarios, oversize (>25 MB) y nombres con
51
+ caracteres no permitidos se saltan con aviso. Los archivos sin cambios no se
52
+ re-suben.
53
+
54
+ Los nombres aceptan **espacios, acentos y Unicode** (`Notas Metrónomo.txt`, `日本語.md`):
55
+ sólo se rechaza lo que rompe una ruta o no es portable — `/ \ : * ? " < > |`, bytes de
56
+ control, `.`/`..`, nombres de dispositivo de Windows (`CON`, `NUL`, `COM1`…) y el punto
57
+ final. Los nombres se normalizan a NFC, igual que el backend, para que el mismo archivo
58
+ subido desde macOS y desde Windows sea uno solo.
59
+
60
+ Guardar es **proponer**: el push manda todo lo que cambió como una propuesta que parte
61
+ de la versión que esta carpeta vio por última vez (`base` en `.shilo.json`, que
62
+ `pull` y `push` van anotando). El servidor la aplica solo si combina limpio con la
63
+ versión actual. Si otra persona cambió alguno de los **mismos archivos** desde esa
64
+ base, la propuesta **queda abierta y no se pisa nada**: el CLI dice qué archivos son,
65
+ tus cambios quedan guardados en esa propuesta para que el dueño decida, y vos podés
66
+ traer la versión actual con `shilo pull`, ajustar y volver a `push`. Un push nuevo
67
+ reemplaza tus propuestas abiertas anteriores.
68
+
69
+ El texto viaja inline (hasta 500 archivos o 4 MB por propuesta; más que eso son varias
70
+ propuestas de la misma base); los binarios y los textos de más de 1 MB se suben por URL
71
+ firmada y entran por su identificador de contenido. Contra un servidor anterior a las
72
+ propuestas, el CLI avisa y cae al camino viejo (escribir + commit), que sí puede pisar.
73
+
74
+ #### Subir sólo una parte (`--only`)
75
+
76
+ ```bash
77
+ shilo push --only Documentacion/decisiones.md --only LEEME.md -m "decisiones de la semana"
78
+ shilo push --only Documentacion/ # un prefijo de carpeta también vale
79
+ ```
80
+
81
+ `--only` es repetible y recorta **qué se sube**, no qué se revisa: los ignores y el
82
+ escaneo de secretos corren igual sobre toda la carpeta. Como el push nunca borra
83
+ archivos remotos, subir un subconjunto es seguro por construcción.
84
+
85
+ Un selector que **no matchea nada es un error** (código 1) y no sube nada: nunca vas a
86
+ pedir dos archivos y terminar subiendo el repo entero. Al final, el resumen dice
87
+ cuántos archivos con cambios locales quedaron fuera, para que "una pieza = una tarjeta"
88
+ no se convierta en trabajo olvidado. El índice local no se poda con la selección: el
89
+ push completo siguiente sigue siendo rápido.
90
+
91
+ #### Qué se ignora
92
+
93
+ `push` respeta **`.gitignore` y `.shiloignore`**, incluidos los de subcarpetas (una
94
+ regla de `src/.gitignore` sólo alcanza a `src/`). `.shiloignore` se lee último, así que
95
+ podés re-incluir con `!` algo que git ignora. Por defecto ya se saltean `.git/`,
96
+ `node_modules/`, `.shilo/`, `.vercel/`, `.DS_Store` y `Thumbs.db`.
97
+
98
+ También se poda cualquier carpeta que **tenga forma de repo git** (`HEAD` + `objects/`
99
+ + `refs/`), aunque no se llame `.git`: un `.git-ORIGINAL-BACKUP/` trae todo el
100
+ historial, incluidos secretos de commits ya borrados del árbol de trabajo.
101
+
102
+ > **Limitación conocida:** la negación `!` se evalúa por regla, no por directorio. Como
103
+ > en git, `!` no puede re-incluir un archivo si su carpeta padre ya está excluida
104
+ > (`dist/` + `!dist/keep.txt` no funciona); excluí el contenido en vez de la carpeta
105
+ > (`dist/*` + `!dist/keep.txt`).
106
+
107
+ Lo salteado se reporta **agrupado por motivo** (binario, ilegible, symlink, nombre
108
+ inválido, profundidad) con unos pocos ejemplos; si una carpeta concentra muchos
109
+ descartes, el CLI sugiere la línea de `.shiloignore` que la cubre. Con `--verbose` ves
110
+ la lista completa.
111
+
112
+ #### Escaneo de secretos
113
+
114
+ Antes de tocar la red, `push` revisa nombres sospechosos (`.env`, `id_rsa`,
115
+ `*.pem`, credenciales de service account…) y los primeros 8 KB de cada archivo
116
+ buscando patrones de claves (AWS, JWT de Supabase, tokens de GitHub, claves privadas
117
+ PEM…). Si encuentra algo, **no sube nada** y sale con código 1, indicando archivo y
118
+ línea — nunca el valor detectado. Salidas: agregarlo a `.shiloignore`, sacar el
119
+ secreto, o `--allow-secrets` si es un falso positivo.
120
+
121
+ #### Índice local
122
+
123
+ `push` guarda un índice en `.shilo/index.json` (tamaño + mtime + hash de lo último
124
+ subido). Los archivos que no cambiaron se resuelven sin bajar el contenido remoto,
125
+ que es lo que hace rápido el segundo push de una carpeta grande. Se puede ignorar con
126
+ `--no-index` para recomparar todo contra el servidor. Agregá `.shilo/` a tu
127
+ `.gitignore` (el CLI ya no lo sube).
128
+
129
+ `--dry-run` lista qué se crearía y qué se actualizaría, sin tocar la red más que para
130
+ leer el árbol. Ya no hace falta adivinar si vas a pisar a alguien: eso lo decide el
131
+ servidor con la base exacta, y si pasa, no pisa.
132
+
133
+ ### ¿Estoy desactualizado? (status)
134
+
135
+ ```bash
136
+ shilo status # compara local vs remoto SIN bajar contenido
137
+ shilo status --short # sólo rutas
138
+ shilo status --show-ignored # lista los archivos del remoto ignorados localmente
139
+ shilo status --json # para scripts
140
+ ```
141
+
142
+ Es el comando barato: usa sólo listados de carpeta (una request por carpeta, cero
143
+ descargas de contenido) y compara por **ruta + tamaño**. Informa qué tiene el remoto
144
+ que vos no (con autor y fecha del último commit de cada archivo), qué tenés local sin
145
+ subir, y marca aparte los archivos que cambiaron **de los dos lados** — el caso
146
+ peligroso. **Exit code 2** = estás atrasado respecto del remoto, útil en scripts.
147
+
148
+ **Los ignores valen en los dos sentidos.** Un archivo que existe en el remoto pero
149
+ está ignorado localmente (un `.env.ejemplo`, un `settings.local.json` que alguien subió
150
+ desde otra máquina) **no cuenta como "atrás"**: se informa en una línea aparte —con
151
+ `--show-ignored` para verlos, y en el campo `ignoredRemote` del `--json`— pero no
152
+ enciende el exit code 2. Antes quedaba en "faltan localmente" para siempre y la señal
153
+ que debería frenarte era ruido permanente.
154
+
155
+ Compensación consciente: dos archivos del mismo tamaño se reportan como iguales
156
+ aunque difieran byte a byte. `shilo diff` y `shilo push` sí comparan contenido.
157
+
158
+ La comparación con "desde la última vez que sincronicé" se apoya en `lastSync`, que
159
+ `push` y los `pull` completos escriben en `.shilo.json`. Si nunca sincronizaste desde
160
+ esta carpeta, no hay línea de base y los cambios remotos se reportan igual.
161
+
162
+ ### Historial (log)
163
+
164
+ ```bash
165
+ shilo log # últimos commits del proyecto
166
+ shilo log src/app.js # sólo los commits que tocaron ese archivo
167
+ shilo log -n 50 --files # más commits, listando archivos de cada uno
168
+ shilo log --json
169
+ ```
170
+
171
+ La paginación es **por commit**, no por archivo: si un archivo no aparece, subí `-n`.
172
+
173
+ ### Deploys a Vercel (deploys)
174
+
175
+ Si el owner conectó el proyecto con Vercel (pestaña **Deploys** en la web), cada
176
+ push que cambia algo dispara un deploy: por defecto a **preview**. Producción sólo
177
+ si se pide explícitamente.
178
+
179
+ ```bash
180
+ shilo push --deploy prod # este push va a producción
181
+ shilo push --deploy off # este push no despliega
182
+ shilo deploys # destino y últimos deploys, con estado y URL
183
+ shilo deploys -n 20 --json
184
+ ```
185
+
186
+ `--deploy` sólo agrega el trailer `[deploy: …]` al mensaje; escribirlo a mano en
187
+ `-m` da lo mismo.
188
+
189
+ ### Diferencias (diff)
190
+
191
+ ```bash
192
+ shilo diff # resumen de todo lo que difiere (sólo metadata)
193
+ shilo diff src/app.js # diff de contenido línea a línea
194
+ shilo diff src/app.js --context 6
195
+ shilo diff --stat
196
+ ```
197
+
198
+ El diff usa el remoto como base (`---`) y tu copia local como destino (`+++`), o sea
199
+ que las líneas `+` son lo que un `push` agregaría. **Exit code 1** = hay diferencias.
200
+
201
+ ### Bajar un proyecto (pull)
202
+
203
+ ```bash
204
+ shilo pull ./mi-carpeta --project <uuid> # baja el repo a una carpeta local
205
+ shilo pull --dry-run # muestra qué cambiaría localmente, sin bajar nada
206
+ shilo pull --yes # sin pedir confirmación
207
+ shilo pull --force # sobrescribe también los archivos que difieren
208
+ shilo pull --include-ignored # baja también lo ignorado localmente
209
+ ```
210
+
211
+ `pull` recorre el árbol remoto (carpeta por carpeta), baja los archivos de texto
212
+ y deja `.shilo.json` listo para un `push` posterior sin `--project`.
213
+
214
+ Protección del trabajo local sin guardar: **no pisa** un archivo local que difiera del
215
+ remoto salvo con `--force`. Sin `--force` no aborta el pull entero — baja lo que es
216
+ seguro, deja afuera los conflictivos y sale con código 1 para que decidas (mirá
217
+ `shilo diff <archivo>`). Los archivos que sólo existen localmente nunca se tocan: el
218
+ pull no borra. `--dry-run` muestra el impacto sobre tu disco (nuevo / sobrescribe /
219
+ se conserva), no el árbol remoto crudo.
220
+
221
+ **Respaldo automático con `--force`.** Antes de pisar un archivo local que difería, el
222
+ pull guarda tu versión en `.shilo/respaldo/<fecha-ISO>/<ruta>` y te dice la carpeta al
223
+ terminar. Va dentro de `.shilo/` —ignorado por defecto— y no como un `<archivo>.local`
224
+ al lado, que el push siguiente subiría: cambiaríamos "perder trabajo" por "publicar tu
225
+ versión en conflicto". Al ir por fecha, dos pulls seguidos no se pisan entre sí.
226
+
227
+ **Lo ignorado localmente no se baja.** Si `.env.*` está en tu `.shiloignore`, que
228
+ alguien haya subido un `.env.ejemplo` desde otra máquina no lo hace aparecer acá: se
229
+ cuenta aparte como "ignorados (no se bajan)", y con `--include-ignored` se bajan igual.
230
+ Esto también arregla el conteo, que antes prometía más archivos de los que escribía.
231
+
232
+ También bloquea nombres que intenten escribir fuera de la carpeta destino (path
233
+ traversal) y saltea binarios con aviso (volverían corruptos). Espejo de `push`:
234
+ `--project` pisa a `.shilo.json`. Con `shilo init --pull` se ata y baja en un solo paso.
235
+
236
+ ### Exportar a Git (export)
237
+
238
+ ```bash
239
+ shilo export ../mi-repo --project <uuid> # la historia entera, como repositorio Git
240
+ shilo export ../mi-repo # otra vez: trae sólo lo nuevo
241
+ git -C ../mi-repo push <remoto> main
242
+ git -C ../mi-repo push <remoto> +refs/notes/shilo
243
+ ```
244
+
245
+ Escribe todos los puntos de guardado de `main` como commits de Git, con su autor y
246
+ su fecha, y la procedencia (agente, tarjeta, propuesta) al pie de cada mensaje y
247
+ completa en `refs/notes/shilo`. Es de sólo lectura para el proyecto. Necesita `git`
248
+ en el PATH; un proyecto público se exporta sin sesión.
249
+
250
+ - **La primera vez**, la carpeta tiene que estar vacía o no existir. Después, repetir
251
+ el comando sobre la misma carpeta baja sólo los archivos nuevos
252
+ (`.git/shilo/mapa` recuerda lo exportado) y exige que lo viejo dé los mismos SHA-1.
253
+ - **Determinístico:** el mismo proyecto da los mismos SHA-1 en cualquier máquina. Los
254
+ commits firman con el nombre congelado de cada persona y
255
+ `<usuario>@users.noreply.shilo.city`, nunca con su correo real.
256
+ - **No pisa trabajo ajeno:** si `main` tiene commits que no salieron de Shilo, o hay
257
+ cambios sin guardar, se niega y no toca nada.
258
+ - **Git no guarda carpetas vacías:** quedan afuera y el informe las lista.
259
+ - La ref de notas se rehace entera en cada exportación: por eso el `+` en su push.
260
+
261
+ ### Servidor alternativo
262
+
263
+ ```bash
264
+ shilo login --base-url https://mi-shilo.vercel.app
265
+ ```
266
+
267
+ ## Credenciales
268
+
269
+ Se guardan en `~/.shilo/config.json` (override con `SHILO_CONFIG_DIR`), **cifradas**
270
+ con AES-256-CBC y con permisos `0600`.
271
+
272
+ - Por defecto la clave de cifrado se deriva de la máquina (hostname + usuario).
273
+ Esto es **ofuscación en reposo**, no secreto fuerte.
274
+ - Para cifrado fuerte, exportá `SHILO_ENCRYPTION_KEY` (una passphrase propia) antes
275
+ de `shilo login`; necesitarás la misma variable para futuras ejecuciones.
276
+
277
+ Se almacena el JWT, el refresh token, la expiración y el email; **no** se guarda la
278
+ contraseña.
279
+
280
+ ### Tokens de acceso personal (PAT)
281
+
282
+ Un token pasado como `--token abc123` queda en el historial del shell y en la lista de
283
+ procesos (`ps` lo muestra a cualquier usuario de la máquina). El CLI lo acepta —es la
284
+ forma que se copia desde la web— pero avisa. Alternativas:
285
+
286
+ ```bash
287
+ cat pat.txt | shilo login --token - # lee el token de stdin
288
+ SHILO_TOKEN=<pat> shilo init --project <uuid> # variable de entorno (lo natural en CI)
289
+ ```
290
+
291
+ ## Variables de entorno
292
+
293
+ | Variable | Efecto |
294
+ | ----------------------- | ------------------------------------------------------------- |
295
+ | `SHILO_CONFIG_DIR` | Directorio de config (default `~/.shilo`). |
296
+ | `SHILO_ENCRYPTION_KEY` | Passphrase para cifrar credenciales (recomendado). |
297
+ | `SHILO_TOKEN` | Token de acceso personal (evita pasarlo por `--token`). |
298
+ | `SHILO_CONCURRENCY` | Requests en paralelo del push (1-32, default 8). |
299
+ | `NO_COLOR` | Desactiva colores ANSI. |
300
+
301
+ ## Roadmap
302
+
303
+ - **Fase 1 — auth** *(hecho)*: `login`, `whoami`, `logout` + config cifrada.
304
+ - **Fase 2 — motor** *(hecho)*: cliente de archivos (`list`/`getMeta`/`create`/`write`/
305
+ `ensurePath`/`commit`) + recorrido de árbol (`.shiloignore`) + detección de binarios.
306
+ - **Fase 3 — push** *(hecho)*: `shilo init` + `shilo push` (crea/actualiza + commit,
307
+ detección de cambios, `.shiloignore`, `--dry-run`).
308
+ - **Fase 4 — pull** *(hecho)*: `shilo pull` (recorrido del árbol remoto con pool,
309
+ guarda anti path-traversal, salto de binarios, colisiones, binding `.shilo.json`,
310
+ `--dry-run`). Cierra el ciclo `pull → trabajar local → push`.
311
+ - **Fase 5 — colaboración asíncrona** *(hecho)*: `status` (comparación barata local vs
312
+ remoto + exit code 2), `log` (historial por proyecto o archivo), `diff` (comparación
313
+ de contenido + exit code 1), atribución de autor en `push --dry-run`, `pull` que
314
+ resuelve conflictos por archivo en vez de abortar, `lastSync` en `.shilo.json` y
315
+ `push --card <id>` para cruzar commits con un tablero externo (Metrónomo).
316
+ - **Fase 6 — robustez del push** *(hecho)*: `.gitignore` (incluidos los anidados),
317
+ poda de repos git por forma, escaneo de secretos con corte del push, preflight
318
+ 100 % offline antes de tocar la red, planificación con un recorrido del árbol
319
+ remoto (una request por carpeta en vez de una por archivo), índice local
320
+ `.shilo/index.json`, lectura perezosa del contenido, reintentos con backoff ante
321
+ 429/5xx, concurrencia adaptativa, barra de progreso y confirmación real en
322
+ `init --push`.
323
+ - **Fase 7 — propuestas** *(hecho)*: `push` deja de escribir archivo por archivo y
324
+ manda una propuesta con la base exacta; el servidor la combina con la versión actual
325
+ y nunca pisa un archivo que otra persona cambió. `base` en `.shilo.json`,
326
+ `registerBlob` para binarios, camino viejo sólo como reserva.
327
+ - **Fase 8 — deploys** *(hecho)*: `push --deploy <prod|preview|off>` y
328
+ `shilo deploys` para el deploy automático a Vercel (PLAN_DEPLOY_VERCEL).
329
+
330
+ ## Exit codes
331
+
332
+ | Comando | Código | Significado |
333
+ | -------- | ------ | -------------------------------------------- |
334
+ | `status` | 2 | El remoto tiene cambios que no tenés. |
335
+ | `diff` | 1 | Hay diferencias entre local y remoto. |
336
+ | `pull` | 1 | Quedaron conflictos sin bajar (o fallas). |
337
+ | `push` | 1 | Se detectaron posibles secretos (nada subido). |
338
+ | cualquiera | 1 | Error. |