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
8 changes: 3 additions & 5 deletions Deckard.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@
QA200006QA200006QA200006 /* QuotaMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = QA200005QA200005QA200005 /* QuotaMonitorTests.swift */; };
SE000001SE000001SE000001 /* SessionExplorerModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = SE000002SE000002SE000002 /* SessionExplorerModels.swift */; };
SE000003SE000003SE000003 /* BookmarkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = SE000004SE000004SE000004 /* BookmarkManager.swift */; };
SE000005SE000005SE000005 /* SummaryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = SE000006SE000006SE000006 /* SummaryManager.swift */; };
SE000007SE000007SE000007 /* SessionExplorerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = SE000008SE000008SE000008 /* SessionExplorerWindowController.swift */; };
SE000009SE000009SE000009 /* SessionExplorerTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = SE00000ASE00000ASE00000A /* SessionExplorerTimelineView.swift */; };
CF000001CF000001CF000001 /* ClaudeCLIFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF000002CF000002CF000002 /* ClaudeCLIFlags.swift */; };
Expand Down Expand Up @@ -124,7 +123,6 @@
QA200005QA200005QA200005 /* QuotaMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuotaMonitorTests.swift; sourceTree = "<group>"; };
SE000002SE000002SE000002 /* SessionExplorerModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionExplorerModels.swift; sourceTree = "<group>"; };
SE000004SE000004SE000004 /* BookmarkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkManager.swift; sourceTree = "<group>"; };
SE000006SE000006SE000006 /* SummaryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SummaryManager.swift; sourceTree = "<group>"; };
SE000008SE000008SE000008 /* SessionExplorerWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionExplorerWindowController.swift; sourceTree = "<group>"; };
SE00000ASE00000ASE00000A /* SessionExplorerTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionExplorerTimelineView.swift; sourceTree = "<group>"; };
CF000002CF000002CF000002 /* ClaudeCLIFlags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClaudeCLIFlags.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -230,7 +228,6 @@
7BE2AA0719D32ACCD1549184 /* SessionState.swift */,
SE000002SE000002SE000002 /* SessionExplorerModels.swift */,
SE000004SE000004SE000004 /* BookmarkManager.swift */,
SE000006SE000006SE000006 /* SummaryManager.swift */,
SE000008SE000008SE000008 /* SessionExplorerWindowController.swift */,
SE00000ASE00000ASE00000A /* SessionExplorerTimelineView.swift */,
);
Expand Down Expand Up @@ -447,7 +444,6 @@
QA200004QA200004QA200004 /* QuotaView.swift in Sources */,
SE000001SE000001SE000001 /* SessionExplorerModels.swift in Sources */,
SE000003SE000003SE000003 /* BookmarkManager.swift in Sources */,
SE000005SE000005SE000005 /* SummaryManager.swift in Sources */,
SE000007SE000007SE000007 /* SessionExplorerWindowController.swift in Sources */,
SE000009SE000009SE000009 /* SessionExplorerTimelineView.swift in Sources */,
CF000001CF000001CF000001 /* ClaudeCLIFlags.swift in Sources */,
Expand Down Expand Up @@ -648,7 +644,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = Resources/Deckard.entitlements;
CODE_SIGN_IDENTITY = "Developer ID Application: Gilles Dubuc (TATY79TCRY)";
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = TATY79TCRY;
COMBINE_HIDPI_IMAGES = YES;
Expand All @@ -670,6 +666,8 @@
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down
102 changes: 75 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Deckard

