A CLI tool that reduces cognitive load when navigating between development projects. It enforces an opinionated directory structure (~/src/<source>/<org>/<project>) and provides fast navigation.
brew tap dsaiztc/tap
brew install devgo install github.com/dsaiztc/dev@latestSee Development.
brew upgrade devgo install github.com/dsaiztc/dev@latestAdd to your ~/.zshrc or ~/.bashrc:
eval "$(dev init)"Clones a git repository into ~/src/<source>/<org>/<project>.
dev clone git@github.com:dsaiztc/dotfiles.git
# → clones to ~/src/github.com/dsaiztc/dotfilesSupports SSH, HTTPS, and ssh:// URLs. If the repo is already cloned, it prints the path and exits.
Creates a new project directory under ~/src/<source>/<org>/<name> and cd's into it.
dev new cool-idea # → creates ~/src/github.com/dsaiztc/cool-idea and cd's into itOn first use, prompts for default source and org, saving them to ~/.config/dev/config.json. Override defaults per-invocation with --source and --org:
dev new --source gitlab.com --org myteam specialPass --no-input to skip the prompt and use defaults (useful in scripts or AI agents):
dev new cool-idea --no-inputNavigates to a project directory.
dev cd kafka # fuzzy matches → cd ~/src/github.com/apache/kafka
dev cd # opens interactive fuzzy finder
dev cd - # returns to the previous directory (like `cd -`)Prints the full path to a repository to stdout. Useful for composing with other commands.
dev loc dotfiles # → /Users/dsaiztc/src/github.com/dsaiztc/dotfiles
code $(dev loc project) # open repo in VS Code
ls -la $(dev loc dev) # list files in repo
dev loc | pbcopy # interactive mode, copy path to clipboard
dev loc dotfiles --json # → {"repo":"github.com/dsaiztc/dotfiles","path":"/Users/..."}Displays a tree view of all repositories under ~/src/.
dev tree
# → ~/src/
# ├── github.com
# │ ├── dsaiztc
# │ │ ├── dev
# │ │ └── dotfiles
# │ └── apache
# │ └── kafka
# └── gitlab.com
# └── team
# └── serviceUseful for getting an overview of your repository organization at a glance. Pass --json to get a machine-readable array of repo paths:
dev tree --json | jq 'length' # count reposManages git worktrees. Run without a subcommand to navigate to a worktree via the fuzzy finder (equivalent to dev wt cd).
Note:
wktis a deprecated alias forwt. It still works but prints a deprecation warning to stderr and will be removed in a future release.
Creates a new git worktree with a new branch and cd's into it. Worktrees are stored under ~/src__worktrees/<source>/<org>/<repo>__<branch>, separate from ~/src/ so dev cd is unaffected.
dev wt new feature-login
# → creates worktree at ~/src__worktrees/github.com/dsaiztc/dev__feature-login
dev wt new fix/bug-123
# → creates worktree at ~/src__worktrees/github.com/dsaiztc/dev__fix--bug-123Branch name slashes are replaced with -- in the directory name to keep paths flat.
Opens a fuzzy finder to navigate between worktrees of the current repository.
dev wt cd # fuzzy finder with branch names, main worktree annotated with (main)
dev wt # same thing — cd is the default subcommand
dev wt cd - # returns to the previous directory (like `cd -`)Removes a worktree, its local branch, and its remote branch (best-effort).
dev wt rm # from a linked worktree: removes the current one, cd's to main
dev wt rm feature-x # from the main worktree: removes the named worktree
dev wt rm # from the main worktree: opens fuzzy finder to pick one
dev wt rm feature-x -y # skip confirmation prompt (for scripts/agents)Prompts for confirmation unless --yes / -y (or --force) is passed. When no TTY is available and --yes is absent, the command errors rather than silently cancelling. The main worktree is protected and cannot be removed.
The worktree root directory defaults to ~/src__worktrees/ and can be customized in ~/.config/dev/config.json:
{
"worktree_root": "~/my_worktrees"
}Prints the shell wrapper function. The wrapper intercepts cd, clone, new, and wt (and the deprecated wkt) subcommands to eval their stdout, enabling actual directory changes in the parent shell.
These flags are available on every command (designed to keep dev scriptable and agent-friendly):
| Flag | Description |
|---|---|
--version |
Print the version and exit. |
--no-input |
Disable all interactive prompts. Commands fall back to defaults or error instead of blocking — useful in scripts, CI, and AI agents. |
--no-color |
Disable ANSI color output. Also honored via the NO_COLOR environment variable. |
-h, --help |
Show help for any command. |
- Go 1.25+
Clone the repo into the expected directory structure and install dependencies:
git clone git@github.com:dsaiztc/dev.git ~/src/github.com/dsaiztc/dev
cd ~/src/github.com/dsaiztc/dev
go mod downloadgo test ./...Install the binary from local source to $GOPATH/bin:
go install .After that, dev in your shell reflects the local code. Re-run go install . after each change to rebuild.
To run a command without installing:
go run . <command> [args] # e.g. go run . clone git@github.com:foo/bar.gitNote: commands that depend on the shell wrapper (dev cd, dev clone) need the full installed binary to work correctly via eval "$(dev init)".
- Fork the repo and create a branch
- Make changes and add tests where appropriate
- Run
go test ./...to verify everything passes - Open a pull request
| Directory | Purpose |
|---|---|
cmd/ |
Cobra command implementations (one file per command) |
internal/config/ |
Config loading/saving (~/.config/dev/config.json) |
internal/fuzzy/ |
Bubbletea interactive fuzzy finder TUI |
internal/repos/ |
Repository discovery and fuzzy matching |
internal/repourl/ |
Git URL parsing (SSH, HTTPS, ssh://) |
internal/shell/ |
Shell wrapper function generation |
internal/worktree/ |
Git worktree detection, creation, and removal |
- Cobra — CLI framework
- Bubbletea / Bubbles / Lipgloss — TUI and terminal styling
- sahilm/fuzzy — fuzzy string matching
Commands that need to affect the parent shell (cd, clone, new, wt) print shell commands to stdout. The wrapper function installed via eval "$(dev init)" captures and evals that output. All user-facing messages go to stderr to keep stdout clean for eval. Help and version flags (--help, -h, --version) bypass the eval path entirely.
A GitHub Actions workflow (.github/workflows/release.yml) triggers on every v* tag push. It runs GoReleaser (.goreleaser.yml), which cross-compiles binaries for macOS and Linux (amd64 + arm64), creates a GitHub Release with tar.gz archives, and pushes an updated Homebrew formula to dsaiztc/homebrew-tap. The version is injected at build time via -ldflags.
Interactive commands (fuzzy finder, config prompts) can't read from stdin because it's captured by the $() subshell. They open /dev/tty directly instead. Use --no-input to disable all prompts, or --yes on destructive commands, to run non-interactively when no TTY is available (e.g. in CI or AI agents).
Releases are automated with GoReleaser and GitHub Actions. Pushing a tag triggers a build that cross-compiles binaries, creates a GitHub Release, and updates the Homebrew formula.
git tag v0.2.0
git push origin v0.2.0- Create a
dsaiztc/homebrew-taprepo on GitHub - Create a fine-grained Personal Access Token with Contents read/write access to
homebrew-tap - Add the token as a repository secret named
HOMEBREW_TAP_TOKENindsaiztc/dev
All repositories are organized under ~/src/:
~/src/
github.com/
dsaiztc/
dev/
dotfiles/
apache/
kafka/
gitlab.com/
team/
service/