feat(owt): add all and cd commands for workspaces across repos

- owt all lists the workspaces of every repo with their branch and
  source checkout, from any directory
- owt cd jumps into a workspace by name, repo/name or an fzf picker
  that starts filtered to the current repo
- An owt function in .zshrc performs the cd, since a script cannot
  change its parent shell's directory
This commit is contained in:
David Ibia
2026-10-06 23:02:29 +01:00
parent d0c2895797
commit 81e5c6be67
3 changed files with 90 additions and 0 deletions
+75
View File
@@ -43,6 +43,12 @@ Usage (run from anywhere inside the repo):
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 ls List this repo's workspaces and their branches
owt all List the workspaces of every repo with their branches and
source checkouts (works from any directory)
owt cd [name] Jump into a workspace of any repo: <name> or <repo>/<name>,
otherwise pick with fzf (filtered to this repo's workspaces
when run inside one; jumps straight in if only one matches).
Needs the owt shell function in .zshrc; without it, prints the path.
owt path <name> Print a workspace path (cd "$(owt path lagos)")
owt rm <name> [--force]
Remove the workspace and delete its branch if merged.
@@ -291,6 +297,73 @@ cmd_path() {
printf '%s\n' "$dir"
}
# One "<repo>/<name>\t<branch>\t<source checkout>" line per workspace under
# $OWT_ROOT, across every repo. Works outside a repository.
all_workspaces() {
local dir common branch src
for dir in "$OWT_ROOT"/*/*/; do
dir="${dir%/}"
[[ -f "$dir/.git" ]] || continue
if common="$(git -C "$dir" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)"; then
branch="$(git -C "$dir" symbolic-ref --quiet --short HEAD || printf '(detached)')"
src="$(dirname "$common")"
if [[ "$src" == "$HOME"/* ]]; then src='~'/"${src#"$HOME"/}"; fi
else
branch="?"
src="(source checkout missing)"
fi
printf '%s\t%s\t%s\n' "${dir#"$OWT_ROOT"/}" "$branch" "$src"
done
}
cmd_all() {
local rows
rows="$(all_workspaces)"
if [[ -z "$rows" ]]; then
say "no workspaces under $OWT_ROOT"
return 0
fi
{ printf 'WORKSPACE\tBRANCH\tSOURCE\n'; printf '%s\n' "$rows"; } | column -t -s $'\t'
}
# Print a workspace path for the owt shell function in .zshrc to cd into.
cmd_cd() {
[[ $# -le 1 ]] || die "usage: owt cd [name]"
local query="${1:-}" rows repo="" common picked=""
rows="$(all_workspaces)"
[[ -n "$rows" ]] || die "no workspaces under $OWT_ROOT"
if common="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)"; then
repo="$(basename "$(dirname "$common")")"
fi
# Exact <repo>/<name>, then <name> in this repo, then <name> in exactly one repo.
if [[ -n "$query" ]]; then
picked="$(printf '%s\n' "$rows" | awk -F'\t' -v q="$query" -v r="$repo" '
{ ws = $1; name = substr(ws, index(ws, "/") + 1) }
ws == q || ws == r "/" q { print ws; found = 1; exit }
name == q { hits[++n] = ws }
END { if (!found && n == 1) print hits[1] }')"
fi
if [[ -z "$picked" ]]; then
command -v fzf >/dev/null || die "no workspace named '$query'; install fzf to pick one, or see 'owt all'"
if [[ -z "$query" && -n "$repo" && $'\n'"$rows" == *$'\n'"$repo/"* ]]; then query="$repo/"; fi
picked="$(paste <(cut -f1 <<<"$rows") <(column -t -s $'\t' <<<"$rows") \
| fzf --delimiter=$'\t' --with-nth=2.. --query="$query" --select-1 --exit-0 \
--height=40% --reverse --prompt='owt cd> ' \
| cut -f1)" || true
if [[ -z "$picked" ]]; then
[[ -z "$query" ]] || die "no workspace matches '$query'"
die "no workspace selected"
fi
fi
if [[ -t 1 ]]; then
say "printing the path only; the owt shell function in .zshrc does the cd"
fi
printf '%s/%s\n' "$OWT_ROOT" "$picked"
}
cmd_rm() {
local name="" force=0
while [[ $# -gt 0 ]]; do
@@ -432,6 +505,8 @@ case "${1:-}" in
done) shift; cmd_done "$@" ;;
open) shift; cmd_open "$@" ;;
ls) shift; cmd_ls ;;
all) shift; cmd_all ;;
cd) shift; cmd_cd "$@" ;;
path) shift; cmd_path "$@" ;;
rm) shift; cmd_rm "$@" ;;
""|-h|--help|help) usage ;;
+11
View File
@@ -357,6 +357,17 @@ sshm() { TERM=xterm-256color command sshm "$@"; }
source <(sshm completion zsh)
# `owt cd` jumps into an owt workspace. A script cannot change this shell's
# directory, so owt prints the path and this wrapper does the cd.
owt() {
if [[ "$1" == cd ]]; then
local dir
dir="$(command owt "$@")" && cd "$dir"
else
command owt "$@"
fi
}
export SSH_AUTH_SOCK=~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock
# Connect to flowstate-remote over mosh instead of ssh. Mosh is UDP with no TCP
+4
View File
@@ -208,12 +208,16 @@ MCP servers: Playwright, Chrome DevTools, Linear, reoclo, time, ast-grep, headro
owt new # random city name, e.g. ~/worktrees/<repo>/lagos on branch feature/lagos
owt new PRA-107 PRA-108 # same, and tell the agent which Linear issues to start on
owt ls # workspaces and their current branches
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 open lagos # reopen the agent in a workspace
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 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.
#### Neovim AI Integration