# aimgr

AI tool config manager. Installs, updates, and removes AI-tool (Claude Code,
Codex, Cursor) snippets and binaries across your projects — from a built-in
registry, with a per-user overlay for custom packages.

Unlike a dotfiles repo, `aimgr` is project-agnostic: each registry package
declares how to install the tool (shell command), which artifacts it puts
on the machine (for presence checks), which system prereqs it needs, and
what instructions to merge into `CLAUDE.md` (`AGENTS.md`, `.cursor/rules`). The binary ships with a seed registry and can
extend it per user — no registry path to bootstrap, no marketplace to browse.

## Install

```bash
cargo install --locked aimgr
```

Or from a checkout:

```bash
git clone https://github.com/luizhcrocha/aimgr
cd aimgr
mise run install
```

Requires Rust 1.85+.

## Quick start

```bash
# In the project you want to manage
cd ~/src/some-project
aimgr init                    # wizard → writes .aimgr/aimgr.toml, runs sync
aimgr use seek                # add to project, install binary, merge CLAUDE.md block
aimgr use -g claude-mem       # add to global config (applies across projects)
aimgr ls                      # what's in use here + global overlay
aimgr sync                    # reconcile intent (aimgr.toml) vs reality (state.toml)
aimgr up                      # update every installed package in parallel
aimgr unuse seek              # remove from this project
aimgr purge seek              # remove everywhere + run the package's uninstaller
```

Running `aimgr` with no arguments opens an interactive picker for the common
actions (add / remove / sync / update / browse).

## CLI surface

| Verb | What it does |
|---|---|
| `aimgr` | interactive picker |
| `aimgr init` | wizard: write `.aimgr/aimgr.toml`, run `sync` |
| `aimgr use <name>` | add to project manifest (`-g` = global, `--all` = every managed project), run installer + wire instructions |
| `aimgr unuse <name>` | inverse of `use` |
| `aimgr sync [--all] [--force]` | reconcile intent vs reality; `--all` covers every managed project; `--force` overrides drift prompts |
| `aimgr up [<name>]` | parallel update for every installed package (mise-style spinners); pass a name to update one |
| `aimgr purge <name>` | `unuse --all -g`, run the package's uninstaller, drop machine state |
| `aimgr status` / `aimgr ls` | table of packages — intent (project/global) + machine state |
| `aimgr registry` | list every package `aimgr` knows about (built-in + custom) |
| `aimgr install <name>` | scaffold a custom package at `~/.config/aimgr/custom/<name>.toml`, open `$EDITOR` |
| `aimgr uninstall <name>` | delete a custom package |
| `aimgr edit [<name>]` | `$EDITOR` on global config, or on a package (forks built-ins to custom first) |
| `aimgr implode` | remove all aimgr config + state (asks first) |

## Files & directories

```
~/.config/aimgr/config.toml           # global intent (packages + tools)
~/.config/aimgr/custom/<name>.toml    # user-authored registry packages
~/.local/state/aimgr/state.toml       # machine reality + trusted commands + managed-projects index

.aimgr/aimgr.toml                     # project intent (checked in)
.aimgr/state.toml                     # project reality (gitignored)
```

**Intent vs reality.** `aimgr.toml` declares what a project wants. `state.toml`
records what `aimgr` actually wrote. `sync` reconciles them — missing packages
get installed + wired, stale packages get unwired. Hand-edited instruction
blocks trigger a drift prompt (`[o]verwrite / [k]eep / [a]bort`) unless
`--force`.

**Built-in registry.** The binary embeds `plannotator`, `context7`,
`claude-mem`, `seek`, `fff-nvim`, `ccstatusline`, `temporal`,
`modern-web-guidance`, and `serena`. `aimgr registry` lists them; custom
packages at `~/.config/aimgr/custom/<name>.toml` shadow built-ins
transparently.

## Package schema

```toml
name        = "seek"
description = "Ranked, symbol-aware code search"
homepage    = "https://github.com/dualeai/seek"
category    = "tool"                # "tool" | "add-on"
tools       = ["claude", "codex", "cursor"]   # empty = any
scope       = "both"                # "global" | "project" | "both"

[[artifacts]]                        # declarative presence check; all must be present
kind = "cli"                         # "cli" | "claude-skill" | "claude-rule" | "path"
name = "seek"

# verify = "<shell>"                  # fallback probe when artifacts don't fit

[[prereq]]
name  = "universal-ctags"
check = "command -v ctags"

[prereq.install]
apt    = "universal-ctags"
pacman = "ctags"
brew   = "universal-ctags"

[[install]]
id      = "curl"
label   = "curl installer (recommended)"
command = "curl -sSfL https://example.com/install.sh | sh"

[[install]]
id      = "go"
label   = "go install"
command = "go install github.com/example/seek/cmd/seek@latest"

# update    = "<cmd>"    # defaults to the recorded install method's command
# uninstall = "<cmd>"

[[instructions]]
content = """
Prefer `seek 'query'` over grep/ripgrep — results are ranked and symbol-aware.
"""
# tools = ["claude"]    # optional: restrict this block to specific tools
```

## Scope semantics

- `scope = "global"` → `use` only touches machine state + global config (no
  project files written).
- `scope = "project"` → only project files get written; no machine install.
- `scope = "both"` (default) → installs once per machine (deduped), wires
  project files in every project that uses it.

## Trust gate

First time `aimgr` runs a shell installer for a package, it shows the full
command and prompts for explicit approval. Approved `(package, command)` pairs
are cached in `~/.local/state/aimgr/state.toml` under `[[trusted]]`. Prereq
install hints (`apt`, `pacman`, etc.) are printed, never run — the blast radius
is too big to automate.

## Drift handling

Each rendered instruction block is recorded pristine-text in `state.toml`. On
`sync`:

- If the file's block matches the pristine record → re-render silently.
- If it differs → diff prompt: overwrite / keep (skip this sync) / abort.
- `--force` always overwrites.
- Content outside `<!-- BEGIN aimgr: <name> -->` … `<!-- END aimgr: <name> -->`
  is never touched.

## Development

```bash
mise run check         # fmt + clippy -D warnings + tests
mise run fix           # clippy --fix + cargo sort + fmt
mise run preflight     # full pre-release gauntlet (+ cargo-sort, cargo-machete, doc warnings)
mise run release patch # cargo release patch (add --execute to publish)
```

Required cargo subcommands: `cargo install cargo-release cargo-sort cargo-machete`.

## License

MIT. See `LICENSE`.
