fast-resume 1.12.8__py3-none-any.whl
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.
- fast_resume/__init__.py +5 -0
- fast_resume/adapters/__init__.py +25 -0
- fast_resume/adapters/base.py +263 -0
- fast_resume/adapters/claude.py +209 -0
- fast_resume/adapters/codex.py +216 -0
- fast_resume/adapters/copilot.py +176 -0
- fast_resume/adapters/copilot_vscode.py +326 -0
- fast_resume/adapters/crush.py +341 -0
- fast_resume/adapters/opencode.py +333 -0
- fast_resume/adapters/vibe.py +188 -0
- fast_resume/assets/claude.png +0 -0
- fast_resume/assets/codex.png +0 -0
- fast_resume/assets/copilot-cli.png +0 -0
- fast_resume/assets/copilot-vscode.png +0 -0
- fast_resume/assets/crush.png +0 -0
- fast_resume/assets/opencode.png +0 -0
- fast_resume/assets/vibe.png +0 -0
- fast_resume/cli.py +327 -0
- fast_resume/config.py +30 -0
- fast_resume/index.py +758 -0
- fast_resume/logging_config.py +57 -0
- fast_resume/query.py +264 -0
- fast_resume/search.py +281 -0
- fast_resume/tui/__init__.py +58 -0
- fast_resume/tui/app.py +629 -0
- fast_resume/tui/filter_bar.py +128 -0
- fast_resume/tui/modal.py +73 -0
- fast_resume/tui/preview.py +396 -0
- fast_resume/tui/query.py +86 -0
- fast_resume/tui/results_table.py +178 -0
- fast_resume/tui/search_input.py +117 -0
- fast_resume/tui/styles.py +302 -0
- fast_resume/tui/utils.py +160 -0
- fast_resume-1.12.8.dist-info/METADATA +545 -0
- fast_resume-1.12.8.dist-info/RECORD +38 -0
- fast_resume-1.12.8.dist-info/WHEEL +4 -0
- fast_resume-1.12.8.dist-info/entry_points.txt +3 -0
- fast_resume-1.12.8.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fast-resume
|
|
3
|
+
Version: 1.12.8
|
|
4
|
+
Summary: Fuzzy finder for coding agent session history
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.14
|
|
7
|
+
Requires-Dist: click>=8.0.0
|
|
8
|
+
Requires-Dist: humanize>=4.14.0
|
|
9
|
+
Requires-Dist: orjson>=3.10.0
|
|
10
|
+
Requires-Dist: rich>=13.0.0
|
|
11
|
+
Requires-Dist: tantivy>=0.25.0
|
|
12
|
+
Requires-Dist: textual-image>=0.8.4
|
|
13
|
+
Requires-Dist: textual>=0.47.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<img src="assets/logo.png" alt="fast-resume" width="120" height="120">
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
# fast-resume
|
|
21
|
+
|
|
22
|
+
[](https://pypi.org/project/fast-resume/)
|
|
23
|
+
[](https://pypi.org/project/fast-resume/)
|
|
24
|
+
|
|
25
|
+
Search and resume conversations across Claude Code, Codex, and more, all from a single place.
|
|
26
|
+
|
|
27
|
+
## Why fast-resume?
|
|
28
|
+
|
|
29
|
+
Coding agents are really good right now, so I'm using a bunch of them. Sometimes I remember I, or the LLM, mentioned something specific in a previous session, and I want to go back to it.
|
|
30
|
+
|
|
31
|
+
The problem is that currently, agents do have a resume feature, but either they don't support searching, or the search is very basic (e.g., title only).
|
|
32
|
+
|
|
33
|
+
That's why I built `fast-resume`: a command-line tool that aggregates all your coding agent sessions into a single searchable index, so you can quickly find and resume any session.
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **Unified Search**: One search box to find sessions across all your coding agents
|
|
40
|
+
- **Full-Text Search**: Search not just titles, but the entire conversation content (user messages and assistant responses)
|
|
41
|
+
- **Very fast**: Built on the Rust-powered Tantivy search engine for blazing-fast indexing and searching
|
|
42
|
+
- **Fuzzy Matching**: Typo-tolerant search with smart ranking (exact matches boosted)
|
|
43
|
+
- **Direct Resume**: Select, Enter, you're back in your session
|
|
44
|
+
- **Beautiful TUI**: fzf-style interface with agent icons, color-coded results, and live preview
|
|
45
|
+
- **Update Notifications**: Get notified when a new version is available
|
|
46
|
+
|
|
47
|
+
## Supported Agents
|
|
48
|
+
|
|
49
|
+
| Agent | Data Location | Resume Command |
|
|
50
|
+
| ------------------- | --------------------------------------------- | ------------------------------- |
|
|
51
|
+
| **Claude Code** | `~/.claude/projects/` | `claude --resume <id>` |
|
|
52
|
+
| **Codex CLI** | `~/.codex/sessions/` | `codex resume <id>` |
|
|
53
|
+
| **Copilot CLI** | `~/.copilot/session-state/` | `copilot --resume <id>` |
|
|
54
|
+
| **VS Code Copilot** | `~/Library/Application Support/Code/` (macOS) | `code <directory>` |
|
|
55
|
+
| **Crush** | `~/.local/share/crush/projects.json` | _(interactive only)_ |
|
|
56
|
+
| **OpenCode** | `~/.local/share/opencode/storage/` | `opencode <dir> --session <id>` |
|
|
57
|
+
| **Vibe** | `~/.vibe/logs/session/` | `vibe --resume <id>` |
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Run directly (no install needed)
|
|
63
|
+
uvx --from fast-resume fr
|
|
64
|
+
|
|
65
|
+
# Or install permanently
|
|
66
|
+
uv tool install fast-resume
|
|
67
|
+
fr
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
### Interactive TUI
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Open the TUI with all sessions
|
|
76
|
+
fr
|
|
77
|
+
|
|
78
|
+
# Pre-filter search query
|
|
79
|
+
fr "authentication bug"
|
|
80
|
+
|
|
81
|
+
# Filter by agent
|
|
82
|
+
fr -a claude
|
|
83
|
+
fr -a codex
|
|
84
|
+
|
|
85
|
+
# Filter by directory
|
|
86
|
+
fr -d myproject
|
|
87
|
+
|
|
88
|
+
# Combine filters
|
|
89
|
+
fr -a claude -d backend "api error"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Keyword Search Syntax
|
|
93
|
+
|
|
94
|
+
Filter directly in the search box using keywords:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
agent:claude # Filter by agent
|
|
98
|
+
agent:claude,codex # Multiple agents (OR)
|
|
99
|
+
-agent:vibe # Exclude agent
|
|
100
|
+
agent:claude,!codex # Include claude, exclude codex
|
|
101
|
+
|
|
102
|
+
dir:myproject # Filter by directory (substring)
|
|
103
|
+
dir:backend,!test # Include backend, exclude test
|
|
104
|
+
|
|
105
|
+
date:today # Sessions from today
|
|
106
|
+
date:yesterday # Sessions from yesterday
|
|
107
|
+
date:<1h # Within the last hour
|
|
108
|
+
date:<2d # Within the last 2 days
|
|
109
|
+
date:>1w # Older than 1 week
|
|
110
|
+
date:week # Within the last week
|
|
111
|
+
date:month # Within the last month
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Combine keywords with free-text search:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
fr "agent:claude date:<1d api bug"
|
|
118
|
+
fr "dir:backend -agent:vibe auth"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Autocomplete**: Type `agent:cl` and press `Tab` to complete to `agent:claude`.
|
|
122
|
+
|
|
123
|
+
### Non-Interactive Mode
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# List sessions in terminal (no TUI)
|
|
127
|
+
fr --no-tui
|
|
128
|
+
|
|
129
|
+
# Just list, don't offer to resume
|
|
130
|
+
fr --list
|
|
131
|
+
|
|
132
|
+
# Force rebuild the index
|
|
133
|
+
fr --rebuild
|
|
134
|
+
|
|
135
|
+
# View your usage statistics
|
|
136
|
+
fr --stats
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Yolo Mode
|
|
140
|
+
|
|
141
|
+
Resume sessions with auto-approve / skip-permissions flags:
|
|
142
|
+
|
|
143
|
+
| Agent | Flag Added | Auto-detected |
|
|
144
|
+
| --------------- | -------------------------------------------- | ------------- |
|
|
145
|
+
| Claude | `--dangerously-skip-permissions` | No |
|
|
146
|
+
| Codex | `--dangerously-bypass-approvals-and-sandbox` | Yes |
|
|
147
|
+
| Copilot CLI | `--allow-all-tools --allow-all-paths` | No |
|
|
148
|
+
| Vibe | `--auto-approve` | Yes |
|
|
149
|
+
| OpenCode | _(config-based)_ | — |
|
|
150
|
+
| Crush | _(no CLI resume)_ | — |
|
|
151
|
+
| VS Code Copilot | _(n/a)_ | — |
|
|
152
|
+
|
|
153
|
+
**Auto-detection:** Codex and Vibe store the permissions mode in their session files. Sessions originally started in yolo mode are automatically resumed in yolo mode.
|
|
154
|
+
|
|
155
|
+
**Interactive prompt:** For agents that support yolo but don't store it (Claude, Copilot CLI), you'll see a modal asking whether to resume in yolo mode. Use Tab to toggle, Enter to confirm.
|
|
156
|
+
|
|
157
|
+
**Force yolo:** Use `fr --yolo` to skip the prompt and always resume in yolo mode, if supported.
|
|
158
|
+
|
|
159
|
+
### Command Reference
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
Usage: fr [OPTIONS] [QUERY]
|
|
163
|
+
|
|
164
|
+
Arguments:
|
|
165
|
+
QUERY Search query (optional)
|
|
166
|
+
|
|
167
|
+
Options:
|
|
168
|
+
-a, --agent [claude|codex|copilot-cli|copilot-vscode|crush|opencode|vibe]
|
|
169
|
+
Filter by agent
|
|
170
|
+
-d, --directory TEXT Filter by directory (substring match)
|
|
171
|
+
--no-tui Output list to stdout instead of TUI
|
|
172
|
+
--list Just list sessions, don't resume
|
|
173
|
+
--rebuild Force rebuild the session index
|
|
174
|
+
--stats Show index statistics
|
|
175
|
+
--yolo Resume with auto-approve/skip-permissions flags
|
|
176
|
+
--version Show version
|
|
177
|
+
--help Show this message and exit
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Keybindings
|
|
181
|
+
|
|
182
|
+
### Navigation
|
|
183
|
+
|
|
184
|
+
| Key | Action |
|
|
185
|
+
| ----------------------- | ---------------------------------- |
|
|
186
|
+
| `↑` / `↓` | Move selection up/down |
|
|
187
|
+
| `j` / `k` | Move selection up/down (vim-style) |
|
|
188
|
+
| `Page Up` / `Page Down` | Move by 10 rows |
|
|
189
|
+
| `Enter` | Resume selected session |
|
|
190
|
+
| `/` | Focus search input |
|
|
191
|
+
|
|
192
|
+
### Preview & Actions
|
|
193
|
+
|
|
194
|
+
| Key | Action |
|
|
195
|
+
| --------- | ------------------------------------- |
|
|
196
|
+
| `Ctrl+\`` | Toggle preview pane |
|
|
197
|
+
| `+` / `-` | Resize preview pane |
|
|
198
|
+
| `Tab` | Accept autocomplete suggestion |
|
|
199
|
+
| `c` | Copy full resume command to clipboard |
|
|
200
|
+
| `Ctrl+P` | Open command palette |
|
|
201
|
+
| `q`/`Esc` | Quit |
|
|
202
|
+
|
|
203
|
+
### Yolo Mode Modal
|
|
204
|
+
|
|
205
|
+
| Key | Action |
|
|
206
|
+
| --------------- | ----------------- |
|
|
207
|
+
| `Tab` / `←` `→` | Toggle selection |
|
|
208
|
+
| `Enter` | Confirm selection |
|
|
209
|
+
| `y` | Select Yolo |
|
|
210
|
+
| `n` | Select No |
|
|
211
|
+
| `Esc` | Cancel |
|
|
212
|
+
|
|
213
|
+
## Statistics Dashboard
|
|
214
|
+
|
|
215
|
+
Run `fr --stats` to see analytics about your coding sessions:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Index Statistics
|
|
219
|
+
|
|
220
|
+
Total sessions 751
|
|
221
|
+
Total messages 13,799
|
|
222
|
+
Avg messages/session 18.4
|
|
223
|
+
Index size 15.5 MB
|
|
224
|
+
Index location ~/.cache/fast-resume/tantivy_index
|
|
225
|
+
Date range 2023-11-15 to 2025-12-22
|
|
226
|
+
|
|
227
|
+
Data by Agent
|
|
228
|
+
|
|
229
|
+
┌────────────────┬───────┬──────────┬──────────┬──────────┬──────────┬─────────────┐
|
|
230
|
+
│ Agent │ Files │ Disk │ Sessions │ Messages │ Content │ Data Dir │
|
|
231
|
+
├────────────────┼───────┼──────────┼──────────┼──────────┼──────────┼─────────────┤
|
|
232
|
+
│ claude │ 477 │ 312.9 MB │ 377 │ 10,415 │ 3.1 MB │ ~/.claude/… │
|
|
233
|
+
│ copilot-vscode │ 191 │ 146.0 MB │ 189 │ 954 │ 1.4 MB │ ~/Library/… │
|
|
234
|
+
│ codex │ 107 │ 23.6 MB │ 89 │ 321 │ 890.6 kB │ ~/.codex/… │
|
|
235
|
+
│ opencode │ 9275 │ 46.3 MB │ 72 │ 1,912 │ 597.7 kB │ ~/.local/… │
|
|
236
|
+
│ vibe │ 12 │ 858.2 kB │ 12 │ 138 │ 380.0 kB │ ~/.vibe/… │
|
|
237
|
+
│ crush │ 3 │ 1.0 MB │ 7 │ 44 │ 15.2 kB │ ~/.local/… │
|
|
238
|
+
│ copilot-cli │ 5 │ 417.1 kB │ 5 │ 15 │ 6.9 kB │ ~/.copilot… │
|
|
239
|
+
└────────────────┴───────┴──────────┴──────────┴──────────┴──────────┴─────────────┘
|
|
240
|
+
|
|
241
|
+
Activity by Day
|
|
242
|
+
|
|
243
|
+
Mon ██████████ 89
|
|
244
|
+
Tue ██████████ 86
|
|
245
|
+
Wed █████ 44
|
|
246
|
+
Thu ██████████████ 115
|
|
247
|
+
Fri █████████████ 112
|
|
248
|
+
Sat ████████████████████ 163
|
|
249
|
+
Sun █████████████████ 142
|
|
250
|
+
|
|
251
|
+
Activity by Hour
|
|
252
|
+
|
|
253
|
+
0h ▄▁ ▄▄▅▂▂▂▂▂▃▃▃▅▅█ 23h
|
|
254
|
+
Peak hours: 23:00 (99), 22:00 (63), 12:00 (63)
|
|
255
|
+
|
|
256
|
+
Top Directories
|
|
257
|
+
|
|
258
|
+
┌───────────────────────┬──────────┬──────────┐
|
|
259
|
+
│ Directory │ Sessions │ Messages │
|
|
260
|
+
├───────────────────────┼──────────┼──────────┤
|
|
261
|
+
│ ~/git/openvpn-install │ 234 │ 5,597 │
|
|
262
|
+
│ ~/lab/larafeed │ 158 │ 2,590 │
|
|
263
|
+
│ ~/lab/fast-resume │ 81 │ 2,027 │
|
|
264
|
+
│ ... │ │ │
|
|
265
|
+
└───────────────────────┴──────────┴──────────┘
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## How It Works
|
|
269
|
+
|
|
270
|
+
### Architecture
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
┌────────────────────────────────────────────────────────────────────────────────────────┐
|
|
274
|
+
│ SessionSearch │
|
|
275
|
+
│ │
|
|
276
|
+
│ • Orchestrates adapters in parallel (ThreadPoolExecutor) │
|
|
277
|
+
│ • Compares file mtimes to detect changes (incremental updates) │
|
|
278
|
+
│ • Delegates search queries to Tantivy index │
|
|
279
|
+
└────────────────────────────────────────────────────────────────────────────────────────┘
|
|
280
|
+
│ │
|
|
281
|
+
┌────────────┴────────────┐ │
|
|
282
|
+
▼ ▼ ▼
|
|
283
|
+
┌──────────────────┐ ┌───────────────────────────────────────────────────────────────────────────────┐
|
|
284
|
+
│ TantivyIndex │ │ Adapters │
|
|
285
|
+
│ │ │ ┌────────┐ ┌───────┐ ┌───────┐ ┌─────────┐ ┌───────┐ ┌────────┐ ┌────┐ │
|
|
286
|
+
│ • Fuzzy search │◄───│ │ Claude │ │ Codex │ │Copilot│ │ Copilot │ │ Crush │ │OpenCode│ │Vibe│ │
|
|
287
|
+
│ • mtime tracking │ │ │ │ │ │ │ CLI │ │ VS Code │ │ │ │ │ │ │ │
|
|
288
|
+
│ │ │ └───┬────┘ └───┬───┘ └───┬───┘ └────┬────┘ └───┬───┘ └───┬────┘ └─┬──┘ │
|
|
289
|
+
│ ~/.cache/ │ │ │ │ │ │ │ │ │ │
|
|
290
|
+
│ fast-resume/ │ └──────┼──────────┼─────────┼──────────┼──────────┼─────────┼────────┼───────────┘
|
|
291
|
+
└──────────────────┘ ▼ ▼ ▼ ▼ ▼ ▼ ▼
|
|
292
|
+
~/.claude/ ~/.codex/ ~/.copilot/ VS Code/ crush.db opencode/ ~/.vibe/
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Session Parsing
|
|
296
|
+
|
|
297
|
+
Each agent stores sessions differently. Adapters normalize them into a common `Session` structure:
|
|
298
|
+
|
|
299
|
+
| Agent | Format | Parsing Strategy |
|
|
300
|
+
| -------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
301
|
+
| Claude Code | JSONL in `~/.claude/projects/<project>/*.jsonl` | Stream line-by-line, extract `user`/`assistant` messages, skip `agent-*` subprocess files |
|
|
302
|
+
| Codex | JSONL in `~/.codex/sessions/**/*.jsonl` | Line-by-line parsing, extract from `session_meta`, `response_item`, and `event_msg` entries |
|
|
303
|
+
| Copilot CLI | JSONL in `~/.copilot/session-state/*.jsonl` | Line-by-line parsing, extract `user.message` and `assistant.message` types |
|
|
304
|
+
| Copilot VSCode | JSON in VS Code's `workspaceStorage/*/chatSessions/` | Parse `requests` array with message text and response values |
|
|
305
|
+
| Crush | SQLite DB at `<project>/crush.db` | Query `sessions` and `messages` tables directly, parse JSON `parts` column |
|
|
306
|
+
| OpenCode | Split JSON in `~/.local/share/opencode/storage/` | Join `session/<hash>/ses_*.json` + `message/<id>/msg_*.json` + `part/<id>/*.json` |
|
|
307
|
+
| Vibe | JSON in `~/.vibe/logs/session/session_*.json` | Parse `messages` array with role-based content |
|
|
308
|
+
|
|
309
|
+
**The normalized Session structure:**
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
@dataclass
|
|
313
|
+
class Session:
|
|
314
|
+
id: str # Unique identifier (usually filename or UUID)
|
|
315
|
+
agent: str # "claude", "codex", "copilot-cli", "copilot-vscode", "crush", "opencode", "vibe"
|
|
316
|
+
title: str # Summary or first user message (max 100 chars)
|
|
317
|
+
directory: str # Working directory where session was created
|
|
318
|
+
timestamp: datetime # Last modified time
|
|
319
|
+
preview: str # First 500 chars for preview pane
|
|
320
|
+
content: str # Full conversation text (» user, ␣␣ assistant)
|
|
321
|
+
message_count: int # Conversation turns (user + assistant, excludes tool results)
|
|
322
|
+
mtime: float # File mtime for incremental update detection
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**What gets indexed:**
|
|
326
|
+
|
|
327
|
+
- User text messages (the actual prompts you typed)
|
|
328
|
+
- Assistant text responses
|
|
329
|
+
|
|
330
|
+
**What's excluded from indexing:**
|
|
331
|
+
|
|
332
|
+
- Tool results (file contents, command outputs, API responses)
|
|
333
|
+
- Tool use/calls (function invocations)
|
|
334
|
+
- Meta messages (system prompts, context summaries)
|
|
335
|
+
- Local command outputs (slash commands like `/context`)
|
|
336
|
+
|
|
337
|
+
This keeps the index focused on the actual conversation and avoids bloating it with large tool outputs that are rarely useful for search.
|
|
338
|
+
|
|
339
|
+
### Indexing
|
|
340
|
+
|
|
341
|
+
**Incremental updates** avoid re-parsing on every launch:
|
|
342
|
+
|
|
343
|
+
1. Load known sessions from Tantivy index with their `mtime` values
|
|
344
|
+
2. Scan session files, compare mtimes against known values
|
|
345
|
+
3. Only parse files where `current_mtime > known_mtime + 0.001`
|
|
346
|
+
4. Detect deleted sessions (in index but not on disk)
|
|
347
|
+
5. Apply changes atomically: delete removed, upsert modified
|
|
348
|
+
|
|
349
|
+
**Parallel loading** via `ThreadPoolExecutor`:
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
with ThreadPoolExecutor(max_workers=len(self.adapters)) as executor:
|
|
353
|
+
futures = {executor.submit(get_incremental, a): a for a in self.adapters}
|
|
354
|
+
for future in as_completed(futures):
|
|
355
|
+
new_or_modified, deleted_ids = future.result()
|
|
356
|
+
self._index.update_sessions(new_or_modified)
|
|
357
|
+
on_progress() # TUI updates as each adapter completes
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Schema versioning**: A `.schema_version` file tracks the index schema. If it doesn't match the code's `SCHEMA_VERSION` constant, the entire index is deleted and rebuilt. This prevents deserialization errors after upgrades.
|
|
361
|
+
|
|
362
|
+
### Search
|
|
363
|
+
|
|
364
|
+
[Tantivy](https://github.com/quickwit-oss/tantivy) is a Rust full-text search library (powers Quickwit, similar to Lucene). We use it via [tantivy-py](https://github.com/quickwit-oss/tantivy-py).
|
|
365
|
+
|
|
366
|
+
**Hybrid search** combines exact and fuzzy matching for best results:
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
# Exact match (boosted 5x) - uses BM25 scoring
|
|
370
|
+
exact_query = index.parse_query(query, ["title", "content"])
|
|
371
|
+
boosted_exact = tantivy.Query.boost_query(exact_query, 5.0)
|
|
372
|
+
|
|
373
|
+
# Fuzzy match (edit distance 1) - for typo tolerance
|
|
374
|
+
for term in query.split():
|
|
375
|
+
fuzzy_title = tantivy.Query.fuzzy_term_query(schema, "title", term, distance=1, prefix=True)
|
|
376
|
+
fuzzy_content = tantivy.Query.fuzzy_term_query(schema, "content", term, distance=1, prefix=True)
|
|
377
|
+
...
|
|
378
|
+
|
|
379
|
+
# Combine: exact OR fuzzy (exact scores higher due to boost)
|
|
380
|
+
tantivy.Query.boolean_query([
|
|
381
|
+
(tantivy.Occur.Should, boosted_exact),
|
|
382
|
+
(tantivy.Occur.Should, fuzzy_query),
|
|
383
|
+
])
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
This ensures exact matches rank first while still finding typos like `auth midleware` → "authentication middleware".
|
|
387
|
+
|
|
388
|
+
**Query lifecycle:**
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
┌─────────────┐ 50ms ┌─────────────┐ background ┌─────────────┐
|
|
392
|
+
│ Keystroke │ ────────► │ Debounce │ ───────────► │ Worker │
|
|
393
|
+
└─────────────┘ timer └─────────────┘ thread └──────┬──────┘
|
|
394
|
+
│
|
|
395
|
+
┌─────────────┐ ┌──────▼──────┐
|
|
396
|
+
│ Render │ ◄─────────── │ Tantivy │
|
|
397
|
+
│ Table │ results │ Query │
|
|
398
|
+
└─────────────┘ └─────────────┘
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### TUI
|
|
402
|
+
|
|
403
|
+
**Streaming results**: Sessions appear as each adapter completes, not after all finish.
|
|
404
|
+
|
|
405
|
+
- **Fast path**: Index up-to-date → load synchronously, no spinner
|
|
406
|
+
- **Slow path**: Changes detected → spinner, stream results via `on_progress()` callback
|
|
407
|
+
|
|
408
|
+
**Preview context**: When searching, the preview pane jumps to the matching portion:
|
|
409
|
+
|
|
410
|
+
```python
|
|
411
|
+
for term in query.lower().split():
|
|
412
|
+
pos = content.lower().find(term)
|
|
413
|
+
if pos != -1:
|
|
414
|
+
start = max(0, pos - 100) # Show ~100 chars before match
|
|
415
|
+
preview_text = content[start:start + 1500]
|
|
416
|
+
break
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Matching terms are highlighted with Rich's `Text.stylize()`.
|
|
420
|
+
|
|
421
|
+
### Resume Handoff
|
|
422
|
+
|
|
423
|
+
When you press Enter on a session, fast-resume hands off to the original agent:
|
|
424
|
+
|
|
425
|
+
```python
|
|
426
|
+
# In cli.py after TUI exits
|
|
427
|
+
resume_cmd, resume_dir = run_tui(query=query, agent_filter=agent)
|
|
428
|
+
|
|
429
|
+
if resume_cmd:
|
|
430
|
+
# 1. Change to the session's original working directory
|
|
431
|
+
os.chdir(resume_dir)
|
|
432
|
+
|
|
433
|
+
# 2. Replace current process with agent's resume command
|
|
434
|
+
os.execvp(resume_cmd[0], resume_cmd)
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
`os.execvp()` replaces the Python process entirely with the agent CLI. This means:
|
|
438
|
+
|
|
439
|
+
- No subprocess overhead
|
|
440
|
+
- Shell history shows `claude --resume xyz`, not `fr`
|
|
441
|
+
- Agent inherits the correct working directory
|
|
442
|
+
- fast-resume process is gone after handoff
|
|
443
|
+
|
|
444
|
+
Each adapter returns the appropriate command:
|
|
445
|
+
|
|
446
|
+
| Agent | Resume Command | With `--yolo` |
|
|
447
|
+
| -------------- | ------------------------------- | -------------------------------------------------------------- |
|
|
448
|
+
| Claude | `claude --resume <id>` | `claude --dangerously-skip-permissions --resume <id>` |
|
|
449
|
+
| Codex | `codex resume <id>` | `codex --dangerously-bypass-approvals-and-sandbox resume <id>` |
|
|
450
|
+
| Copilot CLI | `copilot --resume <id>` | `copilot --allow-all-tools --allow-all-paths --resume <id>` |
|
|
451
|
+
| Copilot VSCode | `code <directory>` | _(no change)_ |
|
|
452
|
+
| OpenCode | `opencode <dir> --session <id>` | _(no change)_ |
|
|
453
|
+
| Vibe | `vibe --resume <id>` | `vibe --auto-approve --resume <id>` |
|
|
454
|
+
| Crush | `crush` | _(no change)_ |
|
|
455
|
+
|
|
456
|
+
### Performance
|
|
457
|
+
|
|
458
|
+
Why fast-resume feels instant:
|
|
459
|
+
|
|
460
|
+
- **Tantivy (Rust)**: Search engine written in Rust, accessed via Python bindings. Handles fuzzy queries over 10k+ sessions in <10ms
|
|
461
|
+
- **Incremental updates**: Only re-parse files where `mtime` changed. Second launch with no changes: ~50ms total
|
|
462
|
+
- **Parallel adapters**: All adapters run simultaneously in ThreadPoolExecutor. Total time = slowest adapter, not sum
|
|
463
|
+
- **Debounced search**: 50ms debounce prevents wasteful searches while typing
|
|
464
|
+
- **Background workers**: Search runs in thread, UI never blocks
|
|
465
|
+
- **orjson**: Rust-based JSON parsing, ~10x faster than stdlib json
|
|
466
|
+
- **Streaming results**: Sessions appear as each adapter completes, not after all finish
|
|
467
|
+
|
|
468
|
+
Typical performance on a machine with ~500 sessions:
|
|
469
|
+
|
|
470
|
+
- Cold start (empty index): ~2s
|
|
471
|
+
- Warm start (no changes): ~50ms
|
|
472
|
+
- Search query: <10ms
|
|
473
|
+
|
|
474
|
+
## Development
|
|
475
|
+
|
|
476
|
+
```bash
|
|
477
|
+
# Clone and setup
|
|
478
|
+
git clone https://github.com/angristan/fast-resume.git
|
|
479
|
+
cd fast-resume
|
|
480
|
+
uv sync
|
|
481
|
+
|
|
482
|
+
# Run locally
|
|
483
|
+
uv run fr
|
|
484
|
+
|
|
485
|
+
# Install pre-commit hooks
|
|
486
|
+
uv run pre-commit install
|
|
487
|
+
|
|
488
|
+
# Run tests
|
|
489
|
+
uv run pytest -v
|
|
490
|
+
|
|
491
|
+
# Lint and format
|
|
492
|
+
uv run ruff check .
|
|
493
|
+
uv run ruff format .
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Project Structure
|
|
497
|
+
|
|
498
|
+
```
|
|
499
|
+
fast-resume/
|
|
500
|
+
├── src/fast_resume/
|
|
501
|
+
│ ├── cli.py # Click CLI entry point
|
|
502
|
+
│ ├── config.py # Constants, colors, paths
|
|
503
|
+
│ ├── index.py # TantivyIndex - search engine
|
|
504
|
+
│ ├── search.py # SessionSearch - adapter orchestration
|
|
505
|
+
│ ├── tui.py # Textual TUI application
|
|
506
|
+
│ ├── assets/ # Agent icons (PNG)
|
|
507
|
+
│ └── adapters/
|
|
508
|
+
│ ├── base.py # Session dataclass, AgentAdapter protocol
|
|
509
|
+
│ ├── claude.py # Claude Code adapter
|
|
510
|
+
│ ├── codex.py # Codex CLI adapter
|
|
511
|
+
│ ├── copilot.py # GitHub Copilot CLI adapter
|
|
512
|
+
│ ├── copilot_vscode.py # VS Code Copilot Chat adapter
|
|
513
|
+
│ ├── crush.py # Crush adapter
|
|
514
|
+
│ ├── opencode.py # OpenCode adapter
|
|
515
|
+
│ └── vibe.py # Vibe adapter
|
|
516
|
+
├── tests/ # pytest test suite
|
|
517
|
+
├── pyproject.toml # Dependencies and build config
|
|
518
|
+
└── README.md
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### Tech Stack
|
|
522
|
+
|
|
523
|
+
| Component | Library |
|
|
524
|
+
| ------------------- | ------------------------------------------------------------------- |
|
|
525
|
+
| TUI Framework | [Textual](https://textual.textualize.io/) |
|
|
526
|
+
| Terminal Formatting | [Rich](https://rich.readthedocs.io/) |
|
|
527
|
+
| CLI Framework | [Click](https://click.palletsprojects.com/) |
|
|
528
|
+
| Search Engine | [Tantivy](https://github.com/quickwit-oss/tantivy) (via tantivy-py) |
|
|
529
|
+
| JSON Parsing | [orjson](https://github.com/ijl/orjson) (fast) |
|
|
530
|
+
| Date Formatting | [humanize](https://python-humanize.readthedocs.io/) |
|
|
531
|
+
|
|
532
|
+
## Configuration
|
|
533
|
+
|
|
534
|
+
fast-resume uses sensible defaults and requires no configuration.
|
|
535
|
+
|
|
536
|
+
To clear the index and rebuild from scratch:
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
rm -rf ~/.cache/fast-resume/
|
|
540
|
+
fr --rebuild
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
## License
|
|
544
|
+
|
|
545
|
+
MIT
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
fast_resume/__init__.py,sha256=gW5lJLa_i_nUqDxC0_G04J-qG0E0tj3Wa86XBSVuwU8,144
|
|
2
|
+
fast_resume/cli.py,sha256=6HlL1-qjuN3nuzST_maKmd9HjmNrLhVua4k-Xz9W7Pc,11008
|
|
3
|
+
fast_resume/config.py,sha256=95fWD6Ly_c6NwuaqqdLVSEoEGcqeWaB2lUKHs1R8_Gs,1185
|
|
4
|
+
fast_resume/index.py,sha256=36nO3NMBYaA5u3oMM9NKH868_gudzmH8ydRIUDngeTs,27969
|
|
5
|
+
fast_resume/logging_config.py,sha256=SfNTLwlM1_iuOyTPTDvJNk32FslP1DzeE4kdkWltRXM,1772
|
|
6
|
+
fast_resume/query.py,sha256=uv3ONZiAy5ghp8BxwwMv5h0BVa6gQ1gXhu2UrndNiOM,8677
|
|
7
|
+
fast_resume/search.py,sha256=sn7h6LbcCFDd5eKFIyWlqJ7K5bb6yJaityca7WLThMA,10520
|
|
8
|
+
fast_resume/adapters/__init__.py,sha256=mE2yN2IcL6SRfv2frCZ0ji4ewkeRp_r2Oxnbh28JQCM,650
|
|
9
|
+
fast_resume/adapters/base.py,sha256=AeMvjhTLH_wVivMfWnIWGuEM8JInHsHgrz9XIwewaB0,7974
|
|
10
|
+
fast_resume/adapters/claude.py,sha256=cR609NCyIX2LWmdT1-WaQpGdRPTezjAo-AKfDHk8WCA,8346
|
|
11
|
+
fast_resume/adapters/codex.py,sha256=J46p0qMAuYQiMpU2uHXhP4sINJ2MX4HjUOJeGouzlJY,8586
|
|
12
|
+
fast_resume/adapters/copilot.py,sha256=cHkWDSJUDJxz9_V_UM2kwtzAGv0Rcs-KpGuI4g_LFeI,6553
|
|
13
|
+
fast_resume/adapters/copilot_vscode.py,sha256=LdK1qDm9kfgqLHO8juN5Z6ZfAn-eCzt4SXgKd_68KR0,11956
|
|
14
|
+
fast_resume/adapters/crush.py,sha256=ulGPP28J4XhWfcFpVN7i-ur-D2h2-37t_JgCHfRKdj0,11862
|
|
15
|
+
fast_resume/adapters/opencode.py,sha256=jyv6BR6HwBBm4zKhVpHt37EpMHssJVRXqNN-k04XcqA,12730
|
|
16
|
+
fast_resume/adapters/vibe.py,sha256=y2ucU_PnX_9Q-DDJjs7-p5UVjNtqNN3EnkiYexERZ5E,6677
|
|
17
|
+
fast_resume/assets/claude.png,sha256=B6qA9yNcCZz1yPymGQEmxAROmLPCrPFsaBzA0eZOtfU,5179
|
|
18
|
+
fast_resume/assets/codex.png,sha256=gvziwdq8T7w7INSePYV3-w4Z41ALCUhm9hgbiEfiuzo,4472
|
|
19
|
+
fast_resume/assets/copilot-cli.png,sha256=gjxS4lWPlRjM8s8snrYK1EaajzDGNmpxG_RPJ9FubHs,10760
|
|
20
|
+
fast_resume/assets/copilot-vscode.png,sha256=AplrxQqrl51XbpNm3xKCaahBGo3N1lQyRNWnJVCX-98,5115
|
|
21
|
+
fast_resume/assets/crush.png,sha256=5U95uAk3qVUzmVO4XWa7JxCNACZunFagmycocuakwFQ,2540
|
|
22
|
+
fast_resume/assets/opencode.png,sha256=JU9M9brAT5Z1sGvkvm6gKLqf1IoRB_1wMJgzFOBxFkI,558
|
|
23
|
+
fast_resume/assets/vibe.png,sha256=SoFyIKHyXQmih3IDqvslp8EQtBxXjgVpkZMaE9A_drw,2011
|
|
24
|
+
fast_resume/tui/__init__.py,sha256=-mcEXE_rk2YYeCUgwXaOlTaYFL1nMq_2a4o6M79D6-0,1384
|
|
25
|
+
fast_resume/tui/app.py,sha256=sRnd_-V_9RNfrcGD5Bcy2LOOIMOG22dIiYeqKf55MEQ,25183
|
|
26
|
+
fast_resume/tui/filter_bar.py,sha256=LWDwquIajmn8XDSthaSl0U473GHBAYWI3C9vsfOUSkY,4484
|
|
27
|
+
fast_resume/tui/modal.py,sha256=wWlIMKRnLphStZUKL4JQV1RtYauwlMnTHVvotRJ3WQY,2398
|
|
28
|
+
fast_resume/tui/preview.py,sha256=lOEjW_GJDXCT_CJrEIVBjCj4_zFXHO6oZz2RV-mx0Gc,14124
|
|
29
|
+
fast_resume/tui/query.py,sha256=U9-Xth8EqRCLQSBovPEYAGpn-AHjoobbJO9zd1CJVA8,2782
|
|
30
|
+
fast_resume/tui/results_table.py,sha256=Du0bIuYJS87nGaKr6-W5JFAMJuuwpOm6Lhqb2rtqQyI,5860
|
|
31
|
+
fast_resume/tui/search_input.py,sha256=3ZiEtZ54ZuLTGOLiEp3kfUwsoeV3LTKX30u7aryYy7k,4136
|
|
32
|
+
fast_resume/tui/styles.py,sha256=l6yZiGbvo7yyFUG7cFyl3IrL6JOn46VxBRIUb8Ps2EQ,4304
|
|
33
|
+
fast_resume/tui/utils.py,sha256=9gXBEqWSi2yYzCt3DUSTO5rpdfwCThLCdmuz7OSHAgw,4773
|
|
34
|
+
fast_resume-1.12.8.dist-info/METADATA,sha256=KJSlG_bY4aA1177v-mJPV115qssUI3q7x0vKjJ65OkU,25923
|
|
35
|
+
fast_resume-1.12.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
36
|
+
fast_resume-1.12.8.dist-info/entry_points.txt,sha256=O8g4Bf32_NGRj6nJta6Om1r8jFIhclJv_pMM1CE-kIA,79
|
|
37
|
+
fast_resume-1.12.8.dist-info/licenses/LICENSE,sha256=p2Mw1d6DdK5a6-ScBMPkuf4lEmI-e2CrebVA-9aYjkw,1072
|
|
38
|
+
fast_resume-1.12.8.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Stanislas Lange
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|