diff --git a/README.md b/README.md
index 2d3fec9..dd618d8 100755
--- a/README.md
+++ b/README.md
@@ -43,13 +43,14 @@ This README is the tour. The field manual lives at
### 📊 Core Development
-| Tool | Description | Features |
-| -------------------------------------------------------------------- | -------------------- | --------------------------------------------------------------------------- |
-| 📝 **[AstroNvim v5](https://astronvim.com/)** | Neovim configuration | • IDE-like features
• Avante.nvim AI assistant
• SilkCircuit theme |
-| 👻 **[Ghostty](https://ghostty.org/)** | Terminal emulator | • GPU-accelerated
• SilkCircuit theme
• Native macOS/Linux |
-| 🌌 **[Starship](https://starship.rs/)** | Cross-shell prompt | • SilkCircuit gradient theme
• Git status integration
• Context-aware |
-| 🖥️ **[Tmux](https://github.com/tmux/tmux)** | Terminal multiplexer | • Custom key bindings
• SilkCircuit color scheme
• Session management |
-| 🤖 **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** | AI pair programmer | • Custom status line
• Security hooks
• Project-aware assistance |
+| Tool | Description | Features |
+| -------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------- |
+| 📝 **[AstroNvim v5](https://astronvim.com/)** | Neovim configuration | • IDE-like features
• Avante.nvim AI assistant
• SilkCircuit theme |
+| 🌙 **[Helix](https://helix-editor.com/)** | Modal editor, no plugins | • AstroNvim-shaped Space leader
• Shares Mason's language servers
• SilkCircuit theme |
+| 👻 **[Ghostty](https://ghostty.org/)** | Terminal emulator | • GPU-accelerated
• SilkCircuit theme
• Native macOS/Linux |
+| 🌌 **[Starship](https://starship.rs/)** | Cross-shell prompt | • SilkCircuit gradient theme
• Git status integration
• Context-aware |
+| 🖥️ **[Tmux](https://github.com/tmux/tmux)** | Terminal multiplexer | • Custom key bindings
• SilkCircuit color scheme
• Session management |
+| 🤖 **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** | AI pair programmer | • Custom status line
• Security hooks
• Project-aware assistance |
### 🎯 Modern CLI Tools
@@ -78,6 +79,7 @@ This README is the tour. The field manual lives at
dotfiles/
├── nvim/ # AstroNvim v5 configuration (→ ~/.config/nvim)
│ └── lua/plugins/ # Plugin configs (silkcircuit, avante, treesitter, …)
+├── helix/ # Helix config + languages (→ ~/.config/helix/*.toml)
├── zsh/ # Zsh configuration (zshrc + completion)
├── bash/ # Bash configuration (profile + bashrc.local)
├── sh/ # 30 modular shell scripts (git, docker, k8s, macos, …)
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 2656a9a..d6d107b 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -21,6 +21,7 @@ export default defineConfig({
{ text: 'Guide', link: '/getting-started/' },
{ text: 'Shell', link: '/shell/' },
{ text: 'Neovim', link: '/neovim/' },
+ { text: 'Helix', link: '/helix/' },
{ text: 'Tools', link: '/tools/' },
{
text: 'Reference',
@@ -88,6 +89,12 @@ export default defineConfig({
],
},
],
+ '/helix/': [
+ {
+ text: 'Helix',
+ items: [{ text: 'Overview', link: '/helix/' }],
+ },
+ ],
'/tools/': [
{
text: 'CLI Tools',
diff --git a/docs/helix/index.md b/docs/helix/index.md
new file mode 100644
index 0000000..e54e31d
--- /dev/null
+++ b/docs/helix/index.md
@@ -0,0 +1,230 @@
+# Helix
+
+Helix 25.07, painted in SilkCircuit glow, with the Space leader laid out the way AstroNvim taught your hands
+
+## Overview
+
+[Helix](https://helix-editor.com) is a modal editor with no plugin system: tree-sitter, LSP, pickers, multiple cursors
+and a file explorer are built in, and the whole setup is two TOML files. It is Kakoune-shaped rather than Vim-shaped, so
+you select first and act second. That model is the reason to try it, and the config here leaves it alone. What it does
+carry over from the [Neovim setup](../neovim/) is the muscle memory that has nothing to do with motions: the Space
+groups, `Ctrl-s`, `]b` and `[b`, and `Esc` clearing your selection.
+
+What you get:
+
+- The same language servers and formatters as Neovim: Rust, Python (ruff and ty), TypeScript (vtsls), HTML, CSS,
+ Tailwind, JSON, YAML, TOML, Markdown, Bash, Lua and Docker, with prettier, stylua and shfmt for formatting
+- One master switch for format-on-save, off by default like the Neovim config, toggled with `Space u F`
+- Inlay hints, inline diagnostics on the cursor line, a bufferline when more than one file is open, a statusline that
+ shows the git branch
+- lazygit in a tmux popup, one-line git blame, and a `Space a y` that copies a `file:line` reference for pasting at an
+ agent
+
+## Structure
+
+```
+helix/
+├── config.toml # Editor options and keymap (→ ~/.config/helix/config.toml)
+├── languages.toml # Language servers and formatters (→ ~/.config/helix/languages.toml)
+└── README.md
+sh/helix.sh # Puts Mason's binaries on PATH so Helix finds the servers
+```
+
+The SilkCircuit installer drops its five Helix themes into `~/.config/helix/themes/`, which is why the two files are
+linked individually rather than the directory. Pick another variant any time with `:theme silkcircuit-neon` (also
+`vibrant`, `soft` and `dawn`); the tracked default is `glow`.
+
+## Quick start
+
+```
+hx . # File explorer at the project root
+hx file.py # Open a file
+
+Space f f # Find files
+Space f w # Grep the project
+Space f c # Grep the word under the cursor
+Space e # File explorer
+Space x x # Diagnostics for this buffer
+Space l f # Format buffer
+Space g g # lazygit (tmux popup)
+Space ? # Command palette, searchable by description
+:config-reload # After editing config.toml
+hx --health python # What a language resolved to
+```
+
+## Coming from Neovim
+
+The unlearning is smaller than it looks. Everything that is not in this table works the way you expect.
+
+| Habit | Helix |
+| ---------------------- | -------------------------------------------------------------------------------------------------- |
+| `dw`, `ciw`, `yap` | Select first, then act: `wd`, `miwc`, `mapy`. `w`, `e`, `b` and friends leave a selection behind |
+| `v` then move | You are always selecting. `v` toggles select mode, where motions extend instead of replace |
+| `x` | Selects the whole line (repeat to grow). `d` deletes what is selected |
+| `%` | Selects the whole file. `mm` jumps to the matching bracket |
+| `Ctrl-r` | `U` redoes, and `Ctrl-r` is mapped to it too |
+| `K` for hover | `Space k` (or `Space l h`). `K` keeps selections matching a regex, which you will want |
+| `.` repeat | Repeats the last insert. Multiple cursors cover most of what `.` did: `s` splits a selection |
+| | into one cursor per regex match, `C` copies the cursor down, `,` collapses back to one |
+| `:%s/a/b/g` | `%` to select all, `s` to pick the matches, then `c` and type. Live, with every match visible |
+| `f`, `t` | Not confined to the line |
+| `:terminal`, lazygit | No terminal. `Space g g` opens lazygit in a tmux popup; anywhere else `Ctrl-z` drops you to the |
+| | shell and `fg` brings Helix back |
+| `:Mason`, `:Lazy` | Nothing to manage. `hx --health` reports what each language found on PATH |
+| which-key | Press `Space` and wait; the infobox lists the group. Same for `g`, `m`, `z`, `Ctrl-w` |
+| `q:`, `:help` | `Space ?` searches every command by description |
+| `gd`, `gr`, `gi`, `gy` | Same keys. `gd` definition, `gr` references, `gi` implementation, `gy` type definition |
+| `]d`, `[d`, `]g`, `[g` | Same keys: diagnostics and git hunks. Also `]f` function, `]t` type, `]a` argument, `]p` paragraph |
+
+## Keybindings
+
+Notation: `Space` is the leader, `C-x` is Ctrl. Mode is normal unless a table says otherwise. Mappings marked (ours)
+come from `helix/config.toml`; the rest are Helix defaults kept because they already matched.
+
+### Files and windows
+
+| Key | Action |
+| ----------------------- | --------------------------------------------------------------- |
+| `Space w` | Save (ours) |
+| `Space q` / `Space Q` | Close view / close all views (ours) |
+| `Space n` | New scratch buffer (ours) |
+| `Space c` / `Space C` | Close buffer / force close (ours) |
+| `Space e` / `Space E` | File explorer at workspace root / at this file's directory |
+| `Space \|` / `Space \\` | Vertical / horizontal split (ours) |
+| `C-h` `C-j` `C-k` `C-l` | Move between splits (ours) |
+| `C-w` | Window mode: `v` `s` split, `q` close, `o` only, `H J K L` swap |
+| `C-s` | Save, in normal and insert mode (ours) |
+| `C-q` | Quit everything, discarding changes (ours, same as AstroNvim) |
+| `]b` / `[b` | Next / previous buffer (ours) |
+| `Esc` | Collapse to one cursor, drop extra selections (ours) |
+
+### Find (`Space f`)
+
+| Key | Action |
+| ----------- | --------------------------------- |
+| `Space f f` | Files in the workspace |
+| `Space f F` | Files under the current directory |
+| `Space f w` | Grep the workspace, live |
+| `Space f c` | Grep the word under the cursor |
+| `Space f b` | Buffers |
+| `Space f s` | Symbols in this file |
+| `Space f S` | Symbols in the workspace |
+| `Space f j` | Jumplist |
+| `Space f g` | Files changed in git |
+| `Space '` | Reopen the last picker |
+
+### Buffers (`Space b`)
+
+| Key | Action |
+| ------------------------- | ------------------------ |
+| `Space b b` | Buffer picker |
+| `Space b n` / `Space b p` | Next / previous buffer |
+| `Space b d` | Close this buffer |
+| `Space b c` | Close every other buffer |
+| `Space b C` | Close all buffers |
+
+### Language tools (`Space l`)
+
+| Key | Action |
+| ------------------------- | -------------------------------------- |
+| `Space l a` | Code action |
+| `Space l r` / `Space r` | Rename symbol |
+| `Space l h` / `Space k` | Hover documentation |
+| `Space l f` | Format buffer |
+| `Space l s` / `Space l G` | Document / workspace symbols |
+| `Space l d` / `Space l D` | Document / workspace diagnostics |
+| `Space l R` | References |
+| `Space l i` / `Space l y` | Implementation / type definition |
+| `Space l l` | Restart the language servers |
+| `Space l L` | Open the Helix log |
+| `Space x x` / `Space x X` | Diagnostics picker, buffer / workspace |
+
+### Git (`Space g`)
+
+| Key | Action |
+| ----------- | ------------------------------------------------------- |
+| `Space g g` | lazygit in a tmux popup |
+| `Space g f` | lazygit filtered to this file's history |
+| `Space g s` | Picker of files changed in the working tree |
+| `Space g b` | Blame the current line: commit, author, age and subject |
+| `]g` / `[g` | Next / previous hunk |
+
+Outside tmux, `Space g g` prints a reminder instead of failing quietly: `Ctrl-z`, run lazygit, `fg`.
+
+### Agents (`Space a`)
+
+| Key | Action |
+| ----------- | -------------------------------------------------------------------------------------------------------- |
+| `Space a y` | Copy `path:line` for the cursor, or `path:start-end` for a multi-line selection, to the system clipboard |
+
+Works in normal and select mode. It uses `pbcopy`, `wl-copy` or `xclip`, whichever the box has.
+
+### Toggles (`Space u`)
+
+| Key | Toggles |
+| ----------- | ------------------------------------------ |
+| `Space u n` | Relative / absolute line numbers |
+| `Space u w` | Soft wrap |
+| `Space u i` | Inlay hints |
+| `Space u g` | Indent guides |
+| `Space u h` | Visible whitespace |
+| `Space u c` | Cursor line highlight |
+| `Space u b` | Bufferline |
+| `Space u m` | Mouse |
+| `Space u d` | Inline and end-of-line diagnostics |
+| `Space u F` | Format on save, for every language at once |
+
+### Kept from Helix
+
+`Space y`, `Space p`, `Space P` and `Space R` move text through the system clipboard. `Space j` is the jumplist,
+`Space h` selects every reference to the symbol under the cursor, `Space G` is the debugger, `Space /` comments the
+selection (matching AstroNvim, and replacing Helix's `Space c`). Two defaults moved: `Space a` is the agents group, so
+code actions are `Space l a`, and `Space w` saves, so window mode is `Ctrl-w`.
+
+## Statusline and bufferline
+
+The bar follows the heirline layout from Neovim: a colored mode block on the left (NORMAL, INSERT and SELECT, each with
+a Nerd Font glyph, the LSP spinner beside it), then the git branch, the file name and its modified or read-only state.
+The right side carries diagnostics for the buffer and the workspace as colored dots, the selection count and length, the
+active register, position and percentage, and the file type. Sections sit on the SilkCircuit highlight surface with cyan
+separators; the mode block takes purple, pink or cyan for normal, insert or select, the same hues heirline uses. Those
+colors live in the SilkCircuit Helix theme, generated from the helix extra in that repo, because Helix draws the bar
+from `ui.statusline` scopes rather than from config.
+
+With more than one buffer open the bufferline appears on top, active buffer in bold purple on the editor background, the
+rest muted on the section surface, matching the heirline tabline.
+
+Helix has no custom statusline components, so a few heirline pieces have no equivalent: git added, changed and removed
+counts, attached server names and the scrollbar. The spinner covers server activity and `Space l L` opens the log.
+
+## Language servers
+
+Everything below resolves to the binaries Mason installed for Neovim under `~/.local/share/nvim/mason/bin`, which
+`sh/helix.sh` appends to PATH. Nothing gets installed twice, and a box that has never run Neovim shows the gaps in
+`hx --health`.
+
+| Language | Server(s) | Formatter |
+| ---------------------- | ----------------------------------------------------------- | ------------------------------- |
+| Rust | rust-analyzer, with clippy as the check command | LSP |
+| Python | ty, ruff | LSP |
+| TypeScript, JavaScript | vtsls (tsx and jsx also get tailwindcss-ls) | prettier |
+| HTML, CSS, SCSS | vscode html and css servers, tailwindcss-ls | prettier |
+| JSON, YAML, Markdown | vscode-json-language-server, yaml-language-server, marksman | prettier |
+| TOML | taplo | LSP |
+| Lua | lua-language-server | stylua |
+| Bash | bash-language-server | shfmt, honoring `.editorconfig` |
+| Dockerfile, Compose | docker-language-server (compose adds yaml-language-server) | none |
+
+Formatting is off on save until you flip `Space u F`, matching the Neovim config. `Space l f` formats on demand. Helix
+has no separate linter hook, so the markdownlint and yamllint passes that nvim-lint runs in Neovim stay in `make lint`.
+
+## Gotchas
+
+- `hx --health` warns that `~/.config/helix/runtime` does not exist. That is where a source build keeps grammars; the
+ Homebrew install ships them elsewhere and the warning is noise.
+- Helix expands the first word of a `:sh` command only when the whole word is an expansion, so `s=%{cursor_line}` stays
+ literal there. `Space a y` goes through `set --` for exactly that reason.
+- `git blame -l` prefixes boundary commits with `^`; `Space g b` uses `--porcelain` to get a clean hash.
+- `Space f c` deliberately does not press Enter: global search runs as you type, and Enter would jump to the first match
+ instead of leaving the picker open.
+- Run `make install` after pulling: it links the two files and lets the SilkCircuit installer lay down the themes.
diff --git a/dotbot.d/base.yaml b/dotbot.d/base.yaml
index 39220e7..64753c1 100644
--- a/dotbot.d/base.yaml
+++ b/dotbot.d/base.yaml
@@ -80,6 +80,11 @@
~/.config/nvim:
path: nvim
force: true
+ # Helix reads these two files from ~/.config/helix, and the SilkCircuit
+ # installer drops its themes into ~/.config/helix/themes, so the files
+ # are linked rather than the directory.
+ ~/.config/helix/config.toml: helix/config.toml
+ ~/.config/helix/languages.toml: helix/languages.toml
~/bin: bin
~/.tmux/plugins/tpm: tpm
diff --git a/helix/README.md b/helix/README.md
new file mode 100644
index 0000000..a6d25c4
--- /dev/null
+++ b/helix/README.md
@@ -0,0 +1,11 @@
+# Helix
+
+Helix 25.07 in the SilkCircuit glow theme, with the Space leader laid out like the AstroNvim config next door.
+`~/.config/helix/config.toml` and `languages.toml` link here.
+
+- `config.toml` holds editor options and the keymap. Motions stay stock Helix; the Space groups mirror Neovim.
+- `languages.toml` wires the language servers and formatters, the same binaries the Neovim config installs through
+ Mason. `sh/helix.sh` puts them on PATH.
+- `~/.config/helix/themes/` is filled by the SilkCircuit installer and is not tracked here.
+
+The full tour lives in `docs/helix/`.
diff --git a/helix/config.toml b/helix/config.toml
new file mode 100644
index 0000000..3aac85a
--- /dev/null
+++ b/helix/config.toml
@@ -0,0 +1,214 @@
+# Helix, tuned for hands that learned AstroNvim.
+#
+# Motions, selections and the g/m/z minor modes stay stock Helix: the
+# select-then-act model is the reason to be here, so nothing below tries
+# to turn it back into Vim. What does carry over is the Space leader
+# layout from the Neovim config in this repo (f find, b buffers, l language
+# tools, g git, u toggles, x diagnostics, a agents) plus a handful of
+# reflexes (Ctrl-s, ]b/[b, Esc clearing the selection). Space e is already
+# the file explorer and Space k is hover, so those needed no remap.
+#
+# The SilkCircuit installer owns ~/.config/helix/themes. `:config-reload`
+# picks up edits here; `hx --health ` shows what each language
+# resolved to.
+
+theme = "silkcircuit-glow"
+
+[editor]
+line-number = "relative"
+cursorline = true
+scrolloff = 6
+bufferline = "multiple"
+color-modes = true
+true-color = true
+undercurl = true
+popup-border = "all"
+text-width = 120
+completion-timeout = 50
+end-of-line-diagnostics = "hint"
+# Master switch, like g.autoformat in the Neovim config. Every language in
+# languages.toml with a formatter sets auto-format = true, so flipping this
+# with Space u F turns format-on-save on for all of them at once.
+auto-format = false
+
+[editor.inline-diagnostics]
+cursor-line = "warning"
+
+[editor.lsp]
+display-inlay-hints = true
+
+[editor.cursor-shape]
+normal = "block"
+insert = "bar"
+select = "underline"
+
+[editor.file-picker]
+# Neo-tree shows dotfiles; so does this picker. .gitignore still applies.
+hidden = false
+
+[editor.indent-guides]
+render = true
+character = "│"
+skip-levels = 1
+
+# Statusline, laid out like heirline: mode block, branch, file and its
+# state on the left; diagnostics, selection, position and filetype on the
+# right. Helix paints the bar from the theme (ui.statusline.* in the
+# SilkCircuit helix theme), so this side only picks elements, the
+# separator glyph and the mode labels. Nerd Font glyphs: terminal for
+# normal, pencil for insert, select-all for select.
+[editor.statusline]
+left = [
+ "mode",
+ "spinner",
+ "version-control",
+ "separator",
+ "file-name",
+ "file-modification-indicator",
+ "read-only-indicator",
+]
+center = []
+right = [
+ "diagnostics",
+ "workspace-diagnostics",
+ "separator",
+ "selections",
+ "primary-selection-length",
+ "register",
+ "separator",
+ "position",
+ "position-percentage",
+ "separator",
+ "file-type",
+]
+separator = "│"
+mode.normal = " NORMAL"
+mode.insert = " INSERT"
+mode.select = " SELECT"
+
+[editor.soft-wrap]
+enable = false
+
+# ─────────────────────────────────────────────────────────────────────
+# Reflexes
+# ─────────────────────────────────────────────────────────────────────
+
+[keys.normal]
+C-s = ":write"
+C-q = ":quit-all!"
+C-r = "redo"
+C-h = "jump_view_left"
+C-j = "jump_view_down"
+C-k = "jump_view_up"
+C-l = "jump_view_right"
+# Vim's Esc clears the highlight; here it collapses to one cursor.
+esc = ["collapse_selection", "keep_primary_selection"]
+
+[keys.normal."]"]
+b = "goto_next_buffer"
+
+[keys.normal."["]
+b = "goto_previous_buffer"
+
+[keys.insert]
+C-s = ["normal_mode", ":write"]
+
+# ─────────────────────────────────────────────────────────────────────
+# Space leader, AstroNvim layout
+#
+# Helix defaults kept as-is: Space e/E explorer, Space k hover, Space r
+# rename, Space s/S symbols, Space d/D diagnostics, Space j jumplist,
+# Space h references, Space ' last picker, Space ? palette, Space y/p/P/R
+# clipboard, Space G debug. Space a becomes the agents group below, so code
+# actions live at Space l a, as in AstroNvim.
+# ─────────────────────────────────────────────────────────────────────
+
+[keys.normal.space]
+w = ":write"
+q = ":quit"
+Q = ":quit-all"
+n = ":new"
+c = ":buffer-close"
+C = ":buffer-close!"
+"/" = "toggle_comments"
+"|" = "vsplit"
+"\\" = "hsplit"
+
+# Find
+[keys.normal.space.f]
+f = "file_picker"
+F = "file_picker_in_current_directory"
+w = "global_search"
+b = "buffer_picker"
+s = "symbol_picker"
+S = "workspace_symbol_picker"
+j = "jumplist_picker"
+g = "changed_file_picker"
+# Word under the cursor, searched across the project: select it, push it
+# into the search register, open global search and paste it in. The picker
+# searches as you type, so no Enter: that would jump to the first hit.
+c = "@miw*fw/"
+
+# Buffers
+[keys.normal.space.b]
+b = "buffer_picker"
+n = "goto_next_buffer"
+p = "goto_previous_buffer"
+d = ":buffer-close"
+c = ":buffer-close-others"
+C = ":buffer-close-all"
+
+# Language tools
+[keys.normal.space.l]
+a = "code_action"
+r = "rename_symbol"
+h = "hover"
+f = ":format"
+s = "symbol_picker"
+G = "workspace_symbol_picker"
+d = "diagnostics_picker"
+D = "workspace_diagnostics_picker"
+R = "goto_reference"
+i = "goto_implementation"
+y = "goto_type_definition"
+l = ":lsp-restart"
+L = ":log-open"
+
+# Git. lazygit rides a tmux popup because Helix has no terminal of its
+# own; outside tmux the shell one-liner says so instead of failing quietly.
+[keys.normal.space.g]
+g = ':sh if [ -n "$TMUX" ]; then tmux display-popup -E -w 90%% -h 90%% -d "$PWD" lazygit; else echo "lazygit popup needs tmux: C-z, lazygit, fg"; fi'
+f = ':sh if [ -n "$TMUX" ]; then tmux display-popup -E -w 90%% -h 90%% -d "$PWD" lazygit -f "%{buffer_name}"; else echo "lazygit popup needs tmux: C-z, lazygit, fg"; fi'
+s = "changed_file_picker"
+b = ':sh git log -1 --format="%%h %%an, %%ar%%n%%s" $(git blame --porcelain -L %{cursor_line},+1 -- %{buffer_name} | head -1 | cut -d" " -f1)'
+
+# Agents: copy a file:line reference the way Claude Code likes it, spanning
+# the selection when it covers more than one line. The variables sit behind
+# `set --` because Helix expands the first word of a :sh command only when
+# the whole word is an expansion: `s=%{selection_line_start}` stays literal.
+[keys.normal.space.a]
+y = ':sh set -- %{selection_line_start} %{selection_line_end} %{buffer_name}; if [ "$1" = "$2" ]; then r="$1"; else r="$1-$2"; fi; if command -v pbcopy >/dev/null 2>&1; then printf "%%s" "$3:$r" | pbcopy; elif command -v wl-copy >/dev/null 2>&1; then printf "%%s" "$3:$r" | wl-copy; else printf "%%s" "$3:$r" | xclip -selection clipboard; fi; echo "copied $3:$r"'
+
+# UI toggles
+[keys.normal.space.u]
+n = ":toggle line-number relative absolute"
+w = ":toggle soft-wrap.enable"
+i = ":toggle lsp.display-inlay-hints"
+g = ":toggle indent-guides.render"
+h = ":toggle whitespace.render all none"
+c = ":toggle cursorline"
+b = ":toggle bufferline multiple never"
+m = ":toggle mouse"
+F = ":toggle auto-format"
+d = [":toggle end-of-line-diagnostics hint disable", ":toggle inline-diagnostics.cursor-line warning disable"]
+
+# Diagnostics lists (Trouble's corner)
+[keys.normal.space.x]
+x = "diagnostics_picker"
+X = "workspace_diagnostics_picker"
+
+[keys.select.space]
+"/" = "toggle_comments"
+
+[keys.select.space.a]
+y = ':sh set -- %{selection_line_start} %{selection_line_end} %{buffer_name}; if [ "$1" = "$2" ]; then r="$1"; else r="$1-$2"; fi; if command -v pbcopy >/dev/null 2>&1; then printf "%%s" "$3:$r" | pbcopy; elif command -v wl-copy >/dev/null 2>&1; then printf "%%s" "$3:$r" | wl-copy; else printf "%%s" "$3:$r" | xclip -selection clipboard; fi; echo "copied $3:$r"'
diff --git a/helix/languages.toml b/helix/languages.toml
new file mode 100644
index 0000000..231ba71
--- /dev/null
+++ b/helix/languages.toml
@@ -0,0 +1,143 @@
+# Language servers and formatters, mirroring the AstroNvim language packs.
+#
+# Helix ships sensible defaults for every language here; this file only
+# swaps servers for the ones Mason already installs (see sh/helix.sh for
+# how those reach PATH), trims servers that are not installed so the
+# health report stays honest, and wires the same formatters conform uses
+# in Neovim. Each formatted language sets auto-format = true so the global
+# editor.auto-format switch in config.toml governs all of them at once.
+
+# ─────────────────────────────────────────────────────────────────────
+# Servers Helix does not define out of the box
+# ─────────────────────────────────────────────────────────────────────
+
+[language-server.vtsls]
+command = "vtsls"
+args = ["--stdio"]
+
+[language-server.vtsls.config]
+# Match the Neovim setup: rename and imports from vtsls, formatting from
+# prettier below.
+typescript.format.enable = false
+javascript.format.enable = false
+
+# Docker's own server, the one Mason installs; it covers Dockerfiles and
+# compose files in a single process.
+[language-server.docker-language-server]
+command = "docker-language-server"
+args = ["start", "--stdio"]
+
+# Helix merges languages.toml three levels deep, so a user [config] table
+# replaces the default one wholesale. This is the 25.07.1 default block plus
+# clippy as the check command.
+[language-server.rust-analyzer.config]
+check.command = "clippy"
+files.watcher = "server"
+inlayHints.bindingModeHints.enable = false
+inlayHints.closingBraceHints.minLines = 10
+inlayHints.closureReturnTypeHints.enable = "with_block"
+inlayHints.discriminantHints.enable = "fieldless"
+inlayHints.lifetimeElisionHints.enable = "skip_trivial"
+inlayHints.typeHints.hideClosureInitialization = false
+
+# ─────────────────────────────────────────────────────────────────────
+# Languages
+# ─────────────────────────────────────────────────────────────────────
+
+[[language]]
+name = "python"
+language-servers = ["ty", "ruff"]
+auto-format = true
+
+[[language]]
+name = "rust"
+auto-format = true
+
+[[language]]
+name = "typescript"
+language-servers = ["vtsls"]
+formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] }
+auto-format = true
+
+[[language]]
+name = "tsx"
+language-servers = ["vtsls", "tailwindcss-ls"]
+formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] }
+auto-format = true
+
+[[language]]
+name = "javascript"
+language-servers = ["vtsls"]
+formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] }
+auto-format = true
+
+[[language]]
+name = "jsx"
+language-servers = ["vtsls", "tailwindcss-ls"]
+formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] }
+auto-format = true
+
+[[language]]
+name = "json"
+formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] }
+auto-format = true
+
+[[language]]
+name = "jsonc"
+formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] }
+auto-format = true
+
+[[language]]
+name = "css"
+language-servers = ["vscode-css-language-server", "tailwindcss-ls"]
+formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] }
+auto-format = true
+
+[[language]]
+name = "scss"
+formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] }
+auto-format = true
+
+[[language]]
+name = "html"
+language-servers = ["vscode-html-language-server", "tailwindcss-ls"]
+formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] }
+auto-format = true
+
+[[language]]
+name = "yaml"
+language-servers = ["yaml-language-server"]
+formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] }
+auto-format = true
+
+[[language]]
+name = "toml"
+language-servers = ["taplo"]
+auto-format = true
+
+[[language]]
+name = "markdown"
+language-servers = ["marksman"]
+formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] }
+auto-format = true
+text-width = 120
+soft-wrap = { enable = true, wrap-at-text-width = true }
+
+[[language]]
+name = "lua"
+formatter = { command = "stylua", args = ["--search-parent-directories", "--stdin-filepath", "%{buffer_name}", "-"] }
+auto-format = true
+
+[[language]]
+name = "bash"
+# shfmt reads the repo .editorconfig once it knows the file name.
+formatter = { command = "shfmt", args = ["--filename", "%{buffer_name}"] }
+auto-format = true
+
+[[language]]
+name = "dockerfile"
+language-servers = ["docker-language-server"]
+
+[[language]]
+name = "docker-compose"
+language-servers = ["docker-language-server", "yaml-language-server"]
diff --git a/herdr/plugins/moniker/moniker.py b/herdr/plugins/moniker/moniker.py
index 3ca8c72..8a51a9e 100644
--- a/herdr/plugins/moniker/moniker.py
+++ b/herdr/plugins/moniker/moniker.py
@@ -221,9 +221,11 @@ def name_pane(pane_id: str, force: bool) -> None:
def on_event() -> None:
try:
- event = json.loads(os.environ.get("HERDR_PLUGIN_EVENT_JSON") or "{}")
+ envelope = json.loads(os.environ.get("HERDR_PLUGIN_EVENT_JSON") or "{}")
except ValueError:
return
+ # Herdr wraps the payload: {"event": "...", "data": {"agent_status": ...}}.
+ event = envelope.get("data") if isinstance(envelope.get("data"), dict) else envelope
if event.get("agent_status") != "working":
return
pane_id = event.get("pane_id") or os.environ.get("HERDR_PANE_ID")
diff --git a/macos/Brewfile b/macos/Brewfile
index c89f365..be32cac 100644
--- a/macos/Brewfile
+++ b/macos/Brewfile
@@ -18,6 +18,7 @@ brew "git-delta" # Syntax-highlighting pager for git
brew "gnu-sed" # GNU implementation of the famous stream editor
brew "go" # Open source programming language
brew "grep" # GNU grep, egrep and fgrep
+brew "helix" # Post-modern modal text editor
brew "btop" # Resource monitor (C++ htop/top alternative)
brew "htop" # Improved top (interactive process viewer)
brew "jq" # Lightweight and flexible command-line JSON processor
diff --git a/packages.conf b/packages.conf
index ba349b1..4f0d969 100644
--- a/packages.conf
+++ b/packages.conf
@@ -46,6 +46,7 @@
golang-backports desktop ppa=ppa:longsleep/golang-backports
git-core desktop,server ppa=ppa:git-core/ppa
fastfetch desktop ppa=ppa:zhangsongcui3371/fastfetch
+helix-editor desktop,server ppa=ppa:maintainers/helix-editor
[Basic development tools]
build-essential desktop,server apt pacman=base-devel
@@ -56,6 +57,7 @@ git desktop,server apt=git-core pacman winget=Git.Git
go desktop apt=golang-go pacman winget=GoLang.Go
inetutils desktop pacman
neovim desktop,server apt pacman winget=Neovim.Neovim
+helix desktop,server apt pacman winget=Helix.Helix
ninja desktop apt=ninja-build pacman winget=Ninja-build.Ninja
pkg-config desktop,server apt pacman
python desktop,server apt=python-is-python3 pacman winget=Python.Python.3.12
diff --git a/sh/helix.sh b/sh/helix.sh
new file mode 100644
index 0000000..28f733b
--- /dev/null
+++ b/sh/helix.sh
@@ -0,0 +1,21 @@
+# helix.sh
+# Helix borrows Neovim's toolbox.
+# https://helix-editor.com
+#
+# Mason installs every language server and formatter the Neovim config
+# uses under ~/.local/share/nvim/mason/bin, and Helix resolves the same
+# binaries by name on PATH. Appending that directory means ruff, ty, vtsls,
+# marksman, taplo, prettier and friends light up in Helix with no second
+# install, while brew, rustup and proto keep winning for anything they also
+# ship. `hx --health ` shows what each language resolved to.
+#
+# No role guard: a box without Mason simply has nothing to append.
+
+_mason_bin="${HOME}/.local/share/nvim/mason/bin"
+if [[ -d "${_mason_bin}" ]]; then
+ case ":${PATH}:" in
+ *":${_mason_bin}:"*) ;;
+ *) export PATH="${PATH}:${_mason_bin}" ;;
+ esac
+fi
+unset _mason_bin