---
name: add-tool
description: Add a new tool to the dotfiles repository. Use when asked to add a new CLI tool, application, or package as a managed dotfiles tool.
---

## Context

- Existing tools: !`ls "${DOTFILES}/tools" | grep -v ^@`
- Template: `tools/@new/` — reference for file structure and patterns

## Your task

Add a new tool to this dotfiles repository. Follow these steps:

### 1. Research the tool

Before writing any files:

- **How is it installed?** Check its README/docs for the recommended installer. Common options: `brew install`, `npm install -g`, `bun install -g`, `uv tool install`, `cargo install`, or a custom install script.
- **What does `{command} --version` output?** Run it (or check the docs) to know the exact format — you'll need this to write `parse_version`.
- **Are there zsh completions?** Check the docs or the brew caveats after install. If completions require explicit shell setup (e.g. `eval "$(tool completion zsh)"`), note it — you'll need `shell.zsh`.
- **Does it have dotfiles config to manage?** If it writes config to `~/.config/{tool}/` or similar, you'll need `link.bash` and a `config/` directory.

### 2. Classify the tool

**Simple tool** (no dotfiles config to symlink): needs only the 4 core files.
Examples: `eza`, `sd`, `logcli`

**Tool with config** (has config files to symlink): also needs `link.bash` and a `config/` directory.
Examples: `claude`, `neovim`, `git`

### 3. Create `tools/{tool}/utils.bash`

```bash
#!/usr/bin/env bash
set -euo pipefail

export TOOL_LOWER="{tool}"
export TOOL_UPPER="{Tool}"
export TOOL_COMMAND="{tool}"
export TOOL_PACKAGE="{package-name}"
export TOOL_EMOJI="🔧"
export TOOL_CONFIG_DIR="${HOME}/.config/${TOOL_LOWER}"  # omit if no config

parse_version() {
  local raw_version="${1}"
  # Adapt to match the actual output of the version command (arg 6 in install/update):
  printf "${raw_version}"
}
```

Key variables:
- `TOOL_LOWER`: lowercase name used in paths and log messages
- `TOOL_UPPER`: display name (often same as `TOOL_LOWER`, but e.g. `"Claude Code"`)
- `TOOL_COMMAND`: the binary name (`which` resolves this)
- `TOOL_PACKAGE`: the package name passed to the installer
- `TOOL_EMOJI`: used in log output
- `TOOL_CONFIG_DIR`: only needed for tools with config; omit the export if unused

**`parse_version` by installer** — receives the raw output of the version command (arg 6) and must return a clean semver string:

| Installer | Version command (arg 6) | Typical raw output | `parse_version` logic |
|---|---|---|---|
| `brew` | `brew list --version {pkg}` | `sd 1.0.0` | strip `"sd "` prefix |
| `npm` / `bun` | `{command} --version` | `1.10.2` | print as-is |
| `npm` (some) | `{command} --version` | `1.0.0 (Claude Code)` | strip suffix |
| `uv tool` | `{pkg} --version` | `ty, version 0.0.1` | strip `"ty, version "` prefix |
| `cargo` | `{command} --version` | `sd 1.0.0` | strip `"sd "` prefix |

### 4. Create `tools/{tool}/install.bash`

Structure is always the same — only the install command (arg 5) and version command (arg 6) change by installer:

**brew:**
```bash
#!/usr/bin/env bash
set -euo pipefail

source "${DOTFILES}/features/install/utils.bash"
source "${DOTFILES}/tools/{tool}/utils.bash" # source last to avoid env var overrides

install_and_symlink \
  "${TOOL_LOWER}" \
  "${TOOL_UPPER}" \
  "${TOOL_COMMAND}" \
  "${TOOL_EMOJI}" \
  "brew install --formula ${TOOL_PACKAGE}" \
  "brew list --version ${TOOL_PACKAGE}" \
  "parse_version"
```

**npm:**
```bash
  "npm install --global ${TOOL_PACKAGE}@latest" \
  "${TOOL_COMMAND} --version" \
```

**bun:**
```bash
  "bun install --global ${TOOL_PACKAGE}" \
  "${TOOL_COMMAND} --version" \
```

**uv:**
```bash
  "uv tool install ${TOOL_PACKAGE}" \
  "${TOOL_PACKAGE} --version" \
```

**cargo:**
```bash
  "cargo install ${TOOL_PACKAGE}" \
  "${TOOL_COMMAND} --version" \
```

For **tools with config**, add the symlink script path as an 8th argument:

```bash
  "parse_version" \
  "${DOTFILES}/tools/${TOOL_LOWER}/link.bash"
```

### 5. Create `tools/{tool}/update.bash`

Same pattern — update command varies by installer, 8th arg is always the install script fallback:

**brew:**
```bash
#!/usr/bin/env bash
set -euo pipefail

source "${DOTFILES}/features/update/utils.bash"
source "${DOTFILES}/tools/{tool}/utils.bash" # source last to avoid env var overrides

update_and_symlink \
  "${TOOL_LOWER}" \
  "${TOOL_UPPER}" \
  "${TOOL_COMMAND}" \
  "${TOOL_EMOJI}" \
  "brew upgrade --formula ${TOOL_PACKAGE}" \
  "brew list --version ${TOOL_PACKAGE}" \
  "parse_version" \
  "${DOTFILES}/tools/${TOOL_LOWER}/install.bash"
```

Update commands by installer: `brew upgrade --formula {pkg}` / `npm install --global {pkg}@latest` / `bun install --global {pkg}` / `uv tool upgrade {pkg}` / `cargo install {pkg}`

For **tools with config**, add the symlink script path as a 9th argument:

```bash
  "${DOTFILES}/tools/${TOOL_LOWER}/install.bash" \
  "${DOTFILES}/tools/${TOOL_LOWER}/link.bash"
```

### 6. Create `tools/{tool}/uninstall.bash` (non-brew tools only)

**Brew-managed tools**: skip this file. Uninstall by moving the tool folder to
`tools/@archive` (or deleting it) and running `u` — the Brewfile entry drops
out of `Brewfile.generated` automatically.

**Non-brew tools** (npm/bun/uv/cargo): create a plain script with only the
cleanup steps brew can't handle — no wrapper needed:

```bash
#!/usr/bin/env bash
set -euo pipefail

source "${DOTFILES}/tools/bash/utils.bash"

info "🔧 Uninstalling {tool}"
{uninstall command}
```

Uninstall commands: `npm uninstall --global {pkg}` / `bun uninstall --global {pkg}` / `uv tool uninstall {pkg}` / `cargo uninstall {pkg}`

### 7. Create `tools/{tool}/shell.zsh` (only if needed)

Only create this file if the tool requires explicit shell setup: environment variables, aliases, or completions not auto-installed by the package manager.

Brew auto-installs completions to `/opt/homebrew/share/zsh/site-functions` — no `shell.zsh` needed for those. Only add completion setup if the tool's docs require something like `eval "$(tool completion zsh)"` or `source <(tool completions zsh)`.

```zsh
########################
# ENVIRONMENT VARIABLES #
########################

# export SOME_VAR="value"

###########
# ALIASES #
###########

# alias x="tool --flag"

###############
# COMPLETIONS #
###############

# eval "$(tool completion zsh)"
```

### 8. For tools with config: create `link.bash`

`tools/{tool}/link.bash`:

```bash
#!/usr/bin/env bash
set -euo pipefail

source "${DOTFILES}/tools/{tool}/utils.bash"
source "${DOTFILES}/tools/bash/utils.bash"

symlink "${DOTFILES}/tools/{tool}/config/settings.json" "${TOOL_CONFIG_DIR}/settings.json"
```

### 9. Verify

```bash
shellcheck tools/{tool}/*.bash
```

Run the install script to confirm it works end-to-end:

```bash
DOTFILES="$HOME/Repos/ooloth/dotfiles" bash tools/{tool}/install.bash
```

## Notes

- **Auto-discovery**: `features/install/tools.bash` and `features/update/tools.bash` use `find` to discover all `install.bash` / `update.bash` files under `tools/` automatically. No manual registration is needed.
- **Shell manifest**: `shell.zsh` is sourced automatically in every new shell via the generated manifest at `$DOTFILES/.cache/shell-files-manifest.zsh`. Regenerate it with the `symlinks` alias after adding a new tool.
