doma v0.5.0

Put your directories on the cutting board
doma Logo

Directory tag manager — put your directories on the cutting board.

Installation • Shell integration • Pipelines • AI agents • Contributing • Releases


doma is a workbench for sets of project directories. Label your projects once (crystal, work/proj-a, bookmark), then operate on a whole label at a time: see which repos have uncommitted work, run a command in every one of them, and get per-directory results back.

doma add ~/Projects/my-app -t crystal -t web
doma status crystal            # branch, ahead/behind, dirty count for every crystal repo
doma run crystal -- shards build   # build them all; failures re-listed at the end
doma run crystal --parallel --json -- crystal spec | jq '.[] | select(.exit_code != 0)'

It is not a frecency jumper: doma never adds a directory on its own, and a directory only joins a set when you label it. Jumping is still there (doma cd crystal, or bare doma for the fuzzy finder), and so is doma mark for short-lived bookmarks.

Features

Tagging

  • Multiple tags per directory; tags are reusable across paths
  • Auto-tag from basename, Git remote, and project type (--auto-tag, --git-tag, --project-tag: shard.yml → crystal, Cargo.toml → rust, …)
  • Bulk onboarding: doma add -r ~/Projects --project-tag registers every git repo under a tree
  • Dynamic sets next to tags: @failed (what the last run failed on) and @dirty (repos with uncommitted changes) — doma run @failed -- make test retries only the failures
  • One selector for every set command: repeat -t to AND, -x to exclude (run crystal -x archived -- …, status -t work -t api, list -x 'tmp*')
  • Glob filter on tags (list -t 'work/*', run 'work/*' -- cmd) — shell-style: * matches within one segment, ** crosses /, ? is a single non-/ character
  • Hierarchical tag display (tags --tree)
  • TTL on tags: --ttl 30m | 1h | 7d | 2w, --tmp for the 7-day default, mark for the cwd + 7-day shortcut
  • Stable 7-char short_id per directory — survives renames; usable via rm <id> and trash restore <id>

Navigation & operations

  • tui — full-screen fuzzy finder over your registered directories, and the default when you run doma with no subcommand. Fuzzy match on paths with match highlighting, narrow with a small query syntax (tag:crystal, -tag:archived, id:0d, path:src), register a directory with Ctrl-A or copy a path with Ctrl-Y without leaving it. Enter prints the path so the shell wrapper cds into it
  • list --pick resolves to a single path (Crystal-native picker, no fzf dependency); the doma cd <tag> shell wrapper from doma setup install builds on it
  • run <tag> -- <cmd> to execute a command in every tagged directory; --parallel (with --jobs N, default CPU count), --fail-fast, and --dry-run available. Each child sees DOMA_PATH / DOMA_NAME / DOMA_ID / DOMA_TAGS, and the sweep ends with an ok · failed · skipped tally that re-lists failures. --json captures each directory's stdout/stderr/exit code into its own row — in stable input order even under --parallel
  • status <tag> for a one-glance git dashboard across a tagged set — branch, ahead/behind, dirty count; --dirty to show only repos with uncommitted work, --json for scripting
  • move to follow a path that moved on disk; tags carry over
  • rename to merge or relabel tags
  • Recency tracking — most-used directories surface first in pickers and list --by recent
  • Substring search across path / basename / tag (doma list <query>)
  • Single-entry detail view (doma info defaults to cwd) — tags, TTLs, last-used, exists check
  • Dead-path detection: list --check, prune --gone; expiry purge: prune --expired

Pipelines & scripting

  • list -t TAG --paths — newline-separated paths for while read / xargs
  • list -t TAG -0 — NUL-separated for xargs -0, safe for paths with spaces
  • list --json, tags --json, stats --json, status --json, run --json, info --json, export --json|--yaml
  • Distinct exit codes per failure class (2 validation, 3 not found, 4 conflict, 5 config, 6 import) so callers branch on status rather than error text
  • "Did you mean ..." hints (Levenshtein) for typos
  • Output stays color-free when piped; SIGPIPE-safe

Storage & safety

  • SQLite-backed (WAL, foreign keys, busy_timeout) — handles concurrent doma calls across multiple shells
  • Path canonicalization: ~, symlinks, and trailing slashes all collapse to one stable key
  • Transactional writes; atomic snapshot import/export
  • Schema migrations via PRAGMA user_version — old DBs upgrade transparently
  • Strict tag validation; sanitized auto-tags so a .dotfiles repo doesn't break --auto-tag

Distribution & integration

  • Single static binary (musl + sqlite-static); no runtime dependencies
  • Packaged for Homebrew, AUR, Snap, .deb, .rpm, .apk
  • Multi-arch container image at ghcr.io/hahwul/doma
  • CycloneDX SBOM published with every release
  • Shell integration installer (doma setup install) for bash, zsh, fish
  • AI skill for Claude / Cursor / etc. — see skills/doma/SKILL.md

Installation

Homebrew

brew tap hahwul/doma
brew install doma

From source

git clone https://github.com/hahwul/doma.git
cd doma
shards install
shards build --release --no-debug --production

Pre-built binaries

Static Linux and macOS binaries are attached to every release. .deb / .rpm / .apk / .snap packages are published alongside.

Container

docker pull ghcr.io/hahwul/doma:latest
docker run --rm -it -v "$HOME/.config/doma:/root/.config/doma" \
  ghcr.io/hahwul/doma list

Shell integration

doma cd lives in a shell function rather than the binary — a child process can't change its parent shell's working directory. The function calls doma list -t <tag> --pick (the binary's single-pick primitive) and runs cd on the result. One-shot install:

doma setup install                    # auto-detects $SHELL, appends to your rc
exec $SHELL                           # or `source ~/.zshrc`
doma cd crystal                       # interactive picker, then cd
doma                                  # bare doma opens the fuzzy finder, then cd

The wrapper also makes bare doma and doma tui cd into whatever you pick in the finder.

If you'd rather wire it manually:

eval "$(doma setup init zsh)"         # bash / zsh
doma setup init fish | source         # fish

Without the wrapper, the equivalent inline form works everywhere:

cd "$(doma list -t crystal --pick)"

Pipelines

doma is designed to compose with the rest of your shell. A few common shapes:

# Update CI files across every Crystal project
doma list -t crystal --paths | while read -r d; do
  (cd "$d" && sed -i 's/crystal: 1.20/crystal: 1.21/' .github/workflows/*.yml)
done

# Repos under work/ with uncommitted changes
doma status 'work/*' --dirty

# Feed paths to tools that want them as arguments (paths-with-spaces safe)
doma list -t 'work/*' -0 | xargs -0 rg -l TODO

# Structured access via jq
doma list --json | jq -r '.[] | "\(.short_id)\t\(.path)\t\(.tags|join(","))"'

# Which tagged repos failed to build? (per-directory rows, parallel-safe)
doma run crystal --parallel --json -- shards build \
  | jq -r '.[] | select(.exit_code != 0) | .path'

# Ad-hoc bookmarks during a code review session
doma mark auth-review
# ... cd around ...
doma list -t auth-review --paths

AI agents

doma ships a Claude Code skill that teaches an agent when to query the database for a path list ("update CI for all my Crystal projects") and when to register or bookmark a directory ("track this", "remember this for later"). It also documents the pieces that make doma safe to drive unattended: the exit-code table, run --json for attributable per-directory results, and the guards that make destructive commands refuse to run without a TTY.

Install via Vercel Skills:

npx skills add hahwul/doma

Or copy the file by hand into your agent's skill directory:

cp -r skills/doma ~/.claude/skills/
# or symlink so updates land automatically:
ln -s "$(pwd)/skills/doma" ~/.claude/skills/doma

Contributing

doma is open-source and PRs are welcome. Please check CONTRIBUTING.md before sending a patch.

Why "doma"?

Doma (도마) is the Korean word for cutting board — the workbench where ingredients are gathered, grouped, and chopped before going into the pan. doma aims to be the same kind of workbench for your directories: pull the ones you care about onto a single board, group them by category (crystal, work/proj-a, bookmark), and run bulk operations across the piles as if they were mise en place.

Repository

doma

Owner
Statistic
  • 4
  • 0
  • 0
  • 0
  • 3
  • about 1 hour ago
  • April 25, 2026
License

MIT License

Links
Synced at

Mon, 09 Nov 2026 15:18:31 GMT

Languages