A terminal built for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). Deckard is a native macOS app that treats Claude Code sessions as first-class objects. Each tab knows whether Claude is thinking, waiting for input, or needs tool approval, and tracks context window usage so you know when a session is running low.
A native macOS workspace for [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [OpenAI Codex](https://openai.com/codex), and classic terminal tabs. Deckard treats agent sessions as first-class tabs: Claude Code and Codex can both be created, resumed, forked, explored, bookmarked, and restored across app launches.

Run multiple sessions side by side in a single window with tabs, projects, and session persistence. Built with Swift and AppKit. Terminal rendering powered by [SwiftTerm](https://github.com/migueldeicaza/SwiftTerm).
Run multiple agents side by side in a single window with project-aware tabs, session persistence, status badges, and usage telemetry when the underlying CLI exposes it. Built with Swift and AppKit. Terminal rendering is powered by [SwiftTerm](https://github.com/migueldeicaza/SwiftTerm).

<p align="center">
<a href="https://github.com/gi11es/deckard/releases/latest/download/Deckard.dmg">
Expand All @@ -19,51 +19,80 @@ Run multiple sessions side by side in a single window with tabs, projects, and s

## Features

### Multi-tab sessions
### Claude, Codex, and Terminal Tabs

Open multiple Claude Code (and plain terminal) tabs per project. Switch between them with Cmd+19 or drag to reorder.
Open Claude Code, Codex, and plain terminal tabs inside the same project workspace. Agent tabs launch the right CLI directly, while terminal tabs remain normal shells. Switch between tabs with Cmd+1-9 or drag tabs to reorder them.

<img src="docs/images/screenshot-tabs.webp?v=cdf54ee7" alt="Tab bar with Claude and terminal tabs" width="600">
<img src="docs/images/screenshot-tabs.webp?v=cdf54ee7" alt="Tab bar with agent and terminal tabs" width="600">

### Project sidebar
### Project Sidebar

Each open directory gets its own set of tabs, persisted across restarts. Group related projects into collapsible sidebar folders for organization (e.g., by client).
Each open directory gets its own persisted tab set. Group related projects into collapsible sidebar folders for organization, and keep different agent runs attached to the project they belong to.

<img src="docs/images/screenshot-sidebar.webp?v=ff3961fc" alt="Project sidebar with folders" width="280">

### Session state detection
### Provider-Specific Status Badges

Tab badges show whether Claude is thinking, waiting for input, needs tool permission, or has errored. Terminal tabs show real-time CPU and disk activity for the foreground process.
Claude Code and Codex have separate badge states and customizable colors. Claude badges use Deckard's Claude hooks to show thinking, waiting, permission, error, and done-unvisited states. Codex badges are read from Codex rollout events and show idle, working, error, and done-unvisited states. Terminal tabs use their own process-activity badges.

<img src="docs/images/screenshot-status-indicators.webp?v=42af91f8" alt="Status indicator dots" width="250">

### Session explorer
### Session Explorer

Browse all past Claude sessions with Cmd+Shift+E. View the full conversation timeline, resume or fork from any point, and bookmark favorites with a star toggle. Optionally summarize sessions and per-turn actions with Haiku — summaries are cached and incrementally updated when sessions are continued.
Browse past Claude Code and Codex sessions with Cmd+Shift+E. The explorer lists both providers for the current project, lets you resume or fork any session, and supports bookmark stars and timeline views.

<img src="docs/images/screenshot-session-explorer.webp?v=03aa1cca" alt="Session explorer window" width="600">
Fork-at-turn works for both agent providers. For Claude Code, Deckard truncates the Claude session JSONL and resumes with Claude's fork support. For Codex, Deckard creates a truncated Codex rollout file, registers it with Codex's local state database, and launches `codex fork` or `codex resume` as appropriate.

### 486 color themes
<img src="docs/images/screenshot-session-explorer.webp?v=03aa1cca" alt="Session explorer window" width="600">

Ships with 486 built-in themes (Ghostty format) and loads custom themes from `~/.config/ghostty/themes`. Search and preview in Settings. Status indicator shapes, colors, and blink are fully customizable.
### Context, Quota, and Token Rate

<img src="docs/images/screenshot-themes.webp?v=1fb04fe4" alt="Theme settings with status indicators" width="500">
Agent usage stats appear only on tabs where Deckard can read real provider data.

### Context & quota tracking
| Metric | Claude Code tabs | Codex tabs | Terminal tabs |
| --- | --- | --- | --- |
| Context usage bar | Yes, from Claude session usage entries | No reliable local signal; hidden | No |
| 5-hour quota | Yes, from Claude status-line hook data | Yes, from Codex app-server rate-limit data with rollout fallback | No |
| 7-day quota | Yes, from Claude status-line hook data | Yes, from Codex app-server rate-limit data with rollout fallback | No |
| Tokens per minute | Yes, from recent Claude output token usage | Yes, from recent Codex generated token usage | No |

A progress bar shows context window usage. A sparkline visualizes token rate over time, and rate limit indicators show 5-hour and 7-day quota consumption.
Classic terminal tabs intentionally do not show agent context, quota, or token-rate panels.

<img src="docs/images/screenshot-context-tracking.png?v=d0e0045b" alt="Context and quota tracking popover" width="250">

### 486 Color Themes

Ships with 486 built-in themes in Ghostty format and loads custom themes from `~/.config/ghostty/themes`. Search and preview in Settings. Status indicator shapes, colors, and blink behavior are fully customizable per provider.

<img src="docs/images/screenshot-themes.webp?v=1fb04fe4" alt="Theme settings with status indicators" width="500">

### More

- **Session persistence**: Claude sessions resume via `--resume`. Tab structure and working directories are preserved across restarts.
- **Customizable shortcuts**: All keyboard shortcuts are rebindable in Settings > Shortcuts.
- **tmux integration**: When tmux is installed, terminal tabs are transparently wrapped in tmux sessions. Quit and relaunch Deckard to resume exactly where you left off — full shell state, scrollback, running processes, and environment preserved. tmux options are editable in Settings > Terminal. Works as a progressive enhancement; no tmux required.
- **Drag and drop**: Drag files from Finder into the terminal — paths are automatically shell-escaped and inserted.
- **Session persistence**: Claude Code sessions resume with `claude --resume`; Codex sessions resume with `codex resume`. Tab structure and working directories are preserved across restarts.
- **Forking workflows**: Claude Code and Codex sessions can be forked from the explorer, including from a specific user turn.
- **Bookmarks**: Claude Code and Codex sessions use separate bookmark caches so provider sessions with similar IDs do not collide.
- **Customizable shortcuts**: All keyboard shortcuts are rebindable in Settings > Shortcuts, including new Claude, Codex, and terminal tab commands.
- **tmux integration**: When tmux is installed, classic terminal tabs are transparently wrapped in tmux sessions. Quit and relaunch Deckard to resume shell state, scrollback, running processes, and environment. tmux options are editable in Settings > Terminal.
- **Drag and drop**: Drag files from Finder into any terminal surface. Paths are shell-escaped and inserted automatically.
- **Auto-updates**: Built-in update checking via [Sparkle](https://sparkle-project.org/). New releases are delivered automatically.
- **Terminal rendering**: Powered by [SwiftTerm](https://github.com/migueldeicaza/SwiftTerm), a self-contained terminal emulator with VT100/xterm emulation, IME support, and mouse reporting.

## Agent Support Matrix

| Workflow | Claude Code | Codex |
| --- | --- | --- |
| Create new agent tab | Yes | Yes |
| Resume existing session | Yes | Yes |
| Fork existing session | Yes | Yes |
| Fork from a specific turn | Yes | Yes |
| Session explorer listing | Yes | Yes |
| Timeline and action view | Yes | Yes |
| Bookmarks | Yes | Yes |
| Provider-specific badges | Yes | Yes |
| Context, quota, token rate | Yes | Quota via Codex app-server; token rate from `token_count` events when present |

Deckard aims for equal day-to-day workflows across Claude Code and Codex. Some telemetry is necessarily provider-specific because the CLIs expose different local data. When Deckard cannot read a metric reliably, it hides that metric instead of showing stale data from another tab or provider.

## Install

**Homebrew:**
Expand All @@ -77,8 +106,11 @@ brew install gi11es/tap/deckard
## Requirements

- macOS 14.0 (Sonoma) or later
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI installed
- Xcode 16+ (to build from source)
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI installed to use Claude tabs
- [Codex CLI](https://openai.com/codex/get-started/) installed to use Codex tabs
- Xcode 16+ to build from source

Deckard can be used with only Claude Code, only Codex, or both installed. Terminal tabs work without either agent CLI.

## Building

Expand All @@ -94,12 +126,28 @@ The built app will be in your Xcode DerivedData directory.

## How It Works

On launch, Deckard automatically installs two integrations into Claude Code (no manual setup needed):
Deckard integrates with each provider using the local state that provider already writes.

**Claude Code**

On launch, Deckard installs Claude Code integrations idempotently:

1. **Lifecycle hooks**: a shell script and entries in `~/.claude/settings.json` notify Deckard when Claude starts thinking, finishes a response, needs tool approval, encounters an error, or emits status-line quota data. Communication happens over a Unix domain socket.
2. **`/deckard` skill**: a Claude Code slash command at `~/.claude/commands/deckard.md` for filing bug reports and feature requests directly from a session.

Deckard reads Claude session JSONL files under `~/.claude/projects` for session discovery, timelines, context usage, resume, and fork-at-turn.

**Codex**

Deckard reads Codex rollout files under `~/.codex/sessions` and the local Codex state database at `~/.codex/state_5.sqlite`. That provides project-scoped session discovery, resume, fork, fork-at-turn, timeline parsing, badges, and token-rate calculation when Codex has written the corresponding events.

For Codex quota, Deckard keeps a local `codex app-server --listen stdio://` JSON-RPC connection open while needed and calls `account/rateLimits/read`, falling back to rollout `token_count` rate-limit events if the app-server data is unavailable.

Deckard does not install Codex hooks. It launches the Codex CLI directly with `codex`, `codex resume`, or `codex fork`.

1. **Lifecycle hooks** — a shell script and entries in `~/.claude/settings.json` that notify Deckard when Claude starts thinking, finishes a response, needs tool approval, or encounters an error. Communication happens over a Unix domain socket.
2. **`/deckard` skill** — a Claude Code slash command (`~/.claude/commands/deckard.md`) for filing bug reports and feature requests directly from a session.
**Terminal**

These are installed idempotently on every launch and don't modify Claude Code itself.
Classic terminal tabs are normal shells, optionally tmux-backed. They do not participate in agent session discovery and do not show agent context, quota, or token-rate telemetry.

## License

Expand Down
23 changes: 19 additions & 4 deletions Sources/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
nc.addObserver(self, selector: #selector(handleSurfaceClosed(_:)), name: .deckardSurfaceClosed, object: nil)
nc.addObserver(self, selector: #selector(handleTitleChanged(_:)), name: .deckardSurfaceTitleChanged, object: nil)
nc.addObserver(self, selector: #selector(handleNewTab), name: .deckardNewTab, object: nil)
nc.addObserver(self, selector: #selector(handleNewCodexTab), name: .deckardNewCodexTab, object: nil)
nc.addObserver(self, selector: #selector(handleCloseTab), name: .deckardCloseTab, object: nil)

// Start the control socket for hook communication.
Expand All @@ -60,9 +61,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
log.log("startup", "Installing Claude Code hooks...")
DeckardHooksInstaller.installIfNeeded()

// Parse Claude CLI flags for autocomplete in settings.
// Parse agent CLI flags for autocomplete in settings.
log.log("startup", "Loading Claude CLI flags...")
ClaudeCLIFlags.shared.load()
log.log("startup", "Loading Codex CLI flags...")
CodexCLIFlags.shared.load()

// Clean up orphaned tmux sessions from previous runs
if TerminalSurface.tmuxAvailable {
Expand Down Expand Up @@ -148,7 +151,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}

@objc private func handleNewTab() {
windowController?.addTabToCurrentProject(isClaude: true)
windowController?.addTabToCurrentProject(kind: .claude)
}

@objc private func handleNewCodexTab() {
windowController?.addTabToCurrentProject(kind: .codex)
}

@objc private func handleCloseTab() {
Expand Down Expand Up @@ -193,6 +200,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
claudeItem.setShortcut(for: .newClaudeTab)
fileMenu.addItem(claudeItem)

let codexItem = NSMenuItem(title: "New Codex Tab", action: #selector(newCodexTab), keyEquivalent: "")
codexItem.setShortcut(for: .newCodexTab)
fileMenu.addItem(codexItem)

let termItem = NSMenuItem(title: "New Terminal Tab", action: #selector(newTerminalTab), keyEquivalent: "")
termItem.setShortcut(for: .newTerminalTab)
fileMenu.addItem(termItem)
Expand Down Expand Up @@ -298,11 +309,15 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}

@objc private func newClaudeTab() {
windowController?.addTabToCurrentProject(isClaude: true)
windowController?.addTabToCurrentProject(kind: .claude)
}

@objc private func newCodexTab() {
windowController?.addTabToCurrentProject(kind: .codex)
}

@objc private func newTerminalTab() {
windowController?.addTabToCurrentProject(isClaude: false)
windowController?.addTabToCurrentProject(kind: .terminal)
}

@objc private func closeCurrentTab() {
Expand Down
Loading
Loading