Rayane Hassani Explore the gameThe game
FR EN
Projects

Memorium

2 sto export 68 sessions; published on PyPI, installable with pipx

Zero dependencies, checked in CI on six Python versions, from 3.8 to 3.13.

ExportArchive and notesUserMemorium01Claude Code logsJSONL, nevermodified02Exportone Python file,no dependencies03Static sitetranscripts, search anddashboard, offline04Archivea hook archives everysession as incrementalgzip05Restoreputs a session back,even after a purge06Annotationsrenames and notes in aderived layer, writtenover localhost
One session before and after Memorium

Claude Code's JSONL log

{"type": "user", "message": {"content": "GET /sessions/{token} got slow since the table passed a million rows. Find out why."}, "timestamp": "2026-09-02T15:32:35.000Z", "cwd": "/home/dev/acme-api", "sessionId": "f6b5c6d7-6666-4f70-a182-0000000000f6"}
{"type": "assistant", "message": {"content": [{"type": "text", "text": "Asking Postgres for the actual plan rather than guessing at the cause."}, {"type": "tool_use", "id": "t1", "name": "Bash", "input": {"command": "psql $DATABASE_URL -c \"EXPLAIN ANALYZE SELECT * FROM sessions WHERE token = 'x'\"", "description": "Explain the slow query"}}]}, "timestamp": "2026-09-02T15:33:00.000Z", "cwd": "/home/dev/acme-api", "sessionId": "f6b5c6d7-6666-4f70-a182-0000000000f6"}
{"type": "user", "message": {"content": [{"type": "tool_result", "tool_use_id": "t1", "content": "Seq Scan on sessions  (cost=0.00..48211.00 rows=1 width=96) (actual time=1794.2..1794.2 rows=0 loops=1)\n  Filter: (token = 'x'::text)\n  Rows Removed by Filter: 1204331\nExecution Time: 1794.6 ms", "is_error": false}]}, "timestamp": "2026-09-02T15:33:12.000Z", "cwd": "/home/dev/acme-api", "sessionId": "f6b5c6d7-6666-4f70-a182-0000000000f6"}

The same session in Memorium

A session history unusable in practice

Claude Code records every work session in JSONL files meant for machines. The history therefore exists, but it is unusable in practice: you can neither reread the reasoning behind a choice, nor find a past command, nor annotate a design decision. On top of that, the tool deletes by default any session left untouched for thirty days, so that reasoning eventually disappears.

Since I now use Claude Code a lot, I needed to format its output and keep track of the sessions where a trade-off was weighed, a benchmark settled or a dead end already explored. The commit says what changed, and I wanted a tool that could show why.

A single Python file with no dependencies

Memorium fits in a single Python file of 2,479 lines with no dependency beyond the standard library, bringing together the command line, the HTML generator, the search index and the viewer. The main command reads Claude Code's logs, generates a static site and opens it in the browser. Each session becomes a transcript laid out for long-form reading, with prompts, answers, tool calls and terminal output, and a dashboard shows the week's activity and the latest sessions of each project.

A work session read back in Memorium, commands included.

Full-text search relies on an index built once at export time and embedded in the site, which makes it instant without a server. Fonts are embedded in the page and nothing is loaded from the network, so the journal opens anywhere, even offline.

A second part protects the history itself. An init command raises Claude Code's purge delay after backing up its configuration, then installs a hook that archives each session as soon as it ends. Raw logs are compressed incrementally, and a restore command puts a session back in place so it can be resumed, including on another machine or after it was purged.

A source never modified and four ADRs

I made the absence of dependencies a design constraint, since a single standard-library file can be audited in one sitting, installs anywhere and cannot break with updates to a third-party library. This constraint shaped the rest of the project, starting with the absence of a backend.

The log folder belongs to Claude Code, which indexes it to resume sessions. Renaming a folder or moving a file there would have broken resuming, so I set a rule never to modify the source. Renames, moves and annotations live in a derived metadata layer, resolved at display time and fully reversible, on the model of a database view applied to flat files.

Writing that metadata from the browser required the File System Access API, which is disabled on a page opened from disk. A subcommand therefore serves the site on localhost, which counts as a secure context, with a plain static server and no business logic. The site stays readable as is, and the server only steps in for writes.

I recorded these decisions in four ADRs, each with the condition that would justify revisiting it. Writing them had an effect I did not expect: while rereading the code to explain each choice, I found two real bugs, which I fixed and documented.

Published on PyPI after a full repository review

Before making the tool public, I reviewed the repository end to end, which surfaced a command-line bug in which the help option created a folder named after itself. A version number published on PyPI can never be reused, so I fixed the bug before tagging the release, then validated the package on TestPyPI and checked its installation from the remote index in a clean environment.

Version 1.0.0 is published as memorium-cli, with an annotated tag, a release containing the wheel and the source distribution, a changelog and a README with a demo video. The publishing token was never stored in the repository and was scoped to this project alone after the first upload.

Installable with pipx and tests as the next step

A single pipx install memorium-cli is enough for anyone to install the tool. On my own history of several months, Memorium exports 68 sessions in two seconds, the whole application weighs 243 KB including fonts, and the archive compresses 197 MB of raw logs down to 62 MB. On every push, the CI runs Ruff linting, a compile step and a smoke test on Python 3.8 to 3.13.

Test coverage is the current limit of the project, which is why linting is deliberately restricted to rules that do not change the code's behaviour. Expanding those tests is the next step before widening the lint rules.