Compare commits

...

5 Commits

Author SHA1 Message Date
David Ibia c155860ff1 chore(nvim): bump the config for updated plugins and its new README
Also lists the snacks file explorer in the Neovim features.
2026-10-10 22:28:58 +01:00
David Ibia b2eca8e1a0 chore(nvim): bump the config to the snacks and blink.cmp modernization
Points the submodule at absolute-vim 2d427aa and updates the README: snacks.nvim replaces Telescope, nvim-notify and dressing, blink.cmp replaces nvim-cmp, and the removed AI plugins are no longer listed.
2026-10-10 22:19:22 +01:00
David Ibia adf62ac948 feat(owt): title workspace tabs and keep agents retitling them
owt title names the tmux windows of the current workspace with one to three words and saves the title in the worktree's git dir so owt open reuses it. owt hook runs on omo's UserPromptSubmit and reminds the agent of the current title so it retitles the tab as the work changes.
2026-10-10 22:12:36 +01:00
David Ibia d92041b6dd feat(tmux): ctrl+click a file path to open it by type
tmux-open-path takes the path under the mouse, strips :line:col, #L, @ and file:// decorations, and resolves it against the pane directory and then its git root. Markdown opens in Markdown Preview (added to Brewfile.apps), images, PDFs and directories in yazi, and everything else in nvim at the clicked line, both in a split beside the pane.
2026-10-10 22:12:36 +01:00
David Ibia 5fbdaa9810 feat(zsh): tab-complete tma and tml from running tmux sessions
tma and tml now complete session names with window counts on Tab. When no tmux server is up, tma starts a new session named after the argument or the current directory, and tml and completion report it instead of erroring. tma switches the client inside tmux, matches names exactly and ignores a cancelled fzf pick; tml takes an optional prefix filter.
2026-10-10 22:12:36 +01:00
8 changed files with 233 additions and 40 deletions
+2 -1
View File
@@ -3,7 +3,8 @@
"UserPromptSubmit": [ "UserPromptSubmit": [
{ {
"hooks": [ "hooks": [
{ "type": "command", "command": "~/.local/bin/omo-notify", "timeout": 10 } { "type": "command", "command": "~/.local/bin/omo-notify", "timeout": 10 },
{ "type": "command", "command": "~/.local/bin/owt hook", "timeout": 5 }
] ]
} }
], ],
+85 -13
View File
@@ -5,6 +5,7 @@
# with the agent launched inside it in its own tmux window. A workspace can # with the agent launched inside it in its own tmux window. A workspace can
# carry any number of Linear issues; the agent renames the branch to # carry any number of Linear issues; the agent renames the branch to
# feature/<keys>-<slug> once it knows them and keeps their status in sync. # feature/<keys>-<slug> once it knows them and keeps their status in sync.
# It also titles the window with one to three words naming the work.
set -euo pipefail set -euo pipefail
@@ -42,6 +43,12 @@ Usage (run from anywhere inside the repo):
feature/<keys>-<slug> (e.g. feature/pra-107-108-imap-timeouts), moves feature/<keys>-<slug> (e.g. feature/pra-107-108-imap-timeouts), moves
each issue to In Progress, and to In Review with the PR link. each issue to In Progress, and to In Review with the PR link.
owt open <name> Open a tmux window running the agent in an existing workspace owt open <name> Open a tmux window running the agent in an existing workspace
owt title <words> Title the tmux windows of the workspace you are in with one to
three words naming the work (owt title imap timeouts). The
agent does this itself; owt open reuses the saved title.
owt hook omo UserPromptSubmit hook (wired in ~/.omo/agent/hooks.json):
inside a workspace, tells the agent its tab title with each
prompt so it keeps the title current; elsewhere prints nothing
owt ls List this repo's workspaces and their branches owt ls List this repo's workspaces and their branches
owt all List the workspaces of every repo with their branches and owt all List the workspaces of every repo with their branches and
source checkouts (works from any directory) source checkouts (works from any directory)
@@ -149,6 +156,27 @@ pr_info() {
} }
ws_dir() { printf '%s/%s/%s\n' "$OWT_ROOT" "$(repo_name)" "$1"; } ws_dir() { printf '%s/%s/%s\n' "$OWT_ROOT" "$(repo_name)" "$1"; }
# A workspace's tab title, kept in the worktree's own git dir so owt open can
# reuse it and removing the worktree removes it too.
title_file() { printf '%s/owt-title\n' "$(git -C "$1" rev-parse --path-format=absolute --git-dir)"; }
ws_title() {
local file
file="$(title_file "$1")"
if [[ -f "$file" ]]; then cat "$file"; fi
}
# tmux windows with a pane open in a workspace, one id per line. tmux reports
# resolved paths, so compare against the resolved workspace path.
ws_windows() {
local real_dir win path
command -v tmux >/dev/null && tmux list-panes -a >/dev/null 2>&1 || return 0
real_dir="$(cd "$1" && pwd -P)"
tmux list-panes -a -F '#{window_id} #{pane_current_path}' | while read -r win path; do
if [[ "$path" == "$real_dir" || "$path" == "$real_dir"/* ]]; then printf '%s\n' "$win"; fi
done | sort -u
}
name_taken() { name_taken() {
[[ -e "$(ws_dir "$1")" ]] || git -C "$(main_root)" show-ref --verify --quiet "refs/heads/feature/$1" [[ -e "$(ws_dir "$1")" ]] || git -C "$(main_root)" show-ref --verify --quiet "refs/heads/feature/$1"
} }
@@ -197,7 +225,8 @@ install_deps() {
# Standing workspace conventions, appended to the agent's system prompt on every launch. # Standing workspace conventions, appended to the agent's system prompt on every launch.
workspace_prompt() { workspace_prompt() {
local name="$1" dir="$2" branch local name="$1" dir="$2" branch title
title="$(ws_title "$dir")"
branch="$(git -C "$dir" symbolic-ref --quiet --short HEAD || printf 'a detached HEAD')" branch="$(git -C "$dir" symbolic-ref --quiet --short HEAD || printf 'a detached HEAD')"
printf 'You are in owt workspace "%s" (%s), a git worktree of %s on branch %s. ' \ printf 'You are in owt workspace "%s" (%s), a git worktree of %s on branch %s. ' \
"$name" "$dir" "$(repo_name)" "$branch" "$name" "$dir" "$(repo_name)" "$branch"
@@ -207,17 +236,26 @@ workspace_prompt() {
# shellcheck disable=SC2016 # shellcheck disable=SC2016
printf '%s is a placeholder: once you know the issues, and before the branch is pushed, rename it with `git branch -m feature/<keys>-<slug>`, where <keys> are the lowercased issue keys joined by hyphens with the team prefix written once (PRA-107 and PRA-108 give pra-107-108) and <slug> is a 2 to 5 word kebab-case summary. ' "$branch" printf '%s is a placeholder: once you know the issues, and before the branch is pushed, rename it with `git branch -m feature/<keys>-<slug>`, where <keys> are the lowercased issue keys joined by hyphens with the team prefix written once (PRA-107 and PRA-108 give pra-107-108) and <slug> is a 2 to 5 word kebab-case summary. ' "$branch"
fi fi
if [[ -n "$title" ]]; then
printf 'Its tmux tab is titled "%s". ' "$title"
else
printf 'Its tmux tab shows the workspace name until you title it. '
fi
# shellcheck disable=SC2016
printf 'As soon as you know what the work is, and again whenever its focus changes, run `owt title <words>` with one to three short words naming it, never a sentence (for example `owt title imap timeouts`). '
printf 'When a pull request is open, comment on each issue with a short summary and the PR link, and move each to In Review.' printf 'When a pull request is open, comment on each issue with a short summary and the PR link, and move each to In Review.'
} }
# launch <name> <dir> [initial message] # launch <name> <dir> [initial message]
launch() { launch() {
local name="$1" dir="$2" args local name="$1" dir="$2" args window
shift 2 shift 2
args=(--name "$name" --append-system-prompt "$(workspace_prompt "$name" "$dir")" "$@") args=(--name "$name" --append-system-prompt "$(workspace_prompt "$name" "$dir")" "$@")
window="$(ws_title "$dir")"
window="${window:-$name}"
if [[ -n "${TMUX:-}" ]]; then if [[ -n "${TMUX:-}" ]]; then
tmux new-window -n "$name" -c "$dir" -- "$OWT_AGENT" "${args[@]}" tmux new-window -n "$window" -c "$dir" -- "$OWT_AGENT" "${args[@]}"
say "opened tmux window '$name' running $OWT_AGENT" say "opened tmux window '$window' running $OWT_AGENT"
else else
say "not in tmux; start with: cd $(printf '%q' "$dir") && $OWT_AGENT$(printf ' %q' "${args[@]}")" say "not in tmux; start with: cd $(printf '%q' "$dir") && $OWT_AGENT$(printf ' %q' "${args[@]}")"
fi fi
@@ -278,6 +316,45 @@ cmd_open() {
launch "$name" "$dir" launch "$name" "$dir"
} }
# Title the tmux windows of the current workspace with what is being worked on.
cmd_title() {
local name dir title words=() win renamed=0
name="$(current_ws)" || die "not inside an owt workspace"
dir="$(ws_dir "$name")"
[[ -d "$dir" ]] || die "no workspace at $dir"
read -ra words <<<"$(printf '%s ' "$@" | tr -s '[:space:]' ' ' | tr -d '[:cntrl:]')"
[[ ${#words[@]} -ge 1 && ${#words[@]} -le 3 ]] \
|| die "a title is one to three words naming the work, e.g. owt title imap timeouts"
title="${words[*]}"
printf '%s\n' "$title" >"$(title_file "$dir")"
while read -r win; do
tmux rename-window -t "$win" "$title"
renamed=$((renamed + 1))
done < <(ws_windows "$dir")
say "titled '$name' as '$title' ($renamed tmux window(s) renamed)"
}
# omo UserPromptSubmit hook. Inside a workspace it hands the agent the tab's
# current title with each prompt, so the title keeps up with the work.
cmd_hook() {
local name dir title context
[[ -t 0 ]] || cat >/dev/null
git rev-parse --git-dir >/dev/null 2>&1 || return 0
name="$(current_ws)" || return 0
dir="$(ws_dir "$name")"
[[ -d "$dir" ]] || return 0
title="$(ws_title "$dir")"
if [[ -n "$title" ]]; then
context="This is owt workspace \"$name\" and its tmux tab is titled \"$title\". If this prompt changes what the work is about, or the focus shifts while you work, retitle it with \`owt title <words>\`: one to three short words naming the work, never a sentence."
else
context="This is owt workspace \"$name\" and its tmux tab has no title yet, so it shows the workspace name. As soon as you know what the work is, title it with \`owt title <words>\`: one to three short words naming the work, never a sentence (for example \`owt title imap timeouts\`). Retitle it whenever the focus shifts."
fi
context="${context//\\/\\\\}"
context="${context//\"/\\\"}"
printf '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"%s"}}\n' "$context"
}
cmd_ls() { cmd_ls() {
local prefix local prefix
prefix="$OWT_ROOT/$(repo_name)/" prefix="$OWT_ROOT/$(repo_name)/"
@@ -471,14 +548,8 @@ cmd_done() {
fi fi
# Windows with a pane open in the workspace, collected before the directory goes away. # Windows with a pane open in the workspace, collected before the directory goes away.
# tmux reports resolved paths, so compare against the resolved workspace path. local windows=() win
local windows=() real_dir while read -r win; do windows+=("$win"); done < <(ws_windows "$dir")
real_dir="$(cd "$dir" && pwd -P)"
if command -v tmux >/dev/null && tmux list-panes -a >/dev/null 2>&1; then
while read -r win path; do
[[ "$path" == "$real_dir" || "$path" == "$real_dir"/* ]] && windows+=("$win")
done < <(tmux list-panes -a -F '#{window_id} #{pane_current_path}' | sort -u)
fi
if [[ $merged == yes && $remote_branch == yes ]]; then if [[ $merged == yes && $remote_branch == yes ]]; then
git -C "$root" push --quiet origin --delete "$branch" && say "deleted remote branch $branch" git -C "$root" push --quiet origin --delete "$branch" && say "deleted remote branch $branch"
@@ -494,7 +565,6 @@ cmd_done() {
say "deleted local branch $branch" say "deleted local branch $branch"
fi fi
local win
for win in $(printf '%s\n' "${windows[@]+"${windows[@]}"}" | sort -u); do for win in $(printf '%s\n' "${windows[@]+"${windows[@]}"}" | sort -u); do
tmux kill-window -t "$win" 2>/dev/null || true tmux kill-window -t "$win" 2>/dev/null || true
done done
@@ -504,6 +574,8 @@ case "${1:-}" in
new) shift; cmd_new "$@" ;; new) shift; cmd_new "$@" ;;
done) shift; cmd_done "$@" ;; done) shift; cmd_done "$@" ;;
open) shift; cmd_open "$@" ;; open) shift; cmd_open "$@" ;;
title) shift; cmd_title "$@" ;;
hook) shift; cmd_hook ;;
ls) shift; cmd_ls ;; ls) shift; cmd_ls ;;
all) shift; cmd_all ;; all) shift; cmd_all ;;
cd) shift; cmd_cd "$@" ;; cd) shift; cmd_cd "$@" ;;
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env zsh
# Open the file path under the mouse in a tmux pane, routed by type:
# markdown -> Markdown Preview (GUI)
# images, pdf, dirs -> yazi in a split pane (image preview via passthrough)
# anything else -> nvim in a split pane, jumping to :line when present
#
# Called from a tmux mouse binding:
# tmux-open-path <pane_id> <pane_current_path> <mouse_x> <mouse_line>
emulate -L zsh
setopt extendedglob
pane=$1 cwd=$2 x=$3 line=$4
fail() { tmux display-message -d 2000 "open-path: $1"; exit 1 }
[[ -n $pane && -n $cwd && $x == <-> ]] || fail "usage: tmux-open-path PANE CWD X LINE"
# Grab the run of path-like characters around the clicked column. Whitespace,
# quotes, brackets and box-drawing borders end a token.
local stop=$' \t"\'`()[]{}<>|│┃║─━'
(( i = x + 1 )) # mouse_x is 0-based, zsh strings are 1-based
(( i <= ${#line} )) || fail "nothing under cursor"
[[ $stop == *${line[i]}* ]] && fail "nothing under cursor"
(( s = i )); while (( s > 1 )) && [[ $stop != *${line[s-1]}* ]]; do (( s-- )); done
(( e = i )); while (( e < ${#line} )) && [[ $stop != *${line[e+1]}* ]]; do (( e++ )); done
token=${line[s,e]}
# Normalise: file:// URLs, @mentions, trailing punctuation, :line[:col], #Lnn.
token=${token#file://}
token=${token#@}
token=${token%%[.,;:!?]##}
lnum=
if [[ $token == (#b)(*)\#L(<->)* ]]; then
token=$match[1] lnum=$match[2]
elif [[ $token == (#b)(*):(<->):<-> || $token == (#b)(*):(<->) ]]; then
token=$match[1] lnum=$match[2]
fi
[[ -n $token ]] || fail "nothing under cursor"
# Resolve against the pane's directory, then the enclosing git root.
token=${~token} # expand a leading ~
# (not "path": zsh ties that name to $PATH)
file=
if [[ $token == /* ]]; then
[[ -e $token ]] && file=$token
else
for base in $cwd $(git -C $cwd rev-parse --show-toplevel 2>/dev/null); do
[[ -e $base/$token ]] && { file=$base/$token; break }
done
fi
[[ -n $file ]] || fail "no such file: $token"
file=${file:A}
if [[ -d $file ]]; then
tmux split-window -h -t $pane -c $file "yazi ${(q)file}"
exit
fi
case ${file:e:l} in
md|markdown|mdx|mdown|mkd|mkdn)
open -a "Markdown Preview" $file ;;
png|jpg|jpeg|gif|webp|avif|svg|bmp|tif|tiff|heic|ico|pdf)
tmux split-window -h -t $pane -c ${file:h} "yazi ${(q)file}" ;;
*)
tmux split-window -h -t $pane -c $cwd "nvim ${lnum:++$lnum} ${(q)file}" ;;
esac
+5
View File
@@ -142,6 +142,11 @@ bind-key -T copy-mode-vi 'C-\' select-pane -l
set -g set-clipboard on set -g set-clipboard on
set -as terminal-features ',*:clipboard' set -as terminal-features ',*:clipboard'
# Ctrl+click a file path in any pane (omo, shell, logs) to open it:
# markdown in Markdown Preview, images/pdf/dirs in yazi, the rest in nvim.
# yazi and nvim split beside the clicked pane. See ~/.local/bin/tmux-open-path.
bind -n C-MouseDown1Pane run-shell -b "~/.local/bin/tmux-open-path #{q:pane_id} #{q:pane_current_path} #{mouse_x} #{q:mouse_line}"
# #
# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf) # Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
run '~/.tmux/plugins/tpm/tpm' run '~/.tmux/plugins/tpm/tpm'
+62 -11
View File
@@ -198,11 +198,29 @@ _exists() {
# TMUX + FZF Setup Start # TMUX + FZF Setup Start
# #
# Print session names of the running tmux server, one per line. Prints nothing
# and returns 1 when no server is up, so callers and completion fail cleanly.
function _tmux_sessions_raw() {
tmux list-sessions -F '#S' 2>/dev/null
}
# tml [prefix]: list tmux sessions, optionally only those starting with prefix.
function tml() { function tml() {
sessions=$(tmux ls | sed -E 's/:.*$//') if ! _exists tmux; then
for session in $sessions; do echo "tml: tmux is not installed" >&2
echo $session return 127
done fi
local sessions
sessions=$(_tmux_sessions_raw)
if [[ -z $sessions ]]; then
echo "tml: no tmux server running (start one with: tma <name>)" >&2
return 1
fi
if [[ -n $1 ]]; then
sessions=$(print -r -- "$sessions" | grep -- "^$1")
[[ -z $sessions ]] && { echo "tml: no session matches '$1'" >&2; return 1; }
fi
print -r -- "$sessions"
} }
@@ -218,18 +236,37 @@ function tmx(){
fi fi
} }
# tma [session]: attach to a tmux session. With no argument, pick one with fzf.
# Inside tmux it switches the client instead of nesting. If no server is up,
# it starts a new session (named after the argument, or the current directory).
function tma() { function tma() {
if ! _exists tmux; then
echo "tma: tmux is not installed" >&2
return 127
fi
# If no argument is passed, show the list of tmux sessions with fzf local target="$1"
if [ -z "$1" ]; then if [[ -z $(_tmux_sessions_raw) ]]; then
tmux attach -t $(tml | fzf) target="${target:-${PWD:t}}"
echo "tma: no tmux server running, starting new session '$target'" >&2
tmux new-session -s "$target"
return return
fi fi
# If an argument is passed, attach to the session with that name if [[ -z $target ]]; then
tmux attach -t $1 target=$(_tmux_sessions_raw | fzf --reverse --prompt="tma> ") || return 1
elif ! tmux has-session -t "=$target" 2>/dev/null; then
echo "tma: no session named '$target'. Running sessions:" >&2
_tmux_sessions_raw | sed 's/^/ /' >&2
return 1
fi
if [[ -n $TMUX ]]; then
tmux switch-client -t "=$target"
else
tmux attach-session -t "=$target"
fi
} }
function get_tmn_recommendation() { function get_tmn_recommendation() {
@@ -258,7 +295,7 @@ function tmn() {
} }
_fzf_complete_tma() { _fzf_complete_tma() {
_fzf_complete --multi --reverse --prompt="tma> " -- "$@" < <(tmux ls | sed -E 's/:.*$//') _fzf_complete --reverse --prompt="tma> " -- "$@" < <(_tmux_sessions_raw)
} }
_fzf_complete_tmn() { _fzf_complete_tmn() {
@@ -353,6 +390,20 @@ autoload -Uz compinit
compinit compinit
# End of Docker CLI completions # End of Docker CLI completions
# Tab completion for tma/tml: offer running tmux sessions with window counts.
# Registered after the last compinit so it is not discarded by a re-init.
_tmux_session_names() {
local -a sessions
sessions=(${(f)"$(tmux list-sessions -F '#S:#{session_windows} windows#{?session_attached, (attached),}' 2>/dev/null)"})
if (( ! $#sessions )); then
_message 'no tmux server running'
return 1
fi
_describe -t tmux-sessions 'tmux session' sessions
}
compdef '_arguments "1:tmux session:_tmux_session_names"' tma tml
zstyle ':completion:*:*:(tma|tml):*:messages' format '%F{yellow}%d%f'
sshm() { TERM=xterm-256color command sshm "$@"; } sshm() { TERM=xterm-256color command sshm "$@"; }
source <(sshm completion zsh) source <(sshm completion zsh)
+1
View File
@@ -20,6 +20,7 @@ cask "charles"
cask "cyberduck" cask "cyberduck"
cask "chromium" cask "chromium"
cask "johannesnagl/tap/showmd", trusted: true cask "johannesnagl/tap/showmd", trusted: true
cask "markdown-preview"
# AI and agents # AI and agents
cask "conductor" cask "conductor"
+10 -14
View File
@@ -22,7 +22,7 @@ This repository contains personal dotfiles for configuring a complete developmen
## Features ## Features
- **Neovim Configuration**: Extensive Lua-based configuration with LSP support, debugging, and 50+ productivity plugins - **Neovim Configuration**: Extensive Lua-based configuration with LSP support, debugging, and around 40 productivity plugins
- **Terminal Emulators**: Configurations for Ghostty and WezTerm with custom themes and visual effects - **Terminal Emulators**: Configurations for Ghostty and WezTerm with custom themes and visual effects
- **Shell Environment**: Optimized Zsh configuration with Starship prompt and useful aliases - **Shell Environment**: Optimized Zsh configuration with Starship prompt and useful aliases
- **AI Integration**: OmO and Claude Code configuration, shared agent preferences, and `owt` workspaces that give each agent task its own git worktree - **AI Integration**: OmO and Claude Code configuration, shared agent preferences, and `owt` workspaces that give each agent task its own git worktree
@@ -122,13 +122,12 @@ Restart OmO and Claude Code after applying changes. On first use, Claude may ask
- **Themes**: Multiple colorschemes including Catppuccin, Tokyo Night, and Rose Pine - **Themes**: Multiple colorschemes including Catppuccin, Tokyo Night, and Rose Pine
Key features: Key features:
- AI-powered coding assistance (Claude, OmO) - snacks.nvim for the fuzzy picker, file explorer, notifications and input prompts (replacing Telescope, nvim-notify and dressing)
- Advanced Git integration - Completion with blink.cmp (replacing nvim-cmp)
- Advanced Git integration, including inline current-line blame
- Flutter and mobile development support - Flutter and mobile development support
- Python environment management - Python environment management
- Integrated debugging (DAP) - Integrated debugging (DAP)
- File tree navigation
- Fuzzy finding with Telescope
- Terminal integration - Terminal integration
### Terminal Emulators ### Terminal Emulators
@@ -153,11 +152,12 @@ Key features:
- Starship prompt with custom configuration - Starship prompt with custom configuration
- Auto-completion and syntax highlighting - Auto-completion and syntax highlighting
- 1Password as the SSH agent, an `sshm` wrapper with completion, and `fs [session]` to reach the remote dev box over mosh into a named tmux session - 1Password as the SSH agent, an `sshm` wrapper with completion, and `fs [session]` to reach the remote dev box over mosh into a named tmux session
- tmux session helpers: `tma [session]` attaches (fzf picker with no argument, switches the client when already inside tmux) and `tml [prefix]` lists sessions. Both Tab-complete the running sessions with their window counts. When no tmux server is up, `tma` starts a new session named after the argument or the current directory, and `tml` and completion say so instead of erroring
- Resets mouse-reporting modes at each prompt, so an unclean SSH or tmux disconnect cannot leave the terminal spewing escape codes - Resets mouse-reporting modes at each prompt, so an unclean SSH or tmux disconnect cannot leave the terminal spewing escape codes
### Development Tools ### Development Tools
- **tmux** (`.tmux.conf`): Terminal multiplexer with custom key bindings; sets the terminal title to the session name, after the omo status marker when there is one - **tmux** (`.tmux.conf`): Terminal multiplexer with custom key bindings; sets the terminal title to the session name, after the omo status marker when there is one. Ctrl+click a file path in any pane (omo, a shell, logs) to open it with `tmux-open-path`: markdown in Markdown Preview, images, PDFs and directories in yazi, anything else in nvim at the clicked `:line`. yazi and nvim open in a split beside the clicked pane
- **Zed** (`.config/zed/keymap.json`): Editor key bindings. Settings stay local because they reference machine-specific agent paths - **Zed** (`.config/zed/keymap.json`): Editor key bindings. Settings stay local because they reference machine-specific agent paths
- **tmux server** (`.tmux-server.conf`): Lightweight tmux config for remote servers - **tmux server** (`.tmux-server.conf`): Lightweight tmux config for remote servers
- **Git**: Global gitignore patterns (`.rgignore`, `.gitignore`) - **Git**: Global gitignore patterns (`.rgignore`, `.gitignore`)
@@ -174,7 +174,7 @@ Key features:
OpenCode is retired. Its agents, commands and plugins are left in `.config/opencode/` for reference, are no longer installed or linked, and its skills and slash commands moved here. OpenCode is retired. Its agents, commands and plugins are left in `.config/opencode/` for reference, are no longer installed or linked, and its skills and slash commands moved here.
- `omo.jsonc` (linked as `~/.omo/omo.jsonc`): model routing for the main session, task categories and curated agents. `design-critique-a` and `design-critique-b` are the two lanes the Impeccable critique delegates its assessments to. - `omo.jsonc` (linked as `~/.omo/omo.jsonc`): model routing for the main session, task categories and curated agents. `design-critique-a` and `design-critique-b` are the two lanes the Impeccable critique delegates its assessments to.
- `hooks.json` (linked as `~/.omo/agent/hooks.json`): runs `omo-notify` when you send a prompt, when a session stops and when it asks you something, for the sound, the notification and the coloured tab marker. - `hooks.json` (linked as `~/.omo/agent/hooks.json`): runs `omo-notify` when you send a prompt, when a session stops and when it asks you something, for the sound, the notification and the coloured tab marker. It also runs `owt hook` with each prompt, which tells an agent in an `owt` workspace its current tab title so it retitles the tab as the work changes. omo runs a new hook only after you approve it with `/hooks trust <id>` (ids are in `/hooks list`).
- `prompts/` (linked as `~/.omo/agent/prompts`): slash commands. - `prompts/` (linked as `~/.omo/agent/prompts`): slash commands.
- `/linear-sync [notes]` posts a progress comment on the workspace's Linear issues and sets them to In Progress, or In Review once a PR is open. - `/linear-sync [notes]` posts a progress comment on the workspace's Linear issues and sets them to In Progress, or In Review once a PR is open.
- `/owt-done [notes]` finishes a workspace after its PR merges: it comments on each issue and moves it to Done, then runs `owt done --detach`. It asks before discarding uncommitted or unmerged work. - `/owt-done [notes]` finishes a workspace after its PR merges: it comments on each issue and moves it to Done, then runs `owt done --detach`. It asks before discarding uncommitted or unmerged work.
@@ -196,21 +196,16 @@ owt ls # workspaces and their current branches
owt all # workspaces of every repo, with branches and source checkouts (from anywhere) owt all # workspaces of every repo, with branches and source checkouts (from anywhere)
owt cd # jump into a workspace with fzf; owt cd lagos goes straight there owt cd # jump into a workspace with fzf; owt cd lagos goes straight there
owt open lagos # reopen the agent in a workspace owt open lagos # reopen the agent in a workspace
owt title imap timeouts # title the workspace's tmux tab (one to three words); the agent does this itself
owt done # remove the workspace you are in once its work is merged owt done # remove the workspace you are in once its work is merged
``` ```
`owt new` starts from the latest `origin` default branch, copies local `.env` files, installs dependencies, and opens a tmux window running omo with the workspace conventions: rename the placeholder branch to `feature/<keys>-<slug>` before pushing, and keep the Linear issues' statuses in sync. Workspaces share the main checkout's omo memory through a link under `~/.omo/memory/agents/`. `owt new` starts from the latest `origin` default branch, copies local `.env` files, installs dependencies, and opens a tmux window running omo with the workspace conventions: rename the placeholder branch to `feature/<keys>-<slug>` before pushing, title the tab with one to three words naming the work (`owt title`) and update it when the focus changes, and keep the Linear issues' statuses in sync. The title is saved with the worktree, so `owt open` reopens the tab under it. Workspaces share the main checkout's omo memory through a link under `~/.omo/memory/agents/`.
`owt cd` works from any directory. Inside a repository the picker starts filtered to that repo's workspaces and jumps straight in when only one matches. The `owt` function in `.zshrc` performs the `cd`; without it, `owt cd` only prints the path. `owt cd` works from any directory. Inside a repository the picker starts filtered to that repo's workspaces and jumps straight in when only one matches. The `owt` function in `.zshrc` performs the `cd`; without it, `owt cd` only prints the path.
`owt done` refuses to discard work unless you pass `--force`. Work counts as merged when the branch is part of the default branch or its Gitea PR is merged (checked with `tea`). It then deletes the remote and local branch, removes the worktree and its memory link, and closes tmux windows open in it. `OWT_ROOT` (default `~/worktrees`) and `OWT_AGENT` (default `omo`) override the defaults; `owt --help` lists every option. `owt done` refuses to discard work unless you pass `--force`. Work counts as merged when the branch is part of the default branch or its Gitea PR is merged (checked with `tea`). It then deletes the remote and local branch, removes the worktree and its memory link, and closes tmux windows open in it. `OWT_ROOT` (default `~/worktrees`) and `OWT_AGENT` (default `omo`) override the defaults; `owt --help` lists every option.
#### Neovim AI Integration
- **CodeCompanion**: AI pair programming with multiple providers
- **Gen.nvim**: Local LLM integration
- **LLM.nvim**: Additional LLM capabilities
## Configuration ## Configuration
### Environment Variables ### Environment Variables
@@ -231,6 +226,7 @@ Custom scripts and binaries are stored in `.local/bin/`:
- `claude-tmux`: Claude integration with tmux - `claude-tmux`: Claude integration with tmux
- `cldir`: Change directory with Claude context - `cldir`: Change directory with Claude context
- `owt`: Git worktree workspaces for coding agents (see [Agent workspaces](#agent-workspaces-owt)) - `owt`: Git worktree workspaces for coding agents (see [Agent workspaces](#agent-workspaces-owt))
- `tmux-open-path`: Backs tmux's Ctrl+click. Takes the path under the mouse, strips `:line:col`, `#L10`, `@` and `file://`, resolves it against the pane's directory and then its git root, and routes it by type: markdown to Markdown Preview (`cask "markdown-preview"` in `Brewfile.apps`), images, PDFs and directories to yazi, the rest to nvim
- `omo-notify`: Sound, tab marker and notification for OmO's hooks. Ghostty posts the notification, so clicking it opens the session's tab and pane - `omo-notify`: Sound, tab marker and notification for OmO's hooks. Ghostty posts the notification, so clicking it opens the session's tab and pane
- `omo-status`: Coloured marker in front of the Ghostty tab title for each tmux session running omo: 🔴 stopped with an error, 🟡 waiting for your answer, 🟢 finished, 🔵 working. A tab with several agents shows the most urgent one; focusing the tab clears red, amber and green, and undoes any title you gave the tab by hand so it always shows its session name - `omo-status`: Coloured marker in front of the Ghostty tab title for each tmux session running omo: 🔴 stopped with an error, 🟡 waiting for your answer, 🟢 finished, 🔵 working. A tab with several agents shows the most urgent one; focusing the tab clears red, amber and green, and undoes any title you gave the tab by hand so it always shows its session name
- `docker-nuke`: Force-restart a wedged Docker Desktop, capturing diagnostics to `~/.local/state/docker-nuke/` first (`--dry-run` to preview, `--no-restart` to leave it down, `--deep` for root helpers) - `docker-nuke`: Force-restart a wedged Docker Desktop, capturing diagnostics to `~/.local/state/docker-nuke/` first (`--dry-run` to preview, `--no-restart` to leave it down, `--deep` for root helpers)