# AI Tools A1 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** AgentHub knows ~38 AI coding CLIs from a JSON catalog, detects which are installed, lets the user enable/disable them and pick a default, and runs a lane on any of them. **Architecture:** One server module `server/lib/ai-tools.js` owns catalog load/validation, PATH detection and the enable/default state file; `server/lib/shell-path.js` hydrates `process.env.PATH` from the login shell. `pty-run.js` keeps its Claude/Codex builders (`launcher`) and gains one generic catalog builder. The client reads everything through one hook, `useAiTools`. **Tech Stack:** Node/Express + better-sqlite3 (server, `node:test`), React + Vite + vitest (client), tmux. **Spec:** `docs/superpowers/specs/2026-09-28-ai-tools-a1-design.md` ## Global Constraints - Catalog id: `^[a-z0-9][a-z0-9-]{0,39}$`; `detect`/`requires` entries are bare names, no `/`. - `launcher` is accepted only in built-in files (`"claude"|"codex"`). - Args and env only ever come from the catalog; the prompt is ONE argv element or a tmux paste buffer; never a shell string. - Bypass args are emitted only when the run request carries `bypass: true`; never remembered. - State file: `/ai-tools.json` = `{ "disabled": [], "default": null }`. - PATH hydration: `$SHELL` (fallback `/bin/bash`) `-ilc`, delimiters, 5 s timeout, stdin ignored, after listen, off with `AGENTHUB_SHELL_PATH=0`. - `routerCapable` = `claude`, `codex` only. - Every new source file starts with the header ending `@author Nguyễn Ngọc Trí Vĩ `. - Server tests: `npm run test:server`; client: `cd client && NODE_ENV=test npx vitest run`; commits: `NODE_ENV=test git commit` (hook runs both suites, long timeout). - Never push without an explicit ask. --- ### Task 1: Catalog, detection, state (`server/lib/ai-tools.js`) **Files:** - Create: `server/lib/ai-tools.js`, `server/data/ai-tools/*.json` (38 files), `server/data/ai-tools/README.md` - Test: `server/__tests__/ai-tools.test.js` **Interfaces (Produces):** - `list(): Tool[]` — catalog order: `claude`, `codex`, then by label. - `get(id): Tool|null`, `has(id): boolean` - `detect(id): {installed, path, command}` (cached), `refresh()` clears the cache and re-reads the catalog. - `readState(): {disabled: string[], default: string|null}`, `isEnabled(id)`, `setEnabled(id, bool)`, `setDefault(id)` (throws `ENOTOOL` / `EBADDEFAULT`), `effectiveDefault(): string|null` - `view(): {tools: ToolView[], default}` — the GET body. - `ROUTER_CAPABLE = new Set(["claude","codex"])` - `__setCatalogDirs([...])` test seam (built-in dir, custom dir). - `Tool = {id,label,detect,requires,args,homepageUrl,faviconDomain,prompt,resume,bypass,bypassEnv,launcher,source}`. - [ ] Write failing tests: bad id skipped; `/` in detect skipped; non-array args skipped; `launcher` in custom file skipped; custom id replaces built-in; detection over a temp PATH with an executable and a non-executable file; `requires` missing ⇒ not installed; enable/default rules (default falls back to claude, then first installed enabled; disabling the default clears it; `EBADDEFAULT` for not-installed/disabled; `ENOTOOL` unknown). - [ ] Implement; write the 38 JSON files from the Orca table (spec §1 mapping); README attribution (Orca MIT). - [ ] `npm run test:server` green. ### Task 2: PATH hydration (`server/lib/shell-path.js`) **Interfaces:** `hydrateShellPath({spawn?, shell?, timeoutMs?}) : Promise` — merges new segments into `process.env.PATH`, returns whether it ran. - [ ] Tests with an injected `spawn` (execFile-like): delimiters with noisy rc output, timeout, spawn error, `AGENTHUB_SHELL_PATH=0`, no duplicate segments. - [ ] Implement with `child_process.execFile(shell, ["-ilc", "printf '__AGENTHUB_PATH__%s__AGENTHUB_PATH__' \"$PATH\""], {timeout:5000, stdio ignore stdin})`. ### Task 3: Routes `/api/ai-tools` **Files:** Create `server/routes/ai-tools.js`, `server/openapi-extra/ai-tools.js`; modify `server/index.js` (mount + hydrate after listen), `server/openapi-extra.js`, `server/routes/run.js` (binary probe via catalog). - `GET /` → `view()`; `POST /refresh` → hydrate, `refresh()`, `view()`; `PUT /:id {enabled?, default?:true}`. Mutations behind `sameOriginGuard`. - [ ] Route test (pattern: `git-credentials-route.test.js`): GET shape, PUT enable/disable, default errors, `/api/run/binary?tool=nope` 400, `?tool=antigravity` resolves through catalog. ### Task 4: Lanes and spawn on any tool **Files:** `server/lib/lanes.js`, `server/routes/lanes.js`, `server/lib/pty-run.js`, `server/lib/tmux.js` (`pasteText`), `server/lib/dashboard-runs.js`, `server/lib/router-apply.js`, `server/lib/ai-routers.js` (builtin label "Native"), `server/routes/run.js` (forward sandbox/approval/resumeLast/bypass), `bin/agenthub.js` (`lanes add --tool`). - `VALID_TOOLS` ⇒ `aiTools.has`; lane creation on a disabled tool ⇒ 400 `EDISABLED`. - `buildCatalogArgv(tool, {resumeLast, bypass, initialPrompt}, bin)` → `[bin, ...args, ...resume?, ...bypass?, ...prompt]`; `after-start` ⇒ `setTimeout(delayMs)` then `tmux.pasteText(id, prompt)` if the session still exists. - Every spawn env carries `PATH: process.env.PATH`; catalog tools start by `detect().path` (fallback first `detect` name); `bypassEnv` merged when `bypass`. - Codex: `bypass` ⇒ catalog codex bypass args, and `-s`/`-a` dropped. - Unknown tool ⇒ `ENOTOOL` at spawn. - router-apply: non-`routerCapable` accepts only native/builtin. - [ ] Tests: argv per prompt mode, resume, bypass on/off, Codex bypass, Claude ignores bypass, after-start paste scheduling (fake timers), PATH in env, EDISABLED/EBADTOOL/not-installed allowed, router-apply generic, dashboard-runs any id. ### Task 5: Client data layer **Files:** `client/src/lib/api.ts` (`aiTools.list/refresh/update`, `AiTool` type, `RunStartArgs.bypass`), `client/src/lib/types.ts` (`LaneTool = string`, drop `LANE_TOOL_LABEL`), create `client/src/hooks/useAiTools.ts` (`useAiTools(): {tools, default, byId, label(id), reload, loading}` — module-level shared cache + subscribers, refetch on focus), replace all `LANE_TOOL_LABEL` uses and the Workspace per-tool binary probes. ### Task 6: Settings → AI Tools section **Files:** Create `client/src/components/settings/AiToolsSettings.tsx`; modify `client/src/pages/Settings.tsx` (TOC + section), i18n `settings.json` en/vi; hide Claude/Codex tabs while disabled (settings hub nav). - [ ] Test `client/src/components/settings/__tests__/AiToolsSettings.test.tsx`: rows, toggle calls `update(id,{enabled})`, set default, refresh, available rows show only link. ### Task 7: Add lane + router row + run form **Files:** `AddLaneModal.tsx` (Default / Other… select, empty state link), `LaneCard.tsx` `LaneRouterRow` ("Native (