# aiToolManager — Spec (grilled, all decisions settled) CLI + TUI tool for managing AI coding-agent capabilities (skills, MCP servers, extensions, commands, hooks) across multiple harnesses, differentiating global (user) vs project scope. ## Runtime / packaging - Node/TS. Executable/binary name: `aiToolManager`. - Package name suggestion: `ai-tool-manager` (npm), bin entry `aiToolManager`. - CLI framework: commander (or cac) for subcommands/flags. Keep deps minimal. - Interactive mode: running bare `aiToolManager` with no args drops into a TUI (category menu -> item list -> action). Use a lightweight prompt lib (e.g. `@clack/prompts` or `enquirer`) — do NOT pull in a heavy full-screen TUI framework (no ink/blessed) unless genuinely required; this is a menu- driven settings tool, not a dashboard. - Every mutating action available as a scriptable flag-driven subcommand too (TUI is a convenience layer over the same core functions, never the only path). ## Harness detection - Auto-detect current harness + default scope from cwd/env for command defaults: - presence of `.omp/` in cwd (or ancestors up to git root) -> omp, project scope - presence of `.claude/` in cwd -> claude-code, project scope - presence of `.prime/agent/` in cwd -> prime-agent, project scope - env vars: `OMP_PROFILE`/`PI_PROFILE` implies omp; no reliable env signal for the other two — cwd markers are primary signal - no markers found -> no default scope; require explicit `--harness`/`--scope` - Every command accepts explicit `--harness ` and `--scope ` to override detection. ## Categories (screens) Five: **Skills**, **MCP Servers**, **Extensions**, **Commands**, **Hooks**. Each its own screen in the TUI, own subcommand group in the CLI (`aiToolManager skill ...`, `aiToolManager mcp ...`, `aiToolManager extension ...`, `aiToolManager command ...`, `aiToolManager hook ...`). Hooks screen is **Claude Code only** (OMP/Prime Agent hook-style modules are mechanically Extensions internally — surface them there instead; do not build a redundant Hooks concept for those two). Extensions screen for Claude Code is repurposed to Plugin-level toggle (see below) — label it clearly as plugin-level, not extension-level, in any UI text for that harness. ## Per-harness native paths (ground truth, do not invent alternatives) ### Skills | Harness | Global | Project | |---|---|---| | OMP | `~/.omp/agent/skills//SKILL.md` (non-recursive) | `/.omp/skills//SKILL.md` (non-recursive, cwd-only, no ancestor walk) | | OMP (agents provider, also native) | `~/.agents/skills//SKILL.md` | `/.agents/skills//SKILL.md` (walks ancestors to git root) | | Claude Code | `~/.claude/skills//SKILL.md` | `/.claude/skills//SKILL.md` | | Prime Agent | `~/.prime/agent/skills//SKILL.md` or `~/.agents/skills//SKILL.md` (recursive `SKILL.md` discovery; root `.md` files also count in `.prime/agent/skills` only) | `.prime/agent/skills//SKILL.md` or `.agents/skills//SKILL.md` (walks ancestors to git root) | **Canonical store** (real files, source of truth): `~/.agents/skills//` (global) and `/.agents/skills//` (project). Mirror every skill into every harness's own native dir too, **as a symlink** pointing back at the canonical dir, both scopes: - `~/.omp/agent/skills/` -> symlink -> `~/.agents/skills/` - `~/.claude/skills/` -> symlink -> `~/.agents/skills/` - `~/.prime/agent/skills/` -> symlink -> `~/.agents/skills/` - same pattern at project scope with `.agents/skills` as target Rationale: OMP+Prime Agent already read `.agents/skills` natively (belt), but mirror into native dirs too (suspenders) so disabling a harness's agents-provider toggle doesn't silently drop skills. ### Commands Same shape/logic as skills. Canonical: `~/.agents/commands/.md` (global), `/.agents/commands/.md` (project). Mirror-symlink into: - OMP: `~/.omp/agent/commands/*.md` (global), `/.omp/commands/*.md` (project) - Claude Code: `~/.claude/commands/**/*.md` (global, recursive+namespaced), `/.claude/commands/**/*.md` (project) - Prime Agent: equivalent primitive is **prompt templates**: `~/.prime/agent/prompts/*.md` (global), `.prime/agent/prompts/*.md` (project, non-recursive) ### Extensions File-path-present/absent model for OMP + Prime Agent: | Harness | Global | Project | |---|---|---| | OMP | `~/.omp/agent/extensions/` (one level: `*.ts`/`*.js` or `*/index.ts`) | `/.omp/extensions/` (cwd-only, no ancestor walk, same one-level rule) | | Prime Agent | `~/.prime/agent/extensions/*.ts` or `*/index.ts` | `.prime/agent/extensions/*.ts` or `*/index.ts` | No canonical-store mirroring for extensions (TS module code is harness- specific enough — e.g. OMP's `ExtensionAPI` vs Prime Agent's — that a shared file rarely works verbatim). Track install/enable state per harness independently; tool manages presence/absence + `disabledExtensions` (OMP: `extension-module:` ids) where a disable-list exists. Claude Code: **no bare extension primitive**. Repurpose this screen to list installed Plugins and toggle: - global: `enabledPlugins` key in `~/.claude/settings.json` - project: per-project `false` override in `.claude/settings.json` (cannot force-enable a plugin the global list has off — only force-disable) ### Hooks (Claude Code only screen) Declarative JSON `hooks` block inside `settings.json`: - global: `~/.claude/settings.json` - project (shared/committed): `.claude/settings.json` - project (personal/uncommitted): `.claude/settings.local.json` No native per-hook disable field. Tool's own shadow-copy sidecar (see Metadata/state below) stores removed-but-remembered matcher blocks so "disable" -> "enable" round-trips without the user retyping JSON. ### MCP servers No cross-harness canonical store — schemas differ, symlinking a config file is impossible. Read/write each harness's native file directly, in-memory per invocation, always targeting one explicit harness+scope (never fan-out). | Harness | Global file | Project file | Disable mechanism | |---|---|---|---| | OMP | `~/.omp/agent/mcp.json` (or profile dir) | `/.omp/mcp.json` | native: top-level `disabledServers`/`enabledServers` arrays in the user file; `enabled: false` per-entry also honored | | Claude Code | `~/.claude.json` (user+local scope, local keyed by project path) | `/.mcp.json` | **no native disable field** (confirmed: GH feature request closed not-planned) — only remove/re-add. Use shadow-copy sidecar. | | Prime Agent | `mcpServers` key in `~/.prime/agent/settings.json` | `mcpServers` key in `.prime/agent/settings.json` | native: `enabled: false` field per user-declared entry. **Built-in credential-gated integrations (Linear/Notion) are read-only/status-only in this tool — do not attempt to manage them; enabling means running `/mcp login` inside Prime Agent itself, out of scope.** | OMP server shape: `{ type: "stdio"|"http"|"sse", command/url, args, env, headers, enabled, ... }`. Prime Agent shape: `{ type: "http", url, oauth, bearerTokenEnvVar, headers, enabled }` (stdio unsupported there — if a user tries to target an OMP-style stdio server at Prime Agent, tool must reject with a clear error, not write a broken entry). Claude Code shape: treat as compatible with the `mcpServers` `{command,args,env}`/`{type,url}` shape used by OMP/Claude Code interop (OMP explicitly translates Claude Code's files). ## Enable/disable mechanism summary - Prefer native field/list wherever the harness has one (OMP `disabledServers` /`enabledServers`/`enabled`, Prime Agent `enabled: false`). - Shadow-copy sidecar **only** where no native primitive exists: Claude Code MCP servers, Claude Code hooks. Store removed defs in a small per-harness- file sidecar (e.g. `.aitoolmanager-shadow.json` next to the config it shadows), never a general cross-harness registry. ## Skill/tool metadata (category, description, caveats) - Per-item sidecar file living next to the tool's own definition, travels automatically with promote-copy: `.aitoolmanager.json` next to `SKILL.md` (or next to the extension entry / command file / MCP server def in the canonical store where one exists; MCP servers have no canonical store, so their metadata sidecar lives next to `aiToolManager`'s own per-harness state, keyed by server name+harness+scope). - Fields: `{ category?: string, humanDescription?: string, related?: string[] }` - `category`: derived from source repo's directory structure at import time when the source has one (e.g. importing Matt Pocock's skills repo: `engineering`/`productivity`/`misc`/`in-progress`; `deprecated` excluded from import entirely). Default `"uncategorized"` when the source has no grouping. Always manually reassignable after import. - `humanDescription`: separate from the SKILL.md frontmatter `description` (which is model-facing/trigger-oriented). Seed from a category `README.md` bullet describing the tool when the source provides one (Matt Pocock's repo does, per-category `README.md` with `**[name](...)** — description` bullets). Fall back to the frontmatter `description` when no such curated text exists (custom skills, MCP servers, extensions). - `related`: best-effort seed by scanning a source README's prose for other skill names it explicitly names (e.g. `implement`'s README line mentions `tdd` and `code-review` — seed `related: ["tdd", "code-review"]` for `implement`). Blank array + fully manual editing when no such source doc exists. Never treated as authoritative — always user-editable, shown in listings as "Related / overlaps: ..." not "Conflicts:". ## Local -> global promotion 1. User selects a project-scope item, requests promote. 2. Tool shows a confirmation prompt (diff-style: what will move, from where, to where) — no silent action. 3. On confirm: copy canonical project-scope item (and its `.aitoolmanager.json` sidecar) into the canonical global-scope location. 4. Replace the project-scope canonical item with a symlink pointing at the new global-scope copy (never leave a disconnected duplicate; never silently delete without a pointer left behind). 5. Re-run harness-mirror symlinking for the affected item at both scopes so every harness dir's symlink now resolves through to the global copy. ## Conflict display When a same-named item exists at both project and global canonical scope with genuinely different content (not just harness-mirror plumbing), list UI shows both entries side by side, each labeled with its scope, and an annotation on the project entry: "wins here (project overrides global)" — this matches every covered harness's actual real precedence behavior (project beats user/global on same-name collision in OMP, Claude Code, and Prime Agent alike). Never silently hide one; never invent a different precedence rule. ## Non-goals / explicitly out of scope for v1 - Fan-out (write to every harness at once) for MCP servers — always explicit single harness+scope target. - Managing Prime Agent's OAuth-gated built-in MCP integrations (Linear, Notion) beyond showing connection status read-only. - A general cross-harness MCP config registry/source-of-truth file — none exists, read/write native files directly instead. - Triggering any harness's own OAuth/login flows from this tool. - Any harness beyond OMP, Claude Code, Prime Agent (extensible later, not required now). ## Acceptance criteria (what "done" means) - `aiToolManager` installs (npm link or local build), runs with `--help` listing all five category subcommand groups. - Bare `aiToolManager` (no args) launches an interactive menu covering all five categories, scope switching, harness switching. - `aiToolManager skill list [--harness x] [--scope global|project]` shows discovered skills grouped by category, each with name + humanDescription + related list + scope + which harness dirs mirror it. - `aiToolManager skill enable/disable --harness --scope ` actually creates/removes the correct symlink(s) for that harness+scope on disk — verify by inspecting the filesystem after the command, not just trusting exit code 0. - `aiToolManager skill promote ` (project -> global) performs the full copy + confirm + relink sequence from the spec above; verify by inspecting both canonical dirs and the leftover project symlink afterward. - `aiToolManager mcp add/enable/disable --harness --scope ` correctly round-trips through each harness's real native mechanism (`disabledServers` for OMP, `enabled:false` for Prime Agent, shadow-copy sidecar for Claude Code) — verify by inspecting the actual JSON file content after the command. - Real end-to-end smoke test: import the already-cloned `~/.omp/agent/skill-sources/mattpocock-skills` repo via `aiToolManager skill import `, confirm categories/descriptions/ related lists populate correctly for at least `tdd`, `implement`, and `grill-me`/`grilling`, confirm mirrored symlinks appear correctly for OMP at both scopes (Claude Code/Prime Agent dirs may not exist on this machine — code must handle a harness whose base dir doesn't exist yet by creating it, not erroring). - Test suite (vitest or node:test) covering: path resolution per harness/scope, symlink mirroring, promote/relink sequence, MCP native- mechanism read/write per harness, shadow-copy sidecar round-trip, metadata sidecar read/write, conflict-display precedence logic. No project-wide slow/E2E-only suite required to pass on every commit — unit-level coverage of the logic above is the bar. - No stubs/TODOs/fake fallbacks in delivered code. Every acceptance item above must be independently verifiable by running the actual command.