Terminal-based kanban board for managing Claude Code sessions with git worktrees.
- Kanban board - organize tasks across Backlog, In Progress, In Review, and Done columns
- Git worktree integration - each task gets its own worktree via worktrunk
- Zellij sessions - launch and manage Claude Code (or Codex) sessions per task
- Linear sync - import issues from Linear backlog
- PR status tracking - see PR state, merge conflicts, and CI status inline
- Claude activity monitoring - see when Claude is thinking, idle, or waiting for input
If you have Claude Code installed, run:
claude --dangerously-skip-permissions \
"$(curl -fsSL https://raw.githubusercontent.com/piotrostr/vibe/main/setup-prompt.md)"This will detect your OS, install dependencies, configure Zellij, set up the Claude statusline, and install vibe.
- Claude Code or Codex - the AI coding assistant this tool orchestrates
- Rust - for building vibe and worktrunk
cargo install --git https://github.com/piotrostr/vibe
Or build from source:
git clone https://github.com/piotrostr/vibe cd vibe cargo install --path .
- worktrunk (
wt) - git worktree manager - zellij - terminal multiplexer for assistant sessions
- neovim - used as Zellij's scrollback editor
- gh (optional) - GitHub CLI for PR status
Run vibe in any git repository:
cd your-project
vibeUse Codex instead of Claude Code:
vibe --codex
| Key | Action |
|---|---|
j/k |
Navigate up/down |
h/l |
Switch columns |
J/K |
Move task between columns |
g |
Launch coding session for task |
p |
Launch with plan mode |
Enter |
View task details |
c |
Create new task |
e |
Edit task |
d |
Delete task |
v |
Open PR in browser |
w |
View worktrees |
S |
View sessions |
/ |
Search tasks |
? |
Help |
q |
Quit |
Tasks are stored as markdown files with YAML frontmatter:
~/.vibe/projects/{project-name}/tasks/
Example task file:
--- id: 550e8400-e29b-41d4-a716-446655440000 linear_id: TEAM-123 created: 2024年01月15日 --- # Implement user authentication Add OAuth2 login flow with Google and GitHub providers.
Logs are written to ~/.vibe/vibe.log. Set RUST_LOG=info for verbose logging.
For Linear integration, set LINEAR_API_KEY environment variable.
Vibe works best with a minimal Zellij config. Example ~/.config/zellij/config.kdl:
default_mode "locked" default_layout "vibe" pane_frames false simplified_ui true show_startup_tips false show_release_notes false copy_command "pbcopy" copy_on_select false scrollback_editor "nvim" theme "gruvbox-dark" themes { gruvbox-dark { fg "#ebdbb2" bg "#282828" black "#282828" red "#cc241d" green "#98971a" yellow "#d79921" blue "#458588" magenta "#b16286" cyan "#689d6a" white "#a89984" orange "#d65d0e" } } keybinds clear-defaults=true { locked { bind "Ctrl b" { SwitchToMode "Normal"; } bind "Ctrl d" { Detach; } } normal { bind "Ctrl b" { SwitchToMode "Locked"; } bind "Esc" { SwitchToMode "Locked"; } bind "d" { Detach; } bind "[" { SwitchToMode "Scroll"; } bind "s" { NewPane "Down"; SwitchToMode "Locked"; } bind "v" { NewPane "Right"; SwitchToMode "Locked"; } bind "c" { NewTab; SwitchToMode "Locked"; } bind "n" { GoToNextTab; SwitchToMode "Locked"; } bind "p" { GoToPreviousTab; SwitchToMode "Locked"; } bind "h" { MoveFocus "Left"; SwitchToMode "Locked"; } bind "j" { MoveFocus "Down"; SwitchToMode "Locked"; } bind "k" { MoveFocus "Up"; SwitchToMode "Locked"; } bind "l" { MoveFocus "Right"; SwitchToMode "Locked"; } bind "x" { CloseFocus; SwitchToMode "Locked"; } bind "z" { ToggleFocusFullscreen; SwitchToMode "Locked"; } } scroll { bind "j" "Down" { ScrollDown; } bind "k" "Up" { ScrollUp; } bind "d" "Ctrl d" { HalfPageScrollDown; } bind "u" "Ctrl u" { HalfPageScrollUp; } bind "g" { ScrollToTop; } bind "G" { ScrollToBottom; } bind "/" { SwitchToMode "EnterSearch"; SearchInput 0; } bind "Esc" "q" { SwitchToMode "Locked"; } bind "e" { EditScrollback; SwitchToMode "Locked"; } } search { bind "n" { Search "down"; } bind "N" { Search "up"; } bind "Esc" { SwitchToMode "Scroll"; } } entersearch { bind "Enter" { SwitchToMode "Search"; } bind "Esc" { SwitchToMode "Scroll"; } } }
Create a minimal layout at ~/.config/zellij/layouts/vibe.kdl:
layout { default_tab_template { pane size=1 borderless=true { plugin location="compact-bar" } children } }
Key bindings (tmux-like with Ctrl+b prefix):
Ctrl+b- enter command modeCtrl+d- detach sessionCtrl+b [- scroll mode (vim keys,/to search)Ctrl+b s/v- split horizontal/verticalCtrl+b h/j/k/l- navigate panesCtrl+b c/n/p- new tab / next tab / previous tab
For real-time Claude session status indicators (thinking/waiting/idle) and context window usage, configure Claude Code's statusline and hooks.
Create ~/.vibe/claude-statusline.sh:
#!/bin/bash STATE_DIR="$HOME/.vibe/claude-activity" mkdir -p "$STATE_DIR" input=$(cat) working_dir=$(echo "$input" | jq -r '.workspace.current_dir // empty') session_id=$(echo "$input" | jq -r '.session_id // empty') input_tokens=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // "null"') output_tokens=$(echo "$input" | jq -r '.context_window.current_usage.output_tokens // "null"') used_pct=$(echo "$input" | jq -r '.context_window.used_percentage // "null"') api_duration_ms=$(echo "$input" | jq -r '.cost.total_api_duration_ms // "null"') if [ -n "$working_dir" ]; then dir_hash=$(echo -n "$working_dir" | md5 | cut -c1-16) cat > "$STATE_DIR/$dir_hash.json" << EOF {"working_dir":"$working_dir","session_id":"$session_id","input_tokens":$input_tokens,"output_tokens":$output_tokens,"used_percentage":$used_pct,"api_duration_ms":$api_duration_ms,"timestamp":$(date +%s)} EOF fi # Optional: display git branch cd "$working_dir" 2>/dev/null || true branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) [ -n "$branch" ] && printf '033円[33mgit:033円[31m%s033円[0m' "$branch"
For instant thinking detection (spinner appears the moment you send a message), create two hook scripts:
~/.vibe/claude-thinking-start.sh - fires when you submit a prompt:
#!/bin/bash STATE_DIR="$HOME/.vibe/claude-activity" input=$(cat) working_dir=$(echo "$input" | jq -r '.cwd // empty') if [ -n "$working_dir" ]; then dir_hash=$(echo -n "$working_dir" | md5 | cut -c1-16) mkdir -p "$STATE_DIR" touch "$STATE_DIR/$dir_hash.thinking" fi
~/.vibe/claude-thinking-stop.sh - fires when Claude finishes responding:
#!/bin/bash STATE_DIR="$HOME/.vibe/claude-activity" input=$(cat) working_dir=$(echo "$input" | jq -r '.cwd // empty') if [ -n "$working_dir" ]; then dir_hash=$(echo -n "$working_dir" | md5 | cut -c1-16) rm -f "$STATE_DIR/$dir_hash.thinking" fi
chmod +x ~/.vibe/claude-statusline.sh chmod +x ~/.vibe/claude-thinking-start.sh chmod +x ~/.vibe/claude-thinking-stop.sh
Add to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "~/.vibe/claude-statusline.sh"
},
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "~/.vibe/claude-thinking-start.sh"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "~/.vibe/claude-thinking-stop.sh"
}
]
}
]
}
}[*](blue, animated) - Claude is actively thinking[?](yellow) - Claude is waiting for user input- No indicator - Session idle
25%(gray/yellow/red) - Context window usage (red when >90%)
MIT