Skip to content

Commit dca2a88

Browse files
committed
feat: add 'search' subcommand and document features in README
1 parent 4a45b99 commit dca2a88

3 files changed

Lines changed: 64 additions & 11 deletions

File tree

README.md

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,23 @@
22

33
Interactive terminal UI for browsing, searching, and surgically editing Claude Code session JSONL files.
44

5-
Claude Code persists conversations as JSONL under `~/.claude/projects/<slug>/<uuid>.jsonl`. Built-ins only support rewind, clear, and compact. `cc-session` lets you:
6-
7-
- Browse and search every session across all projects.
8-
- Open any session and view messages with role, text, token count, timestamp.
9-
- Delete individual messages, ranges, from-top, or from-bottom.
10-
- Auto-pair tool_use and tool_result blocks so you never orphan one half.
11-
- Save atomically with a `.bak` backup — and refuse to save when Claude Code holds the file open.
5+
Claude Code persists conversations as JSONL under `~/.claude/projects/<slug>/<uuid>.jsonl`. Built-ins only support rewind, clear, and compact. `cc-session` fixes that.
6+
7+
## Features
8+
9+
- **Session browser** — every session across every project, sorted by recency, with project, title, modified time, size, and full UUID.
10+
- **Fuzzy search** — same engine as fzf/Helix (`nucleo-matcher`), searches project + title, ranks by score.
11+
- **Message viewer** — shows only real user/assistant text by default; system messages, tool blocks, attachments, and harness wrappers (`<bash-input>`, `<system-reminder>`, etc.) are hidden but preserved on disk.
12+
- **Edit a session** — open any session and surgically edit it.
13+
- **Delete individual messages** — pick one and drop it.
14+
- **Delete ranges, from-top, from-bottom** — bulk trim by range, prefix, or suffix.
15+
- **Turn-level auto-pair** — deleting a user message also deletes its assistant response (and vice versa). Marking any message in a turn deletes the whole turn cleanly.
16+
- **tool_use ↔ tool_result safety** — tool calls always travel with their results, so resume never breaks.
17+
- **Token counts per message** — tiktoken `cl100k_base`, with `usage` metadata as fallback when present.
18+
- **Atomic save with `.bak` backup** — write `.tmp`, fsync, rename. Backup written every save.
19+
- **Concurrent-open detection**`lsof` check refuses to save while Claude Code holds the file. Override with `--force`.
20+
- **LLM-friendly non-interactive CLI**`list`, `search`, `show`, `info`, `delete` subcommands with `--json` output. Other agents (Claude Code, Codex, scripts) can drive every action a human can.
21+
- **Cross-platform** — macOS and Linux. Windows lsof equivalent deferred.
1222

1323
## Install
1424

@@ -59,12 +69,12 @@ cc-session --version
5969
Every operation is also available as a subcommand with structured output, so other agents (Claude Code, Codex, scripts, CI) can drive the editor without a human at the terminal.
6070

6171
```sh
62-
cc-session list --json [--project <slug>] [--limit N]
63-
cc-session show <id-or-path> [--json] [--full] [--include-hidden]
64-
cc-session info <id-or-path> [--json]
72+
cc-session list [--json] [--project <slug>] [--limit N]
73+
cc-session search <query> [--json] [--limit N]
74+
cc-session show <id-or-path> [--json] [--full] [--include-hidden]
75+
cc-session info <id-or-path> [--json]
6576
cc-session delete <id-or-path> --indices 3,5,7 [--from-top N] [--from-bottom N] [--range lo..hi] [--dry-run] [--force] [--json]
6677
```
67-
6878
`<id-or-path>` accepts a full path, a session UUID, or a unique substring of one. Indices are 0-based positions in the raw JSONL (use `cc-session show --json` to map text → index). Auto-pair always extends the delete set to keep `tool_use`/`tool_result` blocks together; `paired_added` in the output reports what was added.
6979

7080
### Example LLM workflow

src/cli.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,35 @@ pub fn list(
7474
Ok(())
7575
}
7676

77+
pub fn search(projects_dir: &Path, query: &str, limit: Option<usize>, json: bool) -> Result<()> {
78+
let entries = scan::scan(projects_dir)?;
79+
let mut hits: Vec<&SessionEntry> = crate::search::fuzzy_filter(&entries, query);
80+
if let Some(n) = limit {
81+
hits.truncate(n);
82+
}
83+
if json {
84+
let out: Vec<ListItem> = hits.iter().map(|e| list_item(e)).collect();
85+
println!("{}", serde_json::to_string_pretty(&out)?);
86+
} else {
87+
println!(
88+
"{:<40} {:<50} {:<17} {:<10} id",
89+
"project", "title", "modified", "size"
90+
);
91+
for e in &hits {
92+
println!(
93+
"{:<40} {:<50} {:<17} {:<10} {}",
94+
truncate(&e.project_slug, 40),
95+
truncate(&e.title, 50),
96+
format_mtime(e),
97+
human_size(e.size),
98+
e.session_id
99+
);
100+
}
101+
println!("\n{} match(es)", hits.len());
102+
}
103+
Ok(())
104+
}
105+
77106
fn list_item(e: &SessionEntry) -> ListItem {
78107
ListItem {
79108
project: e.project_slug.clone(),

src/main.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ enum Command {
4141
#[arg(long)]
4242
limit: Option<usize>,
4343
},
44+
/// Fuzzy search sessions by project + title. Same matcher the TUI uses.
45+
Search {
46+
/// Query string. Subsequence match, case-insensitive.
47+
query: String,
48+
/// Limit output to N best matches.
49+
#[arg(long)]
50+
limit: Option<usize>,
51+
/// Output JSON.
52+
#[arg(long)]
53+
json: bool,
54+
},
4455
/// Show messages in a session.
4556
Show {
4657
/// Session id (uuid), file path, or substring of either.
@@ -103,6 +114,9 @@ fn main() -> anyhow::Result<()> {
103114
json,
104115
limit,
105116
}) => cli::list(&projects_dir, project.as_deref(), json, limit),
117+
Some(Command::Search { query, limit, json }) => {
118+
cli::search(&projects_dir, &query, limit, json)
119+
}
106120
Some(Command::Show {
107121
target,
108122
include_hidden,

0 commit comments

Comments
 (0)