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