No description
  • TypeScript 52.4%
  • Shell 45.2%
  • AppleScript 2.4%
Find a file
K Gopal Krishna 6deb818184 feat(zsh): add emacs keybindings in vi insert mode
Add common emacs Ctrl+ shortcuts to the viins keymap so they work
alongside vi mode. These keys were all unbound (self-insert) or
already performing the same action, so there are no conflicts.

Bindings added:
  Ctrl+A/E  beginning/end of line
  Ctrl+F/B  forward/backward char
  Ctrl+N/P  next/previous history
  Ctrl+K    kill to end of line
  Ctrl+U    kill whole line
  Ctrl+W    backward kill word
  Ctrl+D    delete char or list completions

Ctrl+T is intentionally left unbound (tmux prefix).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:43:28 +01:00
.ssh fix(ssh): resolve gitea host to internal k3s node 2026-02-21 08:17:27 +01:00
atuin refactor: flatten repo layout, remove nested .config directories 2026-02-28 19:38:10 +01:00
karabiner-ts fix: resolve bugs and portability issues across dotfiles 2026-02-20 16:19:13 +05:30
scripts toggle-menu-bar-visibility: add applescript for easy toggling 2024-04-21 21:04:13 +05:30
tmux feat(tmux): add detach-on-destroy off and display-panes timeout 2026-03-02 07:31:01 +01:00
zsh feat(zsh): add emacs keybindings in vi insert mode 2026-03-09 12:43:28 +01:00
.gitignore docs: document nested tmux clipboard and Shift+Enter limitations 2026-02-28 19:58:12 +01:00
dot fix(dot): harden CLI validation and rerunnable setup 2026-03-05 08:11:23 +01:00
LICENSE chore(license): update copyright year to 2026 2026-01-30 17:25:16 +01:00
README.md fix(dot): harden CLI validation and rerunnable setup 2026-03-05 08:11:23 +01:00

Dotfiles

Configuration files and a management script for a personalized development environment.

Usage

Clone

Clone the repository to any path you like. I keep mine at ${HOME}/.config/dotfiles.

The repository is mirrored on both GitHub and a self-hosted GitLab instance:

GitHub (primary)

git clone https://github.com/kaygdotorg/dotfiles.git "${HOME}/.config/dotfiles"

Self-hosted GitLab (mirror)

git clone https://git.kayg.org/kayg/dotfiles.git "${HOME}/.config/dotfiles"

Both remotes are kept in sync. Use whichever is more convenient or accessible.

Setup

The dot script handles all linking and installation. Start by symlinking it into your PATH:

"${HOME}/.config/dotfiles/dot" setup dot

Then set up whichever apps you need:

dot setup zsh
dot setup tmux
dot setup atuin
dot setup ssh
dot setup karabiner  # macOS only, requires npx

Each dot setup <app> command creates the necessary directories, symlinks configuration files from this repository into the correct system paths, and installs any dependencies (plugins, binaries, etc.). Running setup again is safe: existing symlinks are overwritten, existing zsh plugins are skipped, and existing Atuin installs are reused.

Command format and validation

The dot CLI accepts exactly two arguments:

dot <setup|update> <dot|tmux|zsh|atuin|ssh|karabiner>

If arguments are missing or invalid, dot prints usage and exits with a non-zero status.

Re-running setup commands

  • dot setup zsh reuses the existing install directory and skips plugin repos that are already present.
  • dot setup atuin reuses an existing ~/.atuin/bin/atuin installation, ensures ~/.local/bin exists, and then refreshes the symlink/config.
  • dot setup dot, dot setup tmux, and dot setup ssh are symlink-based and can be run repeatedly.

Apps

  • Zsh — Standalone configuration with Oh My Posh prompt, vi-mode, lazy-loaded nvm, and plugins (autosuggestions, syntax highlighting, history substring search, completions).
  • Tmux — Standalone configuration with Catppuccin Mocha theme, OSC 52 clipboard support for nested sessions, vi-mode copy bindings, mouse support, F12 pass-through toggle for nested sessions, searchable keybindings cheatsheet (prefix + ?), and TPM for plugin management.
  • Atuin — Shell history replacement with sync to a self-hosted server, replacing the default zsh history search.
  • SSH — Managed SSH client configuration.
  • Karabiner — Advanced keyboard customization via karabiner.ts with Colemak-DH layout and hyper key layers.

Repository Structure

Configuration files are stored flat under each app's directory. The dot script is responsible for creating directories at the destination and symlinking files into place. No file in this repository needs to match its final system path.

.
├── dot                      # Setup and management script (POSIX sh)
├── zsh/
│   ├── .zshenv              # Sets ZDOTDIR so zsh finds its config
│   ├── .zshrc               # Main shell configuration
│   └── omp.yaml             # Oh My Posh prompt theme
├── tmux/
│   └── .tmux.conf           # Tmux configuration
├── atuin/
│   └── config.toml          # Atuin shell history configuration
├── .ssh/
│   └── config               # SSH client configuration
├── karabiner-ts/
│   └── index.ts             # Generates Karabiner-Elements JSON profile
└── scripts/
    └── toggle-menu-bar-visibility.applescript

Linking map

The dot script symlinks each file from this repository into its expected system location. The diagram below shows what goes where:

graph LR
    subgraph Repository
        A["zsh/.zshenv"]
        B["zsh/.zshrc"]
        C["zsh/omp.yaml"]
        D["tmux/.tmux.conf"]
        E["atuin/config.toml"]
        F[".ssh/config"]
        G["karabiner-ts/index.ts"]
        H["dot"]
    end

    subgraph System
        A1["~/.zshenv"]
        B1["~/.config/zsh/.zshrc"]
        C1["~/.config/zsh/omp.yaml"]
        D1["~/.config/tmux/tmux.conf"]
        E1["~/.config/atuin/config.toml"]
        F1["~/.ssh/config"]
        G1["~/.config/karabiner/karabiner.json"]
        H1["~/.local/bin/dot"]
    end

    A -->|symlink| A1
    B -->|symlink| B1
    C -->|symlink| C1
    D -->|symlink| D1
    E -->|symlink| E1
    F -->|symlink| F1
    G -->|generates| G1
    H -->|symlink| H1

Note: Karabiner is the exception — index.ts is executed via npx karabiner.ts, which writes the profile JSON directly to ~/.config/karabiner/karabiner.json. It is not symlinked.

Quirks

Nested tmux clipboard (inner SSH → outer Mac → iTerm2)

Tmux's set-clipboard on correctly intercepts and re-emits OSC 52 escape sequences from applications (e.g., a printf in the shell or vim yanking), but it does not emit OSC 52 from its own copy mode when running nested inside another tmux (TERM=tmux-256color). This means yanking or mouse-selecting in copy mode silently fails to reach the outer clipboard.

The workaround: all copy-mode bindings use copy-pipe-and-cancel with an explicit command that base64-encodes the selection and writes an OSC 52 sequence directly to #{client_tty} (the terminal the tmux client is attached to). The full clipboard chain looks like this:

graph LR
    A["Copy-mode selection"] --> B["copy-pipe base64-encodes\nand writes OSC 52\nto #{client_tty}"]
    B --> C["SSH forwards bytes"]
    C --> D["Outer tmux intercepts\n(set-clipboard on)\nand re-emits OSC 52"]
    D --> E["iTerm2 sets\nmacOS clipboard"]

See the clipboard and copy-mode sections in tmux/.tmux.conf for the full implementation and explanation.

Nested tmux pass-through (F12 toggle)

When running tmux inside tmux (e.g., SSH into a remote machine that also runs tmux), key bindings are captured by the outer session. Press F12 to toggle pass-through mode — all keys go directly to the inner tmux. The outer status bar shows a red lock indicator when pass-through is active. Press F12 again to return to normal mode.

Keybindings cheatsheet (prefix + ?)

Press prefix + ? to open a searchable, scrollable popup listing every key binding with human-readable descriptions. It covers all three key tables (prefix, root, copy mode) and includes plugin bindings. Use / to search, arrow keys or j/k to scroll, and q to close.

Paste buffer reference view

Tmux stores every yanked selection in a paste buffer stack. Two bindings make it easy to reference previous yanks while working:

  • Y in copy mode — Yanks the selection to both the paste buffer and the system clipboard (via OSC 52), then immediately opens it in a resizable split pane for reference.
  • prefix + b — Browse all paste buffers, select one, and open it in a split pane. Use prefix + = to browse and paste instead.

The reference split is scrollable and searchable (powered by less), resizable with prefix + H/J/K/L or mouse drag, and closes with q.

Shift+Enter in Claude Code inside tmux

Shift+Enter for newlines does not work inside tmux. Tmux only forwards extended key sequences (kitty keyboard protocol) to applications that explicitly request them, and Claude Code does not opt in.

The extended-keys always setting would fix this, but it causes breakage elsewhere:

  • Shift+Tab sends raw escape codes instead of working properly (tmux#4304)
  • Pasting in Neovim produces artifacts (gpakosz/.tmux#776)
  • Fish shell completions break in tmux 3.5+ (tmux#2705)

Use \ + Enter for newlines in Claude Code instead. This is documented in the terminal section of tmux/.tmux.conf.

License

See LICENSE file for details.