Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 10 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ Search, inspect, chat with, and resume your local Claude Code and Codex history

[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-3776AB.svg)](https://python.org)
[![Tests](https://img.shields.io/badge/tests-331%20passing-34D058.svg)](#development)
[![Local first](https://img.shields.io/badge/local--first-no%20API%20keys-34D058.svg)](#privacy)
[![Providers](https://img.shields.io/badge/providers-Claude%20Code%20%2B%20Codex-8B5CF6.svg)](#providers)

Expand All @@ -32,7 +31,7 @@ Coding agents leave behind useful local transcripts, but built-in resume pickers
- "the Docker healthcheck that broke deploys"
- "the branch where the OAuth callback tests were added"

**code-recall turns those transcripts into a searchable memory layer.** It indexes Claude Code and Codex sessions, ranks results with keyword search, semantic search, reranking, and graph signals, then lets you jump back into the right project with the right provider command.
code-recall indexes Claude Code and Codex sessions, ranks results with keyword search, semantic search, reranking, and graph signals, then lets you jump back into the right project with the right provider command.

## What It Does

Expand All @@ -46,19 +45,15 @@ Coding agents leave behind useful local transcripts, but built-in resume pickers

## Screenshots

All README media is generated from synthetic demo data using [scripts/generate_demo_assets.py](scripts/generate_demo_assets.py). It does not come from a real work index.
Generated from synthetic demo data via [scripts/generate_demo_assets.py](scripts/generate_demo_assets.py)not from a real index.

| Search across providers | Understand why it matched |
| Search across providers | Why this matched |
|---|---|
| <img src="docs/assets/code-recall-search.svg" alt="Search results with Claude Code and Codex provider badges" width="440"> | <img src="docs/assets/code-recall-why.svg" alt="Why tab showing matched evidence for a result" width="440"> |
| <img src="docs/assets/code-recall-search.svg" alt="Search results across Claude Code and Codex" width="440"> | <img src="docs/assets/code-recall-why.svg" alt="Why panel showing matched evidence" width="440"> |

| Inspect activity | Find related sessions |
|---|---|
| <img src="docs/assets/code-recall-activity.svg" alt="Activity tab showing files touched and commands run" width="440"> | <img src="docs/assets/code-recall-related.svg" alt="Related tab showing sessions connected through shared files" width="440"> |

| Chat with a transcript |
|---|
| <img src="docs/assets/code-recall-ai-chat.svg" alt="AI transcript chat for a selected coding-agent session" width="880"> |
<p align="center">
<img src="docs/assets/code-recall-ai-chat.svg" alt="Chat with a selected transcript" width="880">
</p>

## Install

Expand Down Expand Up @@ -195,17 +190,16 @@ code-recall keeps the index fresh in two ways:
- Every search/TUI startup runs a quick incremental index over Claude Code and Codex sources before searching.
- On first run or `code-recall install-hooks`, code-recall installs a Claude Code `SessionEnd` hook that runs `code-recall index --quiet` when Claude Code sessions end.

Codex sessions are indexed on the next incremental index/search run. There is no Codex session-end hook installed today because Codex does not expose the same `SessionEnd` hook surface in this repo's integration path.
Codex doesn't currently expose a session-end hook, so Codex sessions are picked up on the next incremental index or search.

## Privacy

code-recall is designed for local developer recall:
code-recall runs locally:

- The index is a local SQLite database under your app data directory.
- Keyword and graph search do not require network calls.
- Semantic search uses local embeddings.
- AI investigation and transcript chat are explicit actions, not automatic background calls.
- README screenshots are synthetic and reproducible from [scripts/generate_demo_assets.py](scripts/generate_demo_assets.py).

## Architecture

Expand Down Expand Up @@ -300,27 +294,7 @@ uv run pytest tests/test_search_quality.py -v

## Release

Releases are tag driven. CI verifies the version declarations match before publishing. The release workflow builds distributions, publishes to PyPI through Trusted Publishing, and creates or updates the matching GitHub release.

Trusted Publisher fields:

| Field | Value |
|-------|-------|
| PyPI project name | `code-recall` |
| Owner | `lupuletic` |
| Repository name | `code-recall` |
| Workflow name | `release.yml` |
| Environment name | `pypi` |

```bash
uv run python scripts/check_version.py
uv run pytest -q
uv run python -m compileall -q src tests
uv build --no-sources

git tag v0.2.2
git push origin main --tags
```
Release workflow and Trusted Publisher details: [docs/RELEASE.md](docs/RELEASE.md).

## License

Expand Down
25 changes: 25 additions & 0 deletions docs/RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Releasing code-recall

Releases are tag driven. CI verifies the version declarations match before publishing. The release workflow builds distributions, publishes to PyPI through Trusted Publishing, and creates or updates the matching GitHub release.

## Trusted Publisher fields

| Field | Value |
|-------|-------|
| PyPI project name | `code-recall` |
| Owner | `lupuletic` |
| Repository name | `code-recall` |
| Workflow name | `release.yml` |
| Environment name | `pypi` |

## Cutting a release

```bash
uv run python scripts/check_version.py
uv run pytest -q
uv run python -m compileall -q src tests
uv build --no-sources

git tag v0.2.2
git push origin main --tags
```
228 changes: 114 additions & 114 deletions docs/assets/code-recall-activity.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
232 changes: 116 additions & 116 deletions docs/assets/code-recall-ai-chat.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/code-recall-demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
226 changes: 113 additions & 113 deletions docs/assets/code-recall-related.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
228 changes: 114 additions & 114 deletions docs/assets/code-recall-search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
228 changes: 114 additions & 114 deletions docs/assets/code-recall-why.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 42 additions & 1 deletion scripts/generate_demo_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@

import argparse
import asyncio
import io
import json
import shutil
import subprocess
import tempfile
from dataclasses import dataclass
from pathlib import Path

from rich.console import Console
from rich.terminal_theme import SVG_EXPORT_THEME

from code_recall.db import (
build_session_chains,
get_connection,
Expand Down Expand Up @@ -337,7 +341,44 @@ async def _render_screenshot(
assistant_label="Claude Code",
)
await pilot.pause()
app.save_screenshot(filename=filename, path=str(asset_dir))
_export_svg_with_theme(app, asset_dir / filename)


# Remap from Rich's DEFAULT_TERMINAL_THEME palette (pure ANSI: harsh #00ffff
# cyan, #ff00ff magenta) to SVG_EXPORT_THEME palette (softer monokai-style)
# so README screenshots look like a real iTerm/Ghostty rendering. We post-
# process because Textual's compositor resolves ANSI->RGB before Rich's SVG
# theme parameter would take effect.
_ANSI_TO_SVG_THEME_MAP = {
"#000000": "#4b4e55", "#800000": "#cc555a", "#008000": "#98a84b",
"#808000": "#d0b344", "#000080": "#608ab1", "#800080": "#98729f",
"#008080": "#68a0b3", "#c0c0c0": "#c5c8c6", "#808080": "#9a9b99",
"#ff0000": "#ff2627", "#00ff00": "#00823d", "#ffff00": "#d08442",
"#0000ff": "#1984e9", "#ff00ff": "#ff2c7a", "#00ffff": "#398280",
"#ffffff": "#fdfdc5",
}


def _export_svg_with_theme(app: RecallApp, svg_path: Path) -> None:
width, height = app.size
console = Console(
width=width,
height=height,
file=io.StringIO(),
force_terminal=True,
color_system="truecolor",
record=True,
legacy_windows=False,
safe_box=False,
)
screen_render = app.screen._compositor.render_update(
full=True, screen_stack=app._background_screens, simplify=False,
)
console.print(screen_render)
svg = console.export_svg(title=app.title, theme=SVG_EXPORT_THEME)
for harsh, soft in _ANSI_TO_SVG_THEME_MAP.items():
svg = svg.replace(harsh, soft)
svg_path.write_text(svg, encoding="utf-8")


async def render_assets(asset_dir: Path) -> None:
Expand Down