@tejgor/deckhand
v0.1.3
Published
A lightweight agent workbench for your IDE terminal.
Maintainers
Readme
Deckhand
A lightweight agent workbench for your IDE terminal.
Status: early/experimental. Behavior, on-disk state, and the daemon IPC protocol may change between versions.
- Start
claude,pi, orcodexsessions from one UI - Preview output without attaching to every session
- Attach/detach while sessions keep running in the background
- Pick a worktree mode per session: none, new, or existing
- Designed to work well in IDE integrated terminals
Context
Tools like claude-squad and agent-deck manage parallel agent work with tmux and git worktrees. Deckhand uses a different stack with no tmux dependency: an Ink UI, a local daemon, and node-pty workers.
Features
- Split view with session sidebar and Preview / Terminal / Git / Dev tabs
- Live previews of session output without attaching
- Sessions persist across UI quits and crashes — the daemon owns them
- Cleanup prompts after a session exits to remove its worktree and branch
- Merge helpers to merge or squash-merge a session's worktree into the current branch (staged, not committed) for review
- Optional Git tab powered by
lazygit - Configurable Dev tab for a command such as
npm run dev
Requirements
- Node.js
>= 20 - macOS or Linux
- A POSIX shell
gitavailable onPATHclaude,pi, and/orcodexavailable onPATH, depending on which agents you want to run- Optional:
lazygitonPATHfor the Git tab
Installation
Install from npm:
npm install -g @tejgor/deckhandThen run:
deckhandFor local development, install from source:
git clone https://github.com/tejgor/deckhand.git
cd deckhand
npm install
npm run build
npm linkAfter changing source code, rebuild before re-running the linked CLI:
npm run buildThe CLI binary is declared in package.json as deckhand → dist/cli.js.
macOS note:
npm installrunsscripts/fix-node-pty.js, which best-effort repairs thenode-ptyspawn-helperbinary (executable bit,xattr, ad-hoccodesign). See Troubleshooting if install fails.
Quick start
From inside a git repository:
deckhandThen:
- Press
nto create a new session - Choose
claude,pi, orcodex - Enter a session name
- Press
tabto choose a workspace mode: no worktree, new worktree, or existing worktree - Press
enterto launch
Use j / k to move between sessions and o to attach to the selected session.
Controls
Main view
| Key | Action |
| --- | --- |
| n | New session |
| tab | Cycle Preview / Terminal / Git / Dev tabs |
| o | Attach to the selected session, opening whichever tab is currently shown |
| d | Start/stop the selected session's Dev tab command |
| m | Merge selected worktree into the current branch |
| j / k | Move between sessions |
| h / l | Resize the sidebar |
| x / X | Kill selected running session / force kill |
| s | Restart selected exited session |
| backspace | Drop selected exited session from the list |
| r | Refresh session list |
| ? | Show keyboard shortcuts |
| q | Quit the UI; running sessions continue in the daemon |
Worktree sessions may prompt to keep/delete the worktree, or delete both the managed worktree and branch when it is safe to do so.
Attach mode
| Key | Action |
| --- | --- |
| (any) | Sent directly to the attached pane/session |
| Ctrl+Space | Detach and return to Deckhand |
Workspace modes
When creating a session, Deckhand can launch it in one of three workspace modes:
| Mode | Behavior | | --- | --- | | No worktree | Run in the current repository directory | | New worktree | Create or resolve a worktree for the session | | Existing worktree | Pick from existing git worktrees, including the current/main one |
New worktrees are created through a project hook when present; otherwise Deckhand falls back to git worktree add under ~/.deckhand/worktrees/.
Configuration
Deckhand reads configuration from ~/.deckhand/config.json.
Dev command
The Dev tab is started explicitly with d. Configure the global command like this:
{
"dev_command": "npm run dev"
}Set dev_command before pressing d. If omitted, Deckhand tries to runs a dev alias.
Attach scroll sensitivity
Attached sessions dampen trackpad/mouse-wheel scrolling. Configure the multiplier like this:
{
"attach_scroll_sensitivity": 0.25
}Use 1 for normal terminal scrolling, lower values for slower scrolling, or 0 to ignore vertical wheel events while attached. Default: 0.25.
State and logs
| Path | Purpose |
| --- | --- |
| ~/.deckhand/state.json | Persisted session list |
| ~/.deckhand/config.json | User configuration |
| ~/.deckhand/daemon.log | Supervisor daemon diagnostics |
| ~/.deckhand/daemon.pid | Active supervisor daemon PID |
| ~/.deckhand/daemon.sock | Local IPC socket |
| ~/.deckhand/workers/ | Per-session worker PID/log files |
| ~/.deckhand/worktrees/ | Default location for auto-created worktrees |
Worktree hooks
For new-worktree sessions, Deckhand first creates or resolves a git worktree, then starts the agent inside it. Deckhand uses this project hook if present; otherwise it falls back to git worktree add:
.claude/scripts/create-worktree.shHook contract
- Read JSON from
stdin - Use
nameas the sanitized worktree/session name - Use
cwdas the directory wheredeckhandwas launched - Create or register a git worktree
- Print the absolute worktree path to
stdoutas the final non-empty line - Exit
0on success
Deckhand also sets CLAUDE_PROJECT_DIR to the launch cwd for compatibility with Claude-style hooks.
Input example
{ "name": "my_feature", "cwd": "/path/to/current/worktree" }#!/bin/bash
set -e
INPUT="$(cat)"
NAME="$(echo "$INPUT" | jq -r '.name // "worktree"')"
CWD="$(echo "$INPUT" | jq -r '.cwd // env.CLAUDE_PROJECT_DIR // env.PWD')"
DIR="$HOME/.deckhand/worktrees/$NAME"
START="$(git -C "$CWD" rev-parse HEAD)"
if [ -d "$DIR/.git" ] || git -C "$DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "$DIR"
exit 0
fi
if [ -e "$DIR" ]; then
echo "Path exists but is not a git worktree: $DIR" >&2
exit 1
fi
if git -C "$CWD" show-ref --verify --quiet "refs/heads/$NAME"; then
git -C "$CWD" worktree add "$DIR" "$NAME" >&2
else
git -C "$CWD" worktree add -b "$NAME" "$DIR" "$START" >&2
fi
echo "$DIR"If your hook copies symlinks from the source worktree, prefer resolving them with realpath so newly-created worktrees point directly at the real target instead of through another linked worktree.
Architecture
Deckhand has three main pieces:
- Ink frontend — renders the terminal UI, sends requests to the daemon, and attaches to live panes when requested.
- Local daemon — owns session state, IPC, worktree operations, and worker supervision.
- Session workers — one worker per running session; each worker owns the agent PTY plus optional Terminal / Git / Dev PTYs.
Terminal output is fed into a headless xterm.js model. The UI receives rendered snapshots for previews, while attach mode streams input/output directly between your terminal and the selected PTY.
The UI can quit or crash without taking sessions down; the daemon owns them.
Daemon lifecycle
Deckhand spawns a long-lived supervisor daemon the first time you launch the UI. Quitting the UI with q leaves the daemon (and any running sessions) in place; relaunching deckhand reattaches. Stopping the daemon kills all running sessions.
| Action | How |
| --- | --- |
| Check whether the daemon is running | pgrep -F ~/.deckhand/daemon.pid |
| Tail daemon logs | tail -f ~/.deckhand/daemon.log |
| Stop the daemon (and all sessions) | kill $(cat ~/.deckhand/daemon.pid) |
| Recover from a crashed daemon | Remove ~/.deckhand/daemon.pid and ~/.deckhand/daemon.sock, then relaunch |
Development
npm install
npm run dev # run from source via tsx
npm run daemon # run only the daemon in dev mode
npm run build # compile to dist/Useful checks before linking a build:
npm run build
npm pack --dry-run --ignore-scriptsTroubleshooting
deckhand can't find an agent. Confirm the binary you want is on PATH: which claude, which pi, which codex. Deckhand inherits the launching shell's environment.
node-pty fails to load on macOS (e.g. spawn-helper permission errors). Re-run the repair script directly:
node scripts/fix-node-pty.jsIf that doesn't help, reinstall: rm -rf node_modules && npm install.
Stale daemon socket / PID. If deckhand hangs at startup or reports it can't reach the daemon, the supervisor may have exited uncleanly. Remove the stale files and relaunch:
rm -f ~/.deckhand/daemon.pid ~/.deckhand/daemon.sock
deckhandGit tab is empty. Install lazygit and ensure it is on PATH.
Dev tab does nothing. The Dev command is started explicitly with d. If ~/.deckhand/config.json does not set dev_command, Deckhand runs the literal command dev, which usually doesn't exist.
Uninstall
npm unlink -g deckhandTo remove all local state (sessions, logs, auto-created worktrees):
rm -rf ~/.deckhandIf Deckhand created git worktrees under ~/.deckhand/worktrees/, prefer removing them through the UI (or git worktree remove) before deleting the directory, so git's bookkeeping stays consistent.
Contributing
Issues and pull requests are welcome. For larger changes, please open an issue first to discuss the approach. Run npm run build and confirm the CLI launches before sending a PR.
License
Deckhand is released under the MIT License.
