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/vibeOr 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%s\033[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"
fichmod +x ~/.vibe/claude-statusline.sh
chmod +x ~/.vibe/claude-thinking-start.sh
chmod +x ~/.vibe/claude-thinking-stop.shAdd 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