From 81e5c6be67782783b76c2fd9e5506a1db584240d Mon Sep 17 00:00:00 2001 From: David Ibia Date: Tue, 6 Oct 2026 23:02:29 +0100 Subject: [PATCH] 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 --- .local/bin/owt | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ .zshrc | 11 ++++++++ README.md | 4 +++ 3 files changed, 90 insertions(+) diff --git a/.local/bin/owt b/.local/bin/owt index e2f7b71..5f040e6 100755 --- a/.local/bin/owt +++ b/.local/bin/owt @@ -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 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: or /, + 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 Print a workspace path (cd "$(owt path lagos)") owt rm [--force] Remove the workspace and delete its branch if merged. @@ -291,6 +297,73 @@ cmd_path() { printf '%s\n' "$dir" } +# One "/\t\t" 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 /, then in this repo, then 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 ;; diff --git a/.zshrc b/.zshrc index a2e01f0..50373eb 100644 --- a/.zshrc +++ b/.zshrc @@ -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 diff --git a/README.md b/README.md index 8054f08..acfb0ec 100644 --- a/README.md +++ b/README.md @@ -208,12 +208,16 @@ MCP servers: Playwright, Chrome DevTools, Linear, reoclo, time, ast-grep, headro owt new # random city name, e.g. ~/worktrees//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/-` 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