Skip to content

Conversation

@sei40kr
Copy link

@sei40kr sei40kr commented Dec 18, 2025

Summary

Implements session (conversation history) selection and resume functionality for AI CLI tools in sidekick.nvim.

Users can now:

  • Browse previous CLI sessions with a unified picker (using snacks)
  • Resume sessions from any supported CLI tool
  • Sessions are displayed with aligned columns: [CLI] [relative time] Session Title
  • Sessions are sorted by last updated (newest first)
  • Use :Sidekick cli resume command or <leader>ar keybinding to select and resume a session

Implementation Status

Phase 1: Core Infrastructure & Claude Code support ✅

  • ✅ Session discovery for Claude Code
    • Path mapping: all non-alphanumeric characters (except -) replaced with -
    • Optimized: sort files by mtime first, read only top N files
    • Only reads type: "summary" session files
  • ✅ Unified session picker UI (snacks)
  • ✅ Resume session functionality
  • ✅ API: require("sidekick.cli").select_session()
  • ✅ Command: :Sidekick cli resume
  • ✅ Documentation updated (README, doc/sidekick.nvim.txt)

Phase 2: Multi-CLI Support ✅

  • ✅ OpenCode session discovery
    • Storage: ~/.local/share/opencode/storage/session/{global|<projectID>}/ses_*.json
    • Supports both git-managed projects and global sessions
    • Project metadata mapping: worktree field to project ID
    • Resume: opencode -s <session_id>
  • ✅ Codex session discovery
    • Storage: ~/.codex/sessions/**/rollout-*.jsonl (recursive search)
    • First line contains type: "session_meta" with session metadata
    • Resume: codex resume <session_id>

Phase 3: Additional CLIs ✅

  • ✅ Gemini session discovery
    • Storage: ~/.gemini/tmp/<project_hash>/chats/session-*.json
    • Project hash: SHA256 of CWD (calculated with vim.fn.sha256)
    • Title: First user message (max 80 chars, first line only)
    • Resume: gemini --resume <session_id>

UI Improvements ✅

  • ✅ Column-aligned picker display with proper padding
  • ✅ Humanized timestamps (e.g., "2 hours ago", "3 days ago")
  • ✅ Sort sessions by newest first (descending mtime)

Code Quality ✅

  • ✅ Modernized to use vim.uv and vim.fs APIs instead of vim.fn
  • ✅ Fixed LSP diagnostics (type annotations, redefined locals)
  • ✅ Consistent error handling with proper nil checks

Usage

Via Command

:Sidekick cli resume

Via Keybinding (Example)

{
  "<leader>ar",
  function() require("sidekick.cli").select_session() end,
  desc = "Resume CLI Session",
}

Via Lua API

-- With default options (show and focus terminal)
require("sidekick.cli").select_session()

-- With custom options
require("sidekick.cli").select_session({
  focus = false,  -- Don't focus terminal after resume
  show = true,    -- Show terminal (default: true)
  cwd = vim.fn.getcwd(),  -- Filter sessions by directory
})

-- With custom callback
require("sidekick.cli").select_session(function(session)
  if session then
    print("Resuming session: " .. session.id)
  end
end)

Technical Details

Data Model

---@class sidekick.cli.session.Info
---@field id string           -- Session ID for resuming
---@field title string         -- Session title
---@field updated number       -- Last update timestamp (Unix seconds)
---@field cli_name string      -- CLI tool name
---@field cwd string           -- Working directory

Discovery Function Signature

---@field discover_sessions? fun(cwd: string, limit: number): sidekick.cli.session.Info[]

Resume Implementation

Builds command: <cli_cmd> <resume_args> <session_id>

  • Claude: claude --resume <session_id>
  • OpenCode: opencode -s <session_id>
  • Codex: codex resume <session_id>
  • Gemini: gemini --resume <session_id>

Session Discovery Details

Claude:

  • Path: ~/.claude/projects/<project>/<uuid>.jsonl
  • Project name: CWD with non-alphanumeric chars (except -) replaced with -
  • First line: {"type": "summary", "summary": "..."}

OpenCode:

  • Path: ~/.local/share/opencode/storage/session/{global|<projectID>}/ses_*.json
  • Project metadata: ~/.local/share/opencode/storage/project/*.json
  • Git projects: mapped via worktree field in project metadata
  • Non-git projects: stored in global/ directory with directory field filter

Codex:

  • Path: ~/.codex/sessions/**/rollout-*.jsonl (recursive)
  • First line: {"type": "session_meta", "payload": {"id": "...", "cwd": "..."}}
  • Filters by payload.cwd matching current directory

Gemini:

  • Path: ~/.gemini/tmp/<project_hash>/chats/session-*.json
  • Project hash: SHA256 of absolute CWD path (vim.fn.sha256(cwd))
  • Session structure: Full JSON with sessionId, messages array
  • Title extraction: First user message content (truncated to 80 chars)
  • Uses file mtime for consistency with other CLIs

Test Plan

  • Test Claude session discovery and resume
  • Test OpenCode session discovery (git and non-git projects)
  • Test Codex session discovery
  • Test Gemini session discovery and project hash calculation
  • Test picker UI with multiple CLIs
  • Test column alignment and timestamps
  • Test session sorting (newest first)
  • Test :Sidekick cli resume command
  • Update README and documentation

sei40kr and others added 2 commits December 18, 2025 23:38
- Add session discovery for Claude CLI
- Implement session picker UI with snacks
- Add resume session functionality
- Path mapping: both / and . replaced with -
- Optimize: sort by mtime, read only top N files
- Sessions sorted ascending (for snacks picker display)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Replace all non-alphanumeric characters (except -) with -
- Example: test_with.symbols+chars@123 -> test-with-symbols-chars-123
- Matches Claude Code's actual path sanitization behavior

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@github-actions github-actions bot added the size/xl Extra large PR (100+ lines changed) label Dec 18, 2025
@sei40kr
Copy link
Author

sei40kr commented Dec 19, 2025

Naming Conflict Issue

The current implementation uses session to refer to AI conversation history, which conflicts with the existing lua/sidekick/cli/session/ directory that manages multiplexer sessions (tmux, zellij).

Current naming:

  • lua/sidekick/cli/sessions.lua - AI conversation history management
  • lua/sidekick/cli/session/ - Multiplexer session management

Possible alternatives for AI conversation history:

  1. history

    • lua/sidekick/cli/history.lua
    • API: require("sidekick.cli").select_history()
    • Simple and generic
  2. conversation

    • lua/sidekick/cli/conversations.lua
    • API: require("sidekick.cli").select_conversation()
    • Most explicit about AI dialog
  3. chat

    • lua/sidekick/cli/chats.lua
    • API: require("sidekick.cli").select_chat()
    • Casual and intuitive
  4. transcript

    • lua/sidekick/cli/transcripts.lua
    • API: require("sidekick.cli").select_transcript()
    • Technically accurate but formal

Feedback welcome on which naming would be clearest.

@sei40kr sei40kr changed the title feat: add CLI session selection and resume functionality feat(cli): add session selection and resume functionality Dec 19, 2025
…ve timestamps

- Add humanize_time function for relative time display (e.g., "2 hours ago")
- Implement column padding for CLI names and timestamps
- Update M.format to support padded columns for better readability
- Calculate max widths for CLI names and time strings for alignment
- Implement discover_sessions with git project and global support
- Use vim.iter for functional-style data processing
- Add resume configuration with -s flag
- Support both project-specific and global session directories
Replace vim.fn functions with modern vim.uv and vim.fs equivalents for better performance and consistency. This includes fs_scandir for directory traversal, fs_stat for file metadata, fs_open/read/close for file reading, and json.decode for JSON parsing.
When resuming a session:
- Close existing sessions of the same CLI tool
- Hide terminals of different CLI tools that are currently open
- Show new session normally if no conflicts
Add <leader>ar keybinding for session resume to match README
@sei40kr sei40kr marked this pull request as ready for review December 21, 2025 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl Extra large PR (100+ lines changed)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant