Skip to content

Latest commit

 

History

357 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dotfiles

Terminal configuration with fast shell startup (~98ms), modular zsh config, modern CLI tools, and a project-workspace workflow built on Ghostty + tmux + yazi with Claude Code integration.

Quick Start

This machine is set up declaratively with kempt — the config lives in kempt.toml, and kempt installs, verifies, and updates everything from it. You read the whole plan before anything runs.

1. Install kempt, then set up this machine:

curl -fsSL https://kempt.tools/install.sh | sh      # install the kempt binary
kempt init https://github.com/schuettc/dotfiles.git  # clone → pick packages → plan → apply

kempt init walks a picker to choose a profile (developer = everything, minimal = just the shell) and package set, shows the full plan, and applies it on your confirmation.

No git? kempt can also init straight from a tarball of this repo — the tree is fetched and extracted, and kempt update re-fetches it:

kempt init https://github.com/schuettc/dotfiles/archive/refs/heads/main.tar.gz

2. Open Ghostty, then:

proj               # pick a project → spawns a workspace (shell + yazi)

See docs/terminal-usage.md for the day-to-day cheat sheet, and docs/setup-notes.md for the full design rationale behind the migration off cmux.

Day-to-day

What you want Command
See what would change (nothing runs) kempt plan
Apply the manifest / converge this machine kempt apply
Pull latest + self-update + converge kempt update (aliased dotup)
Prompt-safe status line (used by proj) kempt status
Add / drop a package from this machine's selection kempt adopt <pkg> / kempt drop <pkg>

Every kempt run is idempotent and shows its plan first. The packages are core, terminal, nvim, markedit, claude, codex, muster, and the macOS-only pi (Pi coding agent on the claude-bridge provider + published extensions). developer selects all; minimal selects core. The local llama.cpp inference rig is a separate opt-in package, pi-llama (it needs GGUF weights this repo doesn't ship) — add it on a machine that has them with kempt adopt pi-llama.

Manual follow-ups kempt can't automate (it prints these after apply): run codex login to authenticate Codex; restart any running pi session so it picks up the pinned extensions.

What's Included

Terminal stack: Ghostty + tmux + yazi

  • Ghostty — native, GPU-accelerated terminal emulator. Config in config/ghostty/config (MonoLisaCode font, Catppuccin Mocha, keybinds, image-paste workaround, Ctrl+Enter newline).
  • tmux: multiplexer providing splits and detach/reattach (reboot recovery is proj's saved tab). Config in .tmux.conf.
  • yazi — TUI file explorer that lives in a right-side pane. Config in config/yazi/.
  • neovim (LazyVim) — terminal editor and system-wide $EDITOR. Config in config/nvim/ (Catppuccin Mocha; TypeScript, Python, and Go language servers via Mason).

The workspace workflow

One project workspace = one Ghostty window. proj is a two-screen picker: Screen 1 picks a project (or jumps to a live session); Screen 2 picks a session — or names new work. The session name is the identity: sessions are born <project>/<work> (home base: bare <project>), every surface aligns to that name — Claude included, which is launched with --name <session> — and renames go through one gesture (prefix T, where you type only the work half) so no surface is left behind. Isolation is the agent's job — proj opens everything in the primary clone and coding agents make their own worktrees when they need them.

Shell helpers (in config/zsh/04-aliases.zsh; flags come before the positional project — proj --claude dotfiles works, proj dotfiles --claude silently drops the flag):

Command What it does
proj two-screen picker → jump to a session, open the home base, or name new work
proj <project> skip Screen 1
proj --claude same, but auto-launch claude in the left pane
proj --cursor same, but auto-launch Cursor Agent in the left pane
proj --add / --remove add or delete an entry in ~/.config/proj/roots
pt <work> / pt <project> <work> create-or-attach <project>/<work> without the picker
tat <name> attach-or-create a named session
proj-clean reap idle sessions (shell/yazi only — no Claude/editor/server)
bell-clear dismiss the attention banner (-k to kill flagged sessions)

⌘T in a project window auto-joins a new tmux session for that project (via config/zsh/06-tmux-autojoin.zsh); ⌘N opens a fresh window at $HOME, outside any project. Project roots are configured per-machine in ~/.config/proj/roots (not tracked; first proj run sets it up). See docs/terminal-usage.md for the day-to-day walkthrough.

Shell Configuration

  • Modular zsh — configs split into numbered files in config/zsh/
  • Lazy-loaded NVM — Node available immediately, NVM loads on demand
  • Starship prompt — two-line prompt with git status, language versions, AWS profile

Pi with local models

This is an optional, macOS-only component (part of the developer profile). It's declared as the pi package in kempt.toml; kempt apply installs it. To add it to an existing selection: kempt adopt pi && kempt apply.

The pi package installs Pi and a launchd-managed llama.cpp router on the loopback-only port 42137. Pi's native /llama command downloads, loads, and unloads GGUF models; normal pi, pi --resume, and pi --session ... commands need no wrapper. The model, port, defaults, runtime tuning, and service controls are documented in docs/pi-local-models.md.

Modern CLI Tools (via packages/*/Brewfile)

Tool Replaces Purpose
eza ls File listing with icons and git status
bat cat Syntax-highlighted file viewing
ripgrep grep Fast search
fd find Fast file finding
zoxide cd Smart directory jumping
fzf — Fuzzy finder
delta diff Syntax-highlighted git diffs
lazygit — Git TUI
atuin history Shell history with sync

tmux status bar

The status bar surfaces, for the focused pane:

  • left — an attention banner (⚠ N: session1, session2) listing any session whose Claude finished a turn / is waiting for input and that you haven't visited yet. Clears when you switch to the session.
  • right — current git branch + dirty count, the Claude context-window % (⌬ 49%, green/yellow/red) when the focused pane is running Claude, and the date/time.

The branch/context indicators come from the helpers in bin/.

Claude Code Integration

The install script configures Claude Code with:

  • Status line (config/claude/statusline.sh) — model + working directory. (Context % and git status are shown in the tmux status bar instead, to avoid duplication.)
  • Attention bell (config/claude/claude-notify.sh) — the Notification and Stop hooks ring the tmux bell for the exact Claude pane, which drives the status-left attention banner and a 🔔 on the Ghostty tab. Purely in-terminal — no macOS notification, no Dock bounce.

Codex (GPT) bridge

Claude Code stays the primary harness, with OpenAI Codex (cask "codex") wired in two complementary ways so you get GPT for a second opinion without leaving Claude Code:

  • MCP bridge — the codex package registers Codex as a user-scope MCP server (claude mcp add codex -s user -- codex mcp-server), so Claude Code can delegate a discrete coding task or ask GPT for a second opinion mid-session via the codex MCP tool. Verify with claude mcp list (look for codex … ✔ Connected).
  • Standalone — codex in its own tab for an independent pass; run both agents on the same tricky task and let agreement/divergence guide you.

Both run on a ChatGPT subscription (codex login — browser OAuth), not a metered OpenAI API key. Check auth with codex login status. See docs/codex-bridge.md for the day-to-day workflow.

Cursor Agent

Cursor Agent CLI is a peer coding agent on the muster bus. Install packages/cursor with muster to install the CLI and wire Cursor session hooks, the muster MCP server, and its permission allowlist. Start a workspace with proj --cursor; the left pane runs cursor-agent --trust --approve-mcps (or agent when that is the available CLI). See docs/cursor-bridge.md.

muster — cross-terminal agent bus

Where the Codex bridge is vertical (one terminal), muster is horizontal: a local coordination bus that lets standing agent sessions in separate terminals (Claude Code, Codex, and/or Cursor) message and hand tasks to each other — no copy/paste, subscription-only. The muster package self-installs the whole stack: installed from the latest GitHub release (a checksummed binary written to ~/.local/bin/muster — no clone, no Go toolchain required; ~/GitHub/schuettc/muster, if present, is a dev checkout the installer never touches), installs a LaunchAgent (tools.muster.serve — muster serve runs at login, restarts on crash, logs to ~/.local/share/muster/serve.log), and registers the MCP server in Claude Code, Codex, and Cursor. Session hooks (auto-register on the bus + self-resolving inbox via muster hook, built into the binary since v0.3.0) are merged into the Claude/Codex/Cursor settings by the same script. Don't want muster? Skip it and pick the rest of the packages with the install-wizard skill (see "Selective install" below).

  • In an agent session: the agent calls register_agent once, then send_message / task_create / task_claim / get_inbox / … to coordinate with peers. A tmux "wake" knocks the recipient's pane so idle agents notice.
  • From any shell: muster agents, muster inbox <alias>, muster tasks <alias>, muster send <alias> "…" --from me to observe and drive the bus.
  • From tmux: prefix @ copies the session's bus alias to the clipboard; prefix m nudges the session's agent to drain its inbox now (idle agents otherwise only check mail at turn boundaries).

Verify with claude mcp list (muster … ✔ Connected) or Cursor's ~/.cursor/mcp.json. Full docs live in the muster repo's README.

The naming contract (tmux ↔ muster)

The tmux session name (#S) is the identity (spec: docs/superpowers/specs/2026-08-08-proj-session-identity-design.md). A session is born named for its work (<project>/<work>, via the proj picker or pt), and every surface aligns to that one name: tab titles, the picker, Claude's own conversation name, and — when muster is installed — the bus alias, which its SessionStart hook seeds from the session name.

Claude gets that name at launch, via claude --name <session>. Both launch paths carry it: proj --claude / pt --claude type it into the pane (__claude_launch_cmd), and a hand-typed bare claude picks up #S through the wrapper in 04-aliases.zsh. Nothing is injected and nothing waits for the agent to boot. --name writes a transcript custom-title record, which is what config/claude/statusline.sh reads to decide a name is user-set — so the first status tick already sees Claude and #S aligned.

Renames go through one gesture so no surface is left behind. You type the work, never the project. prefix T runs bin/tmux-session-rename.sh --prompt, which pre-fills the prompt with the work segment alone (a home-base session, whose #S is a bare <project>, starts empty); the rename half re-attaches the project prefix to whatever comes back. A typed name that already contains a / is taken verbatim — the escape hatch for re-homing a session under another project. From there it validates the name (letters, digits, -, _, /), refuses names any live session holds, renames the tmux session, and — with muster — calls muster become, which claims the alias on the bus (mail follows via lineage) and types /rename into the registered Claude pane.

/rename inside Claude flows the other way: the statusline proves the name is user-set via the transcript custom-title record, then renames the tmux session (plus muster become --no-inject when available — the name already came from /rename). Note the asymmetry: that path takes the name literally and does not re-attach a project prefix, so /rename foo on dotfiles/nfl-4 leaves a bare foo. Use prefix T. Without muster, both gestures still work, just tmux-only.

@claude_task survives as a display-only subtitle — whatever Claude currently calls the conversation (usually its auto topic), rendered in the title's middle segment and deduped against the session name. It never renames anything. @claude_task_promoted records the last title the statusline acted on, so a stale transcript title (a swallowed /rename) can't revert an operator's rename. @claude_task_manual is retired.

Structure

~/dotfiles/
├── .zshrc                 # Minimal loader, sources config/zsh/*
├── .tmux.conf             # tmux config (prefix C-a, plugins, status bar)
├── kempt.toml             # declarative machine manifest (kempt reads this)
├── bin/
│   ├── tmux-git-status.sh      # branch + dirty count for status-right
│   ├── tmux-claude-context.sh  # Claude context % for status-right
│   ├── tmux-attention.sh       # attention banner for status-left
│   ├── tmux-session-color.sh   # stable name-hashed session color
│   └── claude-attn             # raise/clear the Claude attention flag
├── config/
│   ├── ghostty/config     # Terminal config (fonts, theme, keybinds)
│   ├── yazi/              # File-explorer config (Catppuccin Mocha)
│   ├── nvim/              # neovim config (LazyVim, Catppuccin Mocha)
│   ├── zsh/
│   │   ├── 00-terminal.zsh      # OSC 7 cwd reporting (Ghostty new-tab dir)
│   │   ├── 01-paths.zsh        # PATH + EDITOR (nvim)
│   │   ├── 02-nvm-lazy.zsh     # Lazy NVM loading
│   │   ├── 03-tools.zsh        # Atuin, zoxide, fzf init
│   │   ├── 03-proj-roots.zsh   # project-roots loader (proj/pt)
│   │   ├── 04-aliases.zsh      # aliases + proj/pt/tat/proj-clean/bell-clear
│   │   ├── 05-completions.zsh  # Shell completions
│   │   └── 06-tmux-autojoin.zsh # ⌘T → auto-join project tmux session
│   ├── starship.toml      # Prompt configuration
│   ├── atuin/config.toml  # History sync settings
│   └── claude/
│       ├── statusline.sh      # Claude Code status line (model + dir)
│       └── claude-notify.sh   # Notification/Stop hooks → tmux bell
└── docs/
    ├── pi-local-models.md # Pi + local llama.cpp models
    ├── terminal-usage.md  # day-to-day cheat sheet
    ├── terminal-setup.md  # install tutorial
    ├── codex-bridge.md    # Claude Code + Codex (GPT) workflow
    ├── cursor-bridge.md   # Cursor Agent + muster workflow
    └── setup-notes.md     # design rationale / running log

Customization

To change… Edit
Aliases / workspace commands config/zsh/04-aliases.zsh
Project root directories proj --add / --remove / --edit (writes ~/.config/proj/roots)
The prompt config/starship.toml (starship.rs/config)
Terminal settings / keybinds config/ghostty/config
tmux behavior / status bar .tmux.conf
Homebrew packages packages/<name>/Brewfile, then brew bundle --file=packages/<name>/Brewfile

Requirements

  • macOS
  • Homebrew
  • MonoLisa font (paid; not in any package's Brewfile) — without it Ghostty falls back to a default monospace. Install your .ttfs into ~/Library/Fonts/ first. The config expects MonoLisa 3.000+, whose family is MonoLisaCode (v2.x shipped as MonoLisa); on 3.000 the variable MonoLisaCodeUpright.ttf covers every weight.

About

Personal dotfiles - zsh, starship, ghostty, dev tools

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages