Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
68bbdae
feat: entity tree view, agent navigation fixes, configurable refresh
gavin-jeong Apr 8, 2026
976732b
fix: strip trailing markdown `*` from extracted URLs
gavin-jeong Apr 8, 2026
c77b1ba
fix: handle ctrl+c in subcommand picker to quit cleanly
gavin-jeong Apr 8, 2026
bcd07b1
feat: add cron job support to ccx TUI
gavin-jeong Apr 10, 2026
69c3085
fix: clear session search query on escape reset
gavin-jeong Apr 10, 2026
5d98bee
fix: group stats by repo and show ratios
gavin-jeong Apr 10, 2026
8bb1887
fix: use repo path in stats summary card
gavin-jeong Apr 10, 2026
c04ddea
feat: add separate project stats view
gavin-jeong Apr 10, 2026
c597138
feat: add session picker and conversation navigation improvements
gavin-jeong Apr 13, 2026
22be371
feat: add conversation picker workflow
gavin-jeong Apr 14, 2026
a9f1768
merge: update branch with latest master
gavin-jeong Apr 14, 2026
16c2360
fix: use actual line budget for conversation picker list
gavin-jeong Apr 14, 2026
aec5f4c
fix: align conversation picker body text with header column
gavin-jeong Apr 14, 2026
28baefe
fix: surface failed tool results in conversation preview
gavin-jeong Apr 14, 2026
7a9ad60
fix: improve preview mode behavior in conversation views
gavin-jeong Apr 14, 2026
46ec288
fix: add separators between turns in conversation previews
gavin-jeong Apr 14, 2026
35a6c17
fix: use display width for conversation picker text
gavin-jeong Apr 14, 2026
4055e55
fix: add separators to standard conversation preview
gavin-jeong Apr 14, 2026
b36f82e
fix: separate text blocks in compact and standard previews
gavin-jeong Apr 14, 2026
c175df6
feat: preview focused artifacts in conversation tooltip
gavin-jeong Apr 14, 2026
61c53e1
feat: make standard preview artifacts selectable
gavin-jeong Apr 14, 2026
c2c765f
refactor: remove stale standard preview summary path
gavin-jeong Apr 14, 2026
851a147
fix: make standard artifact list passive
gavin-jeong Apr 14, 2026
ae79ff6
feat: show image details in focused artifact tooltip
gavin-jeong Apr 14, 2026
f3f6da1
feat: add Kitty inline image rendering in conversation tooltip
gavin-jeong Apr 15, 2026
751cff6
fix: improve Kitty detection and add tmux passthrough
gavin-jeong Apr 15, 2026
4e8aba7
fix: position Kitty image at tooltip location and clear on defocus
gavin-jeong Apr 15, 2026
153684d
fix: auto-detect Kitty inside tmux via server environment
gavin-jeong Apr 15, 2026
220cbf7
fix: align Kitty image with tooltip box and handle resize
gavin-jeong Apr 15, 2026
7ab6c1d
fix: clear Kitty images on program exit
gavin-jeong Apr 15, 2026
86ff232
fix: offset Kitty image by tmux pane position
gavin-jeong Apr 15, 2026
4185bce
fix: send cursor move inside tmux DCS passthrough
gavin-jeong Apr 15, 2026
94f5745
fix: cache tmux pane offset and extract images on focus
gavin-jeong Apr 15, 2026
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
Binary file added assets/team.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 50 additions & 1 deletion internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ var Commands = []struct {
}{
{"urls", "List URLs from the Claude session (interactive on TTY)"},
{"files", "List file paths touched by the session (interactive on TTY)"},
{"changes", "List file changes made by the session (interactive on TTY)"},
{"images", "List image paths from the session (interactive on TTY)"},
{"conversation", "List conversation turns from the Claude session (interactive on TTY)"},
{"help", "Show available commands and usage"},
}

Expand Down Expand Up @@ -52,11 +54,20 @@ func Run(command, claudeDir string, plain bool) (*RunResult, error) {
}

func runPlain(command, filePath, sessID, claudeDir string) error {
entries, err := session.LoadMessages(filePath)
if err != nil {
return err
}

switch command {
case "urls":
return printItems(extract.SessionURLs(filePath), "urls")
case "files":
return printItems(extract.SessionFilePaths(filePath), "files")
case "changes":
return printChanges(extract.SessionChanges(filePath))
case "conversation":
return printConversation(extractConversationWithContext(entries, sessID))
case "images":
return printImages(filePath, sessID, claudeDir)
default:
Expand All @@ -77,8 +88,12 @@ func runInteractive(command, filePath, sessID, claudeDir string) (*RunResult, er
items = extractURLsWithContext(entries, sessID)
case "files":
items = extractFilesWithContext(entries, sessID)
case "changes":
items = extractChangesWithContext(entries, sessID)
case "images":
items = extractImagesWithContext(entries, sessID, home)
case "conversation":
items = extractConversationWithContext(entries, sessID)
default:
return nil, fmt.Errorf("unknown command: %s", command)
}
Expand Down Expand Up @@ -117,7 +132,9 @@ func printHelp() {
fmt.Fprintf(os.Stderr, " ccx urls --plain Plain tab-separated output\n")
fmt.Fprintf(os.Stderr, " ccx urls | fzf Pipe to fzf (auto plain)\n")
fmt.Fprintf(os.Stderr, " ccx files Interactive file picker\n")
fmt.Fprintf(os.Stderr, " ccx images Interactive image picker\n\n")
fmt.Fprintf(os.Stderr, " ccx changes Interactive changed-files picker\n")
fmt.Fprintf(os.Stderr, " ccx images Interactive image picker\n")
fmt.Fprintf(os.Stderr, " ccx conversation Interactive conversation picker\n\n")
fmt.Fprintf(os.Stderr, "Picker keys:\n")
fmt.Fprintf(os.Stderr, " ↵ enter Jump to message in full ccx TUI\n")
fmt.Fprintf(os.Stderr, " o Open URL in browser\n")
Expand Down Expand Up @@ -149,6 +166,38 @@ func printItems(items []extract.Item, kind string) error {
return nil
}

func printChanges(items []extract.ChangeItem) error {
if len(items) == 0 {
return fmt.Errorf("no changes found in session")
}
for _, item := range items {
cat := ""
if len(item.ToolNames) > 0 {
cat = strings.ToUpper(item.ToolNames[0])
}
if len(cat) < 5 {
cat += strings.Repeat(" ", 5-len(cat))
}
fmt.Fprintf(os.Stdout, "%s\t%s\t%s\n", cat, item.Item.Label, item.Summary)
}
return nil
}

func printConversation(items []PickerItem) error {
if len(items) == 0 {
return fmt.Errorf("no conversation entries found in session")
}
for _, item := range items {
ref := item.FirstRef()
role := strings.ToUpper(ref.Role)
if len(role) < 5 {
role += strings.Repeat(" ", 5-len(role))
}
fmt.Fprintf(os.Stdout, "%s\t%s\t%s\n", role, item.Item.Label, ref.EntryUUID)
}
return nil
}

func printImages(filePath, sessID, claudeDir string) error {
entries, err := session.LoadMessages(filePath)
if err != nil {
Expand Down
Loading
Loading