Try-style workspace switcher & scaffolder written in Go.
ws keeps all of your project directories under a predictable root (default ~/ws), lets you jump between them with a fuzzy-search TUI, and bootstraps new ones using language-specific templates.
- Installation
- Getting Started
- CLI Usage
- Templates
- Configuration
- Performance Tuning
- Hooks
- TUI Reference
- Notes
- Troubleshooting
- Build locally:
make build(binary drops at./bin/ws) - Install a prebuilt single-file binary:
curl -L <your-release-url>/ws-darwin-arm64 -o /usr/local/bin/ws && chmod +x /usr/local/bin/ws
- bash/zsh:
./ws init | source /dev/stdinto test; persist it by appending once to your profile, e.g.,./ws init >> ~/.zshrcor~/.bashrc. - fish: run
./ws init, then paste the printed Fish snippet into~/.config/fish/config.fish(append once so future shells have the functions)
- Bash:
ws completion bash > /etc/bash_completion.d/ws(or another directory sourced by your profile) - Zsh:
ws completion zsh > ${fpath[1]}/_ws(choose any writable directory in$fpath) - Fish:
ws completion fish > ~/.config/fish/completions/ws.fish - Man pages:
ws man ./dist/man && sudo cp dist/man/*.1 /usr/local/share/man/man1 && mandb make docsbuilds./bin/wsand writes all completions + man pages underdist/
make dist → build binaries for macOS & Linux, produce tarballs + SHA256SUMS.txt
make build → build current platform only
make docs → completions + man pages
make clean → clean artifacts
- Export
WS_PATH=~/ws(or rely on the default). - Run
wsto open the TUI, search existing workspaces with live filtering, and hit Enter to open one. - Create something new with
ws new demo --type goor jump straight intows demo(auto-creates if missing). - Optional niceties:
- Add shell init output to your profile so
wsaliases/functions stay current. - Install completions + man pages for inline help.
- Add shell init output to your profile so
ws— launch the TUI with live search plus a “+ Create” row.ws <name>— jump to (or create) a workspace named<name>via the shell helper_rt.ws edit <name>— open a workspace withWS_EDITOR/EDITOR.ws ls— print Name/LastTouched/OpenCount/Score table.
ws new [name] --type go— scaffold from embedded templates (go,node,ruby,python,php,rust).ws clone <url> [name]— clone a Git repo into a dated workspace.ws worktree dir [name]— create a Git worktree underWS_PATH.ws . [name]— shortcut forws worktree dirusing the current repo.
ws rm <name>— delete a workspace (falls back to renaming it to<name>.deleted-<timestamp>if removal fails).ws doctor— show environment + config diagnostics.ws version/ws --version— printws <semver> (<commit>).
| Template | Files (copied verbatim) |
|---|---|
| go | cmd/app/main.go, internal/app/app.go, go.mod, Makefile, .gitignore |
| node | src/index.ts, package.json, tsconfig.json |
| ruby | bin/app, lib/app.rb, Gemfile |
| python | pyproject.toml, src/app/__init__.py, src/app/main.py |
| php | public/index.php, src/App.php, composer.json |
| rust | src/main.rs, Cargo.toml |
Set WS_TEMPLATES_PATH=/path/to/templates to point at a directory that contains per-language folders (e.g., /path/to/templates/go/...). Files inside override folders are used first; anything missing falls back to the embedded defaults so partial overlays work fine.
WS_PATH— workspace root (default~/ws)WS_DATE_FORMAT— Go time layout used for directory prefixes (default2006-01-02)WS_EDITOR/EDITOR— editor command used byws editWS_TEMPLATES_PATH— optional path whose<type>/...folders override embedded templatesWS_DB_PATH— BoltDB path for recency/open-count trackingWS_INDEX_CACHE_PATH— JSON cache file for workspace scans (default~/.cache/ws/index.cache)WS_INDEX_CACHE_TTL_MS— cache freshness window in milliseconds (default3000, clamped0–600000)WS_TUI_DEBOUNCE_MS— debounce window for TUI searches (default120, clamped50–1000)TOAST_MS— toast lifespan in milliseconds (default2000, clamped800–5000)
- Workspace scans write to
~/.cache/ws/index.cache(override withWS_INDEX_CACHE_PATH). Entries capture directory names/paths/mtimes and invalidate whenWS_PATHchanges, the fingerprint drifts, or the TTL elapses. WS_INDEX_CACHE_TTL_MS=0forces live scans every time; raise it (max600000) to keep large caches warm.- The TUI throttles
Searchcalls viaWS_TUI_DEBOUNCE_MS. Lower values favor responsiveness, higher values reduce filesystem churn. - All performance knobs are opt-in environment variables; defaults already target fast interactive use.
ws can run custom scripts around lifecycle events. Place executables under ~/.config/ws/plugins.d/<event>.d/ (override with WS_PLUGINS_PATH=/path/to/plugins). Scripts inside <event>.d run first, followed by any scoped to <event>.d/<type>/ (e.g., post_create.d/go/). Everything runs sequentially in lexical order with a hard cap of 64 scripts per event.
Supported events:
pre_create,post_createpre_clone,post_clonepre_worktree,post_worktreepre_open,post_open(fires for the TUI,ws edit, and_rtjumps)
Each hook receives JSON on stdin plus helper environment variables. Example payload:
{
"event": "post_create",
"ws_path": "/Users/alice/ws/2025-03-15-demo",
"ws_name": "demo",
"type": "go",
"repo_url": "",
"branch": "",
"timestamp": 1710500000,
"env": {
"WS_PATH": "/Users/alice/ws",
"WS_DATE_FORMAT": "2006-01-02",
"WS_TEMPLATES_PATH": "",
"WS_DB_PATH": "/Users/alice/.local/share/ws/ws.db"
}
}Environment variables exported for each script: WS_EVENT, WS_WORKSPACE_PATH, WS_WORKSPACE_NAME, WS_TYPE, WS_REPO_URL, WS_BRANCH, plus the ambient shell env. A tiny hook might look like:
#!/bin/sh
read payload
printf "%s\n" "$payload" >> "$WS_PATH/hooks.log"Behavioral notes:
- Default timeout is 3s per script. Override with
WS_HOOK_TIMEOUT_MS(clamped100–60000). Timed-out processes are killed and logged. - Script stderr is forwarded to the user; stdout is ignored unless you redirect it yourself.
- Non-zero exits and timeouts are warnings by default and do not stop the main command. Set
WS_FAIL_ON_HOOK_ERROR=1to make failures fatal. - Missing plugin directories are ignored, so you can adopt hooks gradually.
- Enter — open the highlighted workspace or run “+ Create”
- ↑/↓ or Ctrl-P/N or J/K — move the selection
e— open the highlighted workspace inWS_EDITOR/EDITORx— start the remove confirmation prompt- Esc — cancel prompts/help/toasts or exit the app
?— toggle the in-app help overlay
- Searches run asynchronously with debounce so fast typing never blocks the UI; a spinner stays visible until the first results land.
- The footer shows the
wsversion (left), resolvedWS_PATH(center), and live result count (right). - Error/success toasts appear above the footer and auto-dismiss after
TOAST_MS, keeping feedback inline. - Pressing
xpromptsRemove '<name>'? [y/N]; onlyy/Y/Enter proceeds, andwsdeletes the directory (falling back to a safe rename<name>.deleted-<timestamp>if needed). - Pressing
elaunches the workspace via a non-blocking start and still increments recency metadata. - The help overlay mirrors the keybindings so you do not have to remember them.
_rtis an internal helper invoked by the shell function; you normally never call it directly.- Store/DB errors are logged but non-fatal, so flows continue running (check
ws doctorfor details). - Commands like
ws foo,ws edit foo, andws rm fooresolve to the newestDATE-fooworkspace; pass literal names (e.g.,2025-11-08-foo) when you need an exact directory.
- “git not found” — install Git and re-run
ws doctor(gitis required for clone/worktree). - “no editor set (set WS_EDITOR or EDITOR)” — export
WS_EDITOR="code -w"(or your preferred command) before runningws edit.