diff options
| author | Elvis Claros Castro <elvis@claros.ar> | 2026-08-16 19:57:17 -0300 |
|---|---|---|
| committer | Elvis Claros Castro <elvis@claros.ar> | 2026-08-16 19:57:17 -0300 |
| commit | bd3ca3a04fcd8d882d23deeb3f3a0d6f04b3f93f (patch) | |
| tree | a9e0dd65940de54c8f956befe9d86b80350bde2e | |
| parent | da1c60458b0d28e84739475491a3f2d61ed8eff8 (diff) | |
| download | claude-logbook-bd3ca3a04fcd8d882d23deeb3f3a0d6f04b3f93f.tar.gz claude-logbook-bd3ca3a04fcd8d882d23deeb3f3a0d6f04b3f93f.zip | |
Preparar el proyecto para publicarlo
El script suelto pasa a ser un paquete instalable, con tests y documentación.
El comportamiento del CLI no cambia: mismos flags, misma salida.
Estructura
- claude_sesiones/{sessions,terminal,webpage,cli}.py separa parseo,
presentación, generación del HTML y argumentos. El template pasa a ser
data del paquete.
- pyproject.toml con el entry point claude-sesiones, sin dependencias.
- build.sh se disuelve en `claude-sesiones --html [ARCHIVO]`: el HTML se
arma en proceso, sin subprocess ni data.json intermedio. `--open` lo
abre en el navegador.
Arreglos
- El template no tenía doctype ni <meta charset>: abierto como file://
quedaba en quirks mode y con la codificación del sistema, así que los
acentos salían rotos. Tampoco tenía viewport, con lo que en el celular
se veía a escala de escritorio.
- `delete_sessions()` se llamaba con un argumento de menos (lo encontró
el test de borrado).
- El orden de la tabla comparaba timestamps como strings; ahora compara
los datetimes ya parseados.
- `mark()` buscaba el texto crudo dentro del HTML ya escapado, así que
resaltar algo con & o < nunca encontraba nada. `esc()` no escapaba la
comilla simple.
- El caché no tenía versión: al cambiar el esquema del registro se leían
registros de la forma anterior. Ahora se invalida solo. El temporal
lleva el pid, para que dos corridas simultáneas no se pisen.
- `pick()` cortaba el proceso con sys.exit desde adentro; ahora levanta
SessionError y el código de salida lo decide la CLI.
Mejoras
- Respeta CLAUDE_CONFIG_DIR.
- La página tiene botón de tema claro/oscuro que recuerda la elección,
deep links (#uuid abre esa conversación), atajos de teclado, trampa de
foco en el lector y aviso si el JS está apagado. Las tres copias de la
paleta quedaron en dos, una por tema.
- 92 tests con unittest, sin dependencias, contra árboles de .jsonl
falsos: nunca tocan ~/.claude. CI en GitHub Actions, Python 3.9 a 3.13.
- README en inglés y español, con el esquema del JSON y una advertencia
sobre lo que hay adentro de sesiones.html.
- Apache-2.0.
Claude-Session: https://claude.ai/code/session_01RmtZ9qBemrc9TncwVTG6ED
| -rw-r--r-- | .github/workflows/ci.yml | 36 | ||||
| -rw-r--r-- | LICENSE | 201 | ||||
| -rw-r--r-- | NOTICE | 5 | ||||
| -rw-r--r-- | README.es.md | 199 | ||||
| -rw-r--r-- | README.md | 203 | ||||
| -rwxr-xr-x | build.sh | 47 | ||||
| -rwxr-xr-x | claude-sesiones | 775 | ||||
| -rw-r--r-- | claude_sesiones/__init__.py | 8 | ||||
| -rw-r--r-- | claude_sesiones/__main__.py | 6 | ||||
| -rw-r--r-- | claude_sesiones/cli.py | 312 | ||||
| -rw-r--r-- | claude_sesiones/sessions.py | 409 | ||||
| -rw-r--r-- | claude_sesiones/template.html (renamed from template.html) | 165 | ||||
| -rw-r--r-- | claude_sesiones/terminal.py | 251 | ||||
| -rw-r--r-- | claude_sesiones/webpage.py | 64 | ||||
| -rw-r--r-- | pyproject.toml | 41 | ||||
| -rw-r--r-- | tests/__init__.py | 0 | ||||
| -rw-r--r-- | tests/fixtures.py | 70 | ||||
| -rw-r--r-- | tests/test_cli.py | 211 | ||||
| -rw-r--r-- | tests/test_sessions.py | 307 | ||||
| -rw-r--r-- | tests/test_terminal.py | 142 | ||||
| -rw-r--r-- | tests/test_webpage.py | 92 |
21 files changed, 2675 insertions, 869 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cbd02c6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python: ["3.9", "3.10", "3.11", "3.12", "3.13"] + include: + - os: macos-latest + python: "3.13" + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + # Sin dependencias a propósito: si algún día hace falta instalar algo + # para correr los tests, es que el proyecto dejó de ser stdlib-only. + - name: Tests + run: python -m unittest discover -s tests -t . -v + + - name: Se instala y el comando arranca + run: | + pip install . + claude-sesiones --version + claude-sesiones --help @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. @@ -0,0 +1,5 @@ +claude-sesiones +Copyright 2025 Elvis Claros Castro + +This product includes software developed by Elvis Claros Castro. +Licensed under the Apache License, Version 2.0 (see LICENSE). diff --git a/README.es.md b/README.es.md new file mode 100644 index 0000000..09003b6 --- /dev/null +++ b/README.es.md @@ -0,0 +1,199 @@ +# claude-sesiones + +Explorá todas las conversaciones de [Claude Code](https://claude.com/claude-code) +que tenés guardadas en la máquina: como tabla en la terminal, o como una página +HTML autocontenida que se abre con doble clic. + +Español · **[English](README.md)** · Sin dependencias, solo biblioteca estándar. + +``` + # SESIÓN RUTA FECHA CUÁNDO MSG DUR ID + 1 | Migrar el pool de conexiones a … /home/ana/api 16 ago hoy 4 9m 5d10f1ee + 2 | Timeouts intermitentes en el he… /home/ana/api 16 ago hoy 2 3m 0f60f37a + 3 | Reescribir el buscador con Fuse… /home/ana/web 15 ago ayer 7 18m b69c1fc2 + 4 | por qué tarda tanto npm ci /home/ana/web 13 ago hace 3d 1 <1m d4d2a5be + 5 | sesión abierta sin mens… [vacía] /home/ana/infra 08 ago hace 1sem — <1m e0a4300e + +5 sesiones · 3 proyectos · -s <nº> para leer una +``` + +## ⚠️ Tus transcripciones son privadas + +`--json` y `--html` escriben **el texto completo de tus conversaciones**: +prompts, respuestas, rutas de archivos, nombres de ramas. El `sesiones.html` que +sale es una copia legible de todo lo que escribiste alguna vez en Claude Code. + +No lo commitees, no lo subas, no lo pegues en un issue. El `.gitignore` del repo +ya excluye `sesiones.html` y `data.json`, pero el archivo lo cuidás vos. + +## Instalación + +Necesita Python 3.9 o más nuevo. Nada más. + +```bash +pipx install git+https://github.com/ElvisClaros/claude-sesiones +``` + +O con pip, o directamente desde un clon: + +```bash +pip install git+https://github.com/ElvisClaros/claude-sesiones + +git clone https://github.com/ElvisClaros/claude-sesiones && cd claude-sesiones +python3 -m claude_sesiones # sin instalar nada +``` + +## Uso + +```bash +claude-sesiones # tabla de todas las sesiones +claude-sesiones docker # filtra por título, ruta o rama +claude-sesiones -s 3 # lee el chat nº 3 de la tabla +claude-sesiones -s 5d10f1ee # lo mismo, por prefijo de UUID +claude-sesiones -g "port already" # busca dentro de las conversaciones +claude-sesiones -r 3 # imprime el comando para reanudarla +eval "$(claude-sesiones -r 3)" # …o la reanuda directamente +claude-sesiones --html --open # genera sesiones.html y lo abre +``` + +El número es la posición de la fila **en la tabla que estás viendo**, así que si +filtraste hay que repetir el filtro para leer esa fila: + +```bash +claude-sesiones docker # muestra 3 resultados +claude-sesiones docker -s 2 # lee el 2º de esos tres +``` + +### Opciones + +| Flag | Qué hace | +| --- | --- | +| `-s`, `--show REF` | Muestra un chat (índice de la tabla o prefijo de UUID). | +| `-r`, `--resume REF` | Imprime `cd <proyecto> && claude --resume <uuid>`. | +| `-g`, `--grep TEXTO` | Deja las sesiones cuya transcripción contenga `TEXTO`. | +| `-p`, `--project RUTA` | Deja las sesiones cuya ruta de proyecto contenga `RUTA`. | +| `-n`, `--limit N` | Solo las N más recientes. | +| `-E`, `--hide-empty` | Oculta las sesiones sin mensajes. | +| `--no-tools` | En el chat, oculta las llamadas a herramientas. | +| `--no-pager` | No manda el chat a `$PAGER`. | +| `--no-color` | Salida sin color (también respeta `NO_COLOR`). | +| `--json` | Vuelca todas las sesiones en JSON por stdout. | +| `--html [ARCHIVO]` | Genera la página autocontenida (por defecto `sesiones.html`). | +| `--template ARCHIVO` | Usa tu propio template para `--html`. | +| `--open` | Abre en el navegador lo que haya generado `--html`. | +| `--no-cache` | Ignora el caché y re-parsea todo. | + +### Borrar sesiones + +Es irreversible y pregunta antes, salvo que pases `-y`: + +```bash +claude-sesiones --delete-empty --dry-run # qué borraría +claude-sesiones --delete-empty # borra las vacías +claude-sesiones -D 101 -D e0a4300e # borra sesiones puntuales +claude-sesiones -p /tmp --delete-empty # solo las vacías de ese proyecto +``` + +Avisa si alguno de los archivos se escribió en los últimos cinco minutos: es muy +probable que sea una sesión que Claude Code todavía tiene abierta, y que la +vuelva a escribir al cerrarse. + +## La página HTML + +`claude-sesiones --html` genera un único archivo con los datos adentro. Sin +servidor, sin red, sin paso de build: lo copiás a otra máquina y sigue andando. + +- Búsqueda por título, ruta, rama o UUID, y opcionalmente dentro de las + transcripciones, mostrando el fragmento que coincide debajo de la fila. +- Filtro por proyecto, orden por cualquier columna, ocultar las vacías. +- Clic en una fila para leer la conversación en un panel lateral, con cercas de + código, títulos y una línea por herramienta usada. +- Botón para copiar el `cd … && claude --resume …` de cualquier sesión. +- Tema claro y oscuro, con un botón que recuerda cuál elegiste. +- Cada sesión tiene su propio fragmento de URL: `sesiones.html#5d10f1ee-…` abre + esa conversación directamente. +- Teclado: `/` o `Ctrl`+`K` enfoca el buscador, `Esc` lo limpia o cierra el lector. + +Las fechas son relativas a **cuándo se leyeron los datos**, no a tu reloj, así +que "hoy" sigue queriendo decir lo que quería decir cuando generaste la página. + +## Cómo funciona + +Claude Code guarda una conversación por archivo, en formato JSON Lines: + +``` +~/.claude/projects/<ruta-del-proyecto-codificada>/<uuid>.jsonl +``` + +(Si moviste ese directorio, respeta `CLAUDE_CONFIG_DIR`.) + +Cada línea es un evento. `claude-sesiones` los recorre y se queda con la +conversación: tus mensajes, las respuestas de Claude, y una línea por +herramienta usada, del estilo `Bash: git status`. A propósito **descarta lo que +devolvieron las herramientas**: son el 95 % de los bytes en disco y casi nada +del sentido. + +Algunos detalles que conviene saber: + +- **Títulos.** Claude genera uno durante la sesión (eventos `ai-title`); gana el + más reciente. Cuando falta, se usa lo primero que escribiste vos — y ahí se + nota, porque arranca en minúscula o suena a pregunta suelta. +- **Sesiones vacías** son las que se abrieron pero nunca recibieron un mensaje: + un `/resume` cancelado, un `/login`. +- **No interactivas** son `claude -p` con algo piped por stdin — típicamente un + `git diff` para redactar el mensaje de commit. Se detectan como un único + mensaje larguísimo sin ninguna ida y vuelta. +- **Rutas inferidas.** Un `/resume` cancelado nunca registra `cwd`, y el nombre + del directorio no se puede invertir de forma fiable (porque `/` y `.` se + codifican los dos como `-`), así que la ruta se toma prestada de otra sesión + del mismo proyecto y queda marcada. +- **Sidechains** (transcripciones de subagentes) se saltean. +- **Caché.** Lo parseado se guarda en + `$XDG_CACHE_HOME/claude-sesiones/cache.json`, indexado por tamaño y mtime. Es + solo una optimización: si falta, quedó viejo o está roto, se re-parsea todo. + `--no-cache` lo saltea por completo. + +### Esquema del JSON + +`--json` imprime un arreglo, de la sesión más recientemente activa a la más +vieja. Las claves son de una letra porque esos mismos registros van embebidos en +el HTML, donde el costo se paga una vez por sesión: + +| Clave | Qué es | +| --- | --- | +| `id` | UUID de la sesión (el nombre del archivo). | +| `p` | Ruta del proyecto (`cwd`). | +| `b` | Rama de git. | +| `t` | Título. | +| `ai` | `true` si el título lo generó Claude. | +| `n` | `true` si parece un `claude -p` no interactivo. | +| `e` | `true` si la sesión no tiene mensajes. | +| `i` | `true` si `p` se dedujo de otra sesión del mismo proyecto. | +| `f` / `l` | Timestamp del primer y del último evento (ISO 8601). | +| `d` | Duración en minutos. | +| `u` / `a` | Cantidad de mensajes tuyos / de Claude. | +| `k` | Tamaño del archivo en KB. | +| `v` | Versión de Claude Code. | +| `c` | Transcripción: `[{"r": "u"|"a"|"t", "x": texto}]`. | + +## Desarrollo + +```bash +git clone https://github.com/ElvisClaros/claude-sesiones && cd claude-sesiones +python3 -m unittest discover -s tests -t . +``` + +Los tests arman árboles de `.jsonl` falsos en un directorio temporal y nunca +tocan `~/.claude`. No hay nada que instalar: ni runner de tests ni dependencias. + +| Módulo | De qué se ocupa | +| --- | --- | +| `claude_sesiones/sessions.py` | Parsear los `.jsonl`, el caché, los filtros. | +| `claude_sesiones/terminal.py` | Colores ANSI, la tabla, imprimir un chat. | +| `claude_sesiones/webpage.py` | Meter los datos adentro del template. | +| `claude_sesiones/cli.py` | Los argumentos y los comandos. | +| `claude_sesiones/template.html` | La página: marcado, estilos y el código del navegador. | + +## Licencia + +[Apache-2.0](LICENSE). diff --git a/README.md b/README.md new file mode 100644 index 0000000..4252071 --- /dev/null +++ b/README.md @@ -0,0 +1,203 @@ +# claude-sesiones + +Browse every [Claude Code](https://claude.com/claude-code) conversation stored on +your machine — as a table in your terminal, or as a single self-contained HTML +page you open with a double click. + +**[Español](README.es.md)** · English · No dependencies, standard library only. + +> **The CLI, its output and its help text are in Spanish.** Only this README is +> translated. The command reads local files and never sends anything anywhere. + +``` + # SESIÓN RUTA FECHA CUÁNDO MSG DUR ID + 1 | Migrar el pool de conexiones a … /home/ana/api 16 ago hoy 4 9m 5d10f1ee + 2 | Timeouts intermitentes en el he… /home/ana/api 16 ago hoy 2 3m 0f60f37a + 3 | Reescribir el buscador con Fuse… /home/ana/web 15 ago ayer 7 18m b69c1fc2 + 4 | por qué tarda tanto npm ci /home/ana/web 13 ago hace 3d 1 <1m d4d2a5be + 5 | sesión abierta sin mens… [vacía] /home/ana/infra 08 ago hace 1sem — <1m e0a4300e + +5 sesiones · 3 proyectos · -s <nº> para leer una +``` + +## ⚠️ Your transcripts are private + +`--json` and `--html` write out **the full text of your conversations**: prompts, +answers, file paths, branch names. The generated `sesiones.html` is a complete, +readable copy of everything you ever typed into Claude Code. + +Do not commit it, do not upload it, do not paste it into a bug report. The +repository's `.gitignore` already excludes `sesiones.html` and `data.json`, but +the file itself is yours to look after. + +## Install + +Requires Python 3.9 or newer. Nothing else. + +```bash +pipx install git+https://github.com/ElvisClaros/claude-sesiones +``` + +Or with pip, or straight from a clone: + +```bash +pip install git+https://github.com/ElvisClaros/claude-sesiones + +git clone https://github.com/ElvisClaros/claude-sesiones && cd claude-sesiones +python3 -m claude_sesiones # no install needed +``` + +## Usage + +```bash +claude-sesiones # table of every session +claude-sesiones docker # filter by title, path or branch +claude-sesiones -s 3 # read conversation #3 from the table +claude-sesiones -s 5d10f1ee # same, by UUID prefix +claude-sesiones -g "port already" # search inside the conversations +claude-sesiones -r 3 # print the command that resumes it +eval "$(claude-sesiones -r 3)" # …or resume it right away +claude-sesiones --html --open # build sesiones.html and open it +``` + +The number is the row's position **in the table you are looking at**, so if you +filtered, repeat the filter to read that row: + +```bash +claude-sesiones docker # shows 3 results +claude-sesiones docker -s 2 # reads the 2nd of those three +``` + +### Options + +| Flag | What it does | +| --- | --- | +| `-s`, `--show REF` | Print a conversation (table index or UUID prefix). | +| `-r`, `--resume REF` | Print `cd <project> && claude --resume <uuid>`. | +| `-g`, `--grep TEXT` | Keep sessions whose transcript contains `TEXT`. | +| `-p`, `--project PATH` | Keep sessions whose project path contains `PATH`. | +| `-n`, `--limit N` | Only the N most recent. | +| `-E`, `--hide-empty` | Hide sessions with no messages. | +| `--no-tools` | Hide tool calls when printing a conversation. | +| `--no-pager` | Do not pipe the conversation through `$PAGER`. | +| `--no-color` | Plain output (`NO_COLOR` is honoured too). | +| `--json` | Dump every session as JSON on stdout. | +| `--html [FILE]` | Build the standalone page (default `sesiones.html`). | +| `--template FILE` | Use your own template for `--html`. | +| `--open` | Open whatever `--html` produced in your browser. | +| `--no-cache` | Ignore the cache and re-parse everything. | + +### Deleting sessions + +Irreversible, and it asks first unless you pass `-y`: + +```bash +claude-sesiones --delete-empty --dry-run # what it would delete +claude-sesiones --delete-empty # delete the empty ones +claude-sesiones -D 101 -D e0a4300e # delete specific sessions +claude-sesiones -p /tmp --delete-empty # only the empty ones of that project +``` + +It warns you about any file written in the last five minutes: that is very +likely a session Claude Code still has open, and it will write it back on exit. + +## The HTML page + +`claude-sesiones --html` produces one file with the data embedded inside it. No +server, no network, no build step — copy it to another machine and it still +works. + +- Search by title, path, branch or UUID, and optionally inside the transcripts, + with the matching snippet shown under the row. +- Filter by project, sort by any column, hide empty sessions. +- Click a row to read the conversation in a side panel, with code fences, + headings and one line per tool call. +- Copy the `cd … && claude --resume …` command for any session. +- Light and dark themes, with a toggle that remembers your choice. +- Each session gets its own URL fragment, so `sesiones.html#5d10f1ee-…` opens + that conversation directly. +- Keyboard: `/` or `Ctrl`+`K` focuses the search box, `Esc` clears it or closes + the reader. + +Dates are relative to **when the data was read**, not to your clock, so "today" +keeps meaning what it meant when you generated the page. + +## How it works + +Claude Code writes one JSON Lines file per conversation: + +``` +~/.claude/projects/<url-encoded-project-path>/<uuid>.jsonl +``` + +(`CLAUDE_CONFIG_DIR` is honoured if you moved that directory.) + +Every line is an event. `claude-sesiones` walks them and keeps the conversation +itself — your messages, Claude's replies, and a one-line summary per tool call +such as `Bash: git status`. It deliberately **drops tool results**, which are +about 95% of the bytes on disk and almost none of the meaning. + +A few details worth knowing: + +- **Titles.** Claude generates one during the session (`ai-title` events); the + most recent wins. Without one, the first thing you typed is used instead — + which is visible, because it starts in lowercase or reads like a loose + question. +- **Empty sessions** were opened but never received a message: a cancelled + `/resume`, a `/login`. +- **Non-interactive** sessions are `claude -p` with something piped into stdin — + typically a `git diff` to write a commit message. They are detected as a + single very long message with no back and forth. +- **Inferred paths.** A cancelled `/resume` never records a `cwd`, and the + directory name cannot be reversed reliably (both `/` and `.` encode as `-`), + so the path is borrowed from another session of the same project and flagged. +- **Sidechains** (subagent transcripts) are skipped. +- **Cache.** Parsed sessions are cached in + `$XDG_CACHE_HOME/claude-sesiones/cache.json`, keyed by size and mtime. It is + only an optimisation: if it is missing, stale or corrupt, everything is + re-parsed. `--no-cache` skips it entirely. + +### JSON schema + +`--json` prints an array, most recently active first. Keys are one letter +because the same records are embedded in the HTML, where the cost is paid once +per session: + +| Key | Meaning | +| --- | --- | +| `id` | Session UUID (the file name). | +| `p` | Project path (`cwd`). | +| `b` | Git branch. | +| `t` | Title. | +| `ai` | `true` if Claude generated the title. | +| `n` | `true` if it looks like a non-interactive `claude -p`. | +| `e` | `true` if the session has no messages. | +| `i` | `true` if `p` was inferred from a sibling session. | +| `f` / `l` | First and last event timestamps (ISO 8601). | +| `d` | Duration in minutes. | +| `u` / `a` | Message counts, yours / Claude's. | +| `k` | File size in KB. | +| `v` | Claude Code version. | +| `c` | Transcript: `[{"r": "u"|"a"|"t", "x": text}]`. | + +## Development + +```bash +git clone https://github.com/ElvisClaros/claude-sesiones && cd claude-sesiones +python3 -m unittest discover -s tests -t . +``` + +The tests build fake `.jsonl` trees in a temporary directory and never touch +`~/.claude`. There is nothing to install: no test runner, no dependencies. + +| Module | Responsibility | +| --- | --- | +| `claude_sesiones/sessions.py` | Parsing the `.jsonl` files, the cache, filters. | +| `claude_sesiones/terminal.py` | ANSI colours, the table, printing a conversation. | +| `claude_sesiones/webpage.py` | Embedding the data into the template. | +| `claude_sesiones/cli.py` | Argument parsing and the commands. | +| `claude_sesiones/template.html` | The page: markup, styles and the browser-side code. | + +## License + +[Apache-2.0](LICENSE). diff --git a/build.sh b/build.sh deleted file mode 100755 index 264c529..0000000 --- a/build.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash -# Genera sesiones.html a partir de los logs en ~/.claude/projects/ -# -# ./build.sh genera sesiones.html -# ./build.sh --open genera y abre en el navegador -# -# El HTML que sale es autocontenido: los datos y las transcripciones van -# embebidos adentro, no necesita red ni servidor. Se abre con doble clic. - -set -euo pipefail - -cd "$(dirname "$0")" - -# El CLI es la única implementación del parseo; el HTML consume su --json. -./claude-sesiones --json > data.json - -python3 - <<'PY' -import json - -template = open("template.html").read() -raw = open("data.json").read() - -# El payload vive dentro de un <script type="application/json">, que el parser -# de HTML corta en el primer "</script". Escapamos "</" como "<\/" — es un -# escape válido de JSON, así que JSON.parse lo devuelve intacto. -payload = raw.replace("</", "<\\/") - -# Ojo: no se puede verificar buscando __DATA__ en la salida. Estas mismas -# sesiones incluyen conversaciones sobre este script, así que el payload -# contiene el marcador como texto. Validamos el template antes de sustituir. -if template.count("__DATA__") != 1: - raise SystemExit("error: el template debe tener exactamente un __DATA__") - -html = template.replace("__DATA__", payload) - |