aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml36
-rw-r--r--LICENSE201
-rw-r--r--NOTICE5
-rw-r--r--README.es.md199
-rw-r--r--README.md203
-rwxr-xr-xbuild.sh47
-rwxr-xr-xclaude-sesiones775
-rw-r--r--claude_sesiones/__init__.py8
-rw-r--r--claude_sesiones/__main__.py6
-rw-r--r--claude_sesiones/cli.py312
-rw-r--r--claude_sesiones/sessions.py409
-rw-r--r--claude_sesiones/template.html (renamed from template.html)165
-rw-r--r--claude_sesiones/terminal.py251
-rw-r--r--claude_sesiones/webpage.py64
-rw-r--r--pyproject.toml41
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/fixtures.py70
-rw-r--r--tests/test_cli.py211
-rw-r--r--tests/test_sessions.py307
-rw-r--r--tests/test_terminal.py142
-rw-r--r--tests/test_webpage.py92
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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..16bc7d3
--- /dev/null
+++ b/NOTICE
@@ -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)
-
-open("sesiones.html", "w").write(html)
-
-s = json.loads(raw)
-print(f"{len(s)} sesiones · "
- f"{len({x['p'] for x in s})} proyectos · "
- f"{sum(x['u'] for x in s)} mensajes · "
- f"{sum(len(x['c']) for x in s)} bloques de transcripción → sesiones.html")
-PY
-
-if [[ "${1:-}" == "--open" ]]; then
- xdg-open sesiones.html >/dev/null 2>&1 &
-fi
diff --git a/claude-sesiones b/claude-sesiones
deleted file mode 100755
index 4127a68..0000000
--- a/claude-sesiones
+++ /dev/null
@@ -1,775 +0,0 @@
-#!/usr/bin/env python3
-"""Explorador de sesiones de Claude Code para la terminal.
-
-Claude Code guarda una conversación por archivo en
-~/.claude/projects/<ruta-del-proyecto-codificada>/<uuid>.jsonl
-
- 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
- claude-sesiones --json vuelca todo en JSON (lo usa build.sh)
-
-Sin dependencias: solo la biblioteca estándar.
-"""
-
-import argparse
-import glob
-import json
-import os
-import re
-import shutil
-import subprocess
-import sys
-import textwrap</