diff --git a/AGENTS.md b/AGENTS.md index 015025b..d281b46 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ Project-specific guidance for AI coding agents working in this codebase. ## Commits -- **Use conventional commits.** Format: `: ` (e.g., `feat: add --agents flag`, `fix: dry-run skip lock write`, `docs: update CLI reference`, `test: add list --help tests`, `refactor: extract help into per-command functions`, `chore: remove generated license file`). +- **Use conventional commits.** Format: `: ` (e.g., `feat: add --targets flag`, `fix: dry-run skip lock write`, `docs: update CLI reference`, `test: add list --help tests`, `refactor: extract help into per-command functions`, `chore: remove generated license file`). ## File Size Limits diff --git a/README.md b/README.md index 83fbc54..80a6704 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Requires Node.js 18+ (or Bun/Deno). -Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [37 more](docs/supported-agents.md). +Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [37 more](docs/supported-targets.md). @@ -32,8 +32,8 @@ dotai solves this with **canonical authoring**: write a single `RULES.md`, `PROMPT.md`, or `AGENT.md` and dotai transpiles it into every target agent's native format automatically. -- **Write once** — one canonical file fans out to all target agents -- **40+ agents** — Copilot, Claude Code, Cursor, Windsurf, Cline, and more +- **Write once** — one canonical file fans out to all targets +- **40+ targets** — Copilot, Claude Code, Cursor, Windsurf, Cline, and more - **Team sharing** — `npx dotai add owner/repo` gives every teammate the same context - **No lock-in** — canonical files are plain markdown with YAML frontmatter @@ -50,8 +50,8 @@ npx dotai # run without installing # Add context from a GitHub repo npx dotai add owner/repo -# Target specific coding agents -npx dotai add owner/repo --agents copilot,claude,cursor +# Limit to specific targets +npx dotai add owner/repo --targets copilot,claude,cursor # Install specific rules or skills npx dotai add owner/repo --rule code-style --skill db-migrate @@ -62,12 +62,12 @@ repo and transpiles them for your selected targets. ## What dotai installs -| Layer | Canonical file | Install behavior | -| ------- | -------------- | ------------------------------------ | -| Skills | `SKILL.md` | Passthrough (symlink or copy) | -| Rules | `RULES.md` | Transpile per target agent | -| Prompts | `PROMPT.md` | Transpile per supported target agent | -| Agents | `AGENT.md` | Transpile per supported target agent | +| Layer | Canonical file | Install behavior | +| ------- | -------------- | ------------------------------ | +| Skills | `SKILL.md` | Passthrough (symlink or copy) | +| Rules | `RULES.md` | Transpile per target | +| Prompts | `PROMPT.md` | Transpile per supported target | +| Agents | `AGENT.md` | Transpile per supported target | See [Source repo layout](docs/cli-reference.md#source-repo-layout) for where to place these files in your repo so dotai discovers them. @@ -118,18 +118,18 @@ npx dotai add ./my-local-context # local path -Skill installs target [41 agents](docs/supported-agents.md). **GitHub Copilot**, **Claude Code**, and **OpenCode** are actively tested; other agents follow the [Agent Skills specification](https://agentskills.io) but are not individually verified. +Skill installs target [41 targets](docs/supported-targets.md). **GitHub Copilot**, **Claude Code**, and **OpenCode** are actively tested; other targets follow the [Agent Skills specification](https://agentskills.io) but are not individually verified. ## Reference - [CLI Reference](docs/cli-reference.md) — all commands, flags, options, and authoring format -- [Supported Agents](docs/supported-agents.md) — full list of skill install targets +- [Supported Targets](docs/supported-targets.md) — full list of skill install targets ## Fork Lineage dotai started as a fork of [vercel-labs/skills](https://github.com/vercel-labs/skills) / [skills.sh](https://skills.sh). The inherited skills install pipeline remains first-class. dotai extends it with -transpilation of rules, prompts, and agent definitions to multiple target agents. +transpilation of rules, prompts, and agent definitions to multiple targets. ## Acknowledgements diff --git a/docs/cli-reference.md b/docs/cli-reference.md index d0c90f0..2b1d6bd 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -4,47 +4,47 @@ Full option tables, examples, and authoring format for `dotai`. For a quick over ## add command options -| Option | Description | -| --------------------------- | ---------------------------------------------------------------------------- | -| `-g, --global` | Install to user directory instead of project | -| `-t, --type ` | Filter by context type (`skill`, `rule`, `prompt`, `agent`; comma-separated) | -| `-s, --skill ` | Install specific skills by name (repeatable; supports `'*'`) | -| `-r, --rule ` | Install specific canonical rules by name (repeatable) | -| `-p, --prompt ` | Install specific canonical prompts by name (repeatable) | -| `--custom-agent ` | Install specific canonical custom agents by name (repeatable) | -| `-a, --agents ` | Target agents (comma-separated; use `'*'` for all) | -| `--copy` | Copy files instead of symlinking skills | -| `--dry-run` | Preview writes without making changes | -| `--force` | Overwrite conflicting managed/unmanaged outputs | -| `--append` | Append rules to `AGENTS.md`/`CLAUDE.md` instead of per-rule files | -| `--gitignore` | Add transpiled output paths to `.gitignore` (managed section) | -| `--full-depth` | Search all subdirectories even when a root `SKILL.md` exists | -| `-y, --yes` | Skip confirmation prompts | -| `--all` | Shorthand for `--skill '*' --agents '*' -y` | - -> **`--agents`:** A single flag for both skill install targets and transpilation targets. For skills, any of the 41 supported agents (e.g., `--agents cursor,claude-code`). For rules, prompts, and agents, the 6 transpilation targets: copilot, claude, cursor, windsurf, cline, opencode. When omitted, all detected agents are used for skills and all 6 transpilation targets for rules/prompts/agents. +| Option | Description | +| ---------------------------- | ---------------------------------------------------------------------------- | +| `-g, --global` | Install to user directory instead of project | +| `-t, --type ` | Filter by context type (`skill`, `rule`, `prompt`, `agent`; comma-separated) | +| `-s, --skill ` | Install specific skills by name (repeatable; supports `'*'`) | +| `-r, --rule ` | Install specific canonical rules by name (repeatable) | +| `-p, --prompt ` | Install specific canonical prompts by name (repeatable) | +| `--custom-agent ` | Install specific canonical custom agents by name (repeatable) | +| `-a, --targets ` | Targets (comma-separated; use `'*'` for all) | +| `--copy` | Copy files instead of symlinking skills | +| `--dry-run` | Preview writes without making changes | +| `--force` | Overwrite conflicting managed/unmanaged outputs | +| `--append` | Append rules to `AGENTS.md`/`CLAUDE.md` instead of per-rule files | +| `--gitignore` | Add transpiled output paths to `.gitignore` (managed section) | +| `--full-depth` | Search all subdirectories even when a root `SKILL.md` exists | +| `-y, --yes` | Skip confirmation prompts | +| `--all` | Shorthand for `--skill '*' --targets '*' -y` | + +> **`--targets`:** A single flag for both skill install targets and transpilation targets. For skills, any of the 41 supported targets (e.g., `--targets cursor,claude-code`). For rules, prompts, and agents, the 6 transpilation targets: copilot, claude, cursor, windsurf, cline, opencode. When omitted, all detected targets are used for skills and all 6 transpilation targets for rules/prompts/agents. > **Zero-flag mode:** Running `dotai add owner/repo` with no type-specific flags discovers all content types (skills, rules, prompts, agents) and presents an interactive grouped selection. Use `dotai find owner/repo` for a non-interactive preview. -> **`--append`:** Instead of writing individual rule files (e.g., `.github/instructions/code-style.instructions.md`), rules are appended as marker-delimited sections into `AGENTS.md` (Copilot) and `CLAUDE.md` (Claude Code). Useful for projects that prefer a single monolithic instruction file. Only applies to Copilot and Claude Code targets; other agents always get individual files. +> **`--append`:** Instead of writing individual rule files (e.g., `.github/instructions/code-style.instructions.md`), rules are appended as marker-delimited sections into `AGENTS.md` (Copilot) and `CLAUDE.md` (Claude Code). Useful for projects that prefer a single monolithic instruction file. Only applies to Copilot and Claude Code targets; other targets always get individual files. > **`--gitignore`:** Adds transpiled output file paths to a managed `# dotai:start` / `# dotai:end` section in `.gitignore`. Use when transpiled outputs should not be committed — only the canonical source files and `.dotai-lock.json` are checked in, and teammates run `dotai add` to regenerate outputs locally. -Supported agent aliases include values such as `claude-code` and `codex`. See [Supported Agents](supported-agents.md). +Supported target aliases include values such as `claude-code` and `codex`. See [Supported Targets](supported-targets.md). ## remove command options -| Option | Description | -| -------------------------- | ---------------------------------------------------------------------------- | -| `-g, --global` | Remove from global scope | -| `-a, --agents ` | Remove from specific agents (use `'*'` for all agents) | -| `-t, --type ` | Filter by context type (`skill`, `rule`, `prompt`, `agent`; comma-separated) | -| `-y, --yes` | Skip confirmation prompts | -| `--all` | Remove all installed items | +| Option | Description | +| ---------------------------- | ---------------------------------------------------------------------------- | +| `-g, --global` | Remove from global scope | +| `-a, --targets ` | Remove from specific targets (use `'*'` for all targets) | +| `-t, --type ` | Filter by context type (`skill`, `rule`, `prompt`, `agent`; comma-separated) | +| `-y, --yes` | Skip confirmation prompts | +| `--all` | Remove all installed items | ## find command @@ -145,11 +145,11 @@ Convert native agent-specific rule files into canonical `RULES.md` format. ## list command options -| Option | Description | -| -------------------------- | ---------------------------------------------------------------------------- | -| `-g, --global` | List global context (default: project) | -| `-a, --agents ` | Filter by specific agents | -| `-t, --type ` | Filter by context type (`skill`, `rule`, `prompt`, `agent`; comma-separated) | +| Option | Description | +| ---------------------------- | ---------------------------------------------------------------------------- | +| `-g, --global` | List global context (default: project) | +| `-a, --targets ` | Filter by specific targets | +| `-t, --type ` | Filter by context type (`skill`, `rule`, `prompt`, `agent`; comma-separated) | ## Installation Scope @@ -185,8 +185,8 @@ npx dotai add owner/repo --prompt review-code --rule code-style # Install a custom agent npx dotai add owner/repo --custom-agent architect -# Install agents targeting specific transpilation agents -npx dotai add owner/repo --custom-agent architect --agents copilot,claude +# Install agents targeting specific transpilation targets +npx dotai add owner/repo --custom-agent architect --targets copilot,claude # Force replace an existing unmanaged target file npx dotai add owner/repo --rule code-style --force @@ -198,7 +198,7 @@ npx dotai add owner/repo --rule code-style --append npx dotai add owner/repo --rule code-style --gitignore # CI-friendly non-interactive install -npx dotai add owner/repo --all --agents copilot,claude,cursor,windsurf,cline,opencode -y +npx dotai add owner/repo --all --targets copilot,claude,cursor,windsurf,cline,opencode -y ``` ## Team Sharing @@ -215,8 +215,8 @@ npx dotai add owner/repo --prompt review-code -y # Share all rules and prompts from a repo npx dotai add owner/repo --type rule,prompt -y -# CI-friendly: install everything, skip prompts, target specific agents -npx dotai add owner/repo --all --agents copilot,claude,cursor -y +# CI-friendly: install everything, skip prompts, limit to specific targets +npx dotai add owner/repo --all --targets copilot,claude,cursor -y ``` ## How transpilation works @@ -224,7 +224,7 @@ npx dotai add owner/repo --all --agents copilot,claude,cursor -y When you write a canonical file (`RULES.md`, `PROMPT.md`, `AGENT.md`), dotai splits it into two parts: - **Frontmatter** (metadata like `activation`, `globs`, `model`, `tools`) is **mapped per-agent** into each target's native format. -- **Body** (everything after the frontmatter) is **passed verbatim** to all target agents. No content is filtered, adapted, or rewritten. +- **Body** (everything after the frontmatter) is **passed verbatim** to all targets. No content is filtered, adapted, or rewritten. This means canonical bodies should contain **agent-agnostic instructions** — describe _what_ to do, not _how_ to do it with a specific agent's tools. For example, "run the tests before committing" is portable; "use the Bash tool to run tests" is Claude Code-specific and will land unchanged in Cursor, Windsurf, Copilot, and Cline where it won't make sense. @@ -241,7 +241,7 @@ dotai also discovers **native agent-specific files** in source repos and passes | Windsurf | `.windsurf/rules/*.md` | `.windsurf/workflows/*.md` | — | | Cline | `.clinerules/*.md` | — | — | -A single source repo can contain both canonical and native files. Canonical files fan out to all target agents; native files go only to their matching agent. +A single source repo can contain both canonical and native files. Canonical files fan out to all targets; native files go only to their matching agent. | Use case | Approach | | ----------------------------------------------- | --------------------- | diff --git a/docs/supported-agents.md b/docs/supported-targets.md similarity index 95% rename from docs/supported-agents.md rename to docs/supported-targets.md index 46ab3eb..6cf62e4 100644 --- a/docs/supported-agents.md +++ b/docs/supported-targets.md @@ -1,15 +1,15 @@ -# Supported Agents +# Supported Targets -dotai installs `SKILL.md` files into the config directories of 41 agents. **GitHub Copilot**, **Claude Code**, and **OpenCode** are actively tested; other agents follow the [Agent Skills specification](https://agentskills.io) but are not individually verified. +dotai installs `SKILL.md` files into the config directories of 41 targets. **GitHub Copilot**, **Claude Code**, and **OpenCode** are actively tested; other targets follow the [Agent Skills specification](https://agentskills.io) but are not individually verified. Rules, prompts, and agent definitions use a separate set of [transpilation targets](../README.md#supported-targets) (Copilot, Claude Code, Cursor, Windsurf, Cline, OpenCode). OpenCode is both a skill install target and a transpilation target.
-Full agent table +Full target table -| Agent | `--agents` | Project Path | Global Path | +| Target | `--targets` | Project Path | Global Path | | ------------------------------------- | ---------------------------------------- | ---------------------- | ------------------------------- | | Amp, Kimi Code CLI, Replit, Universal | `amp`, `kimi-cli`, `replit`, `universal` | `.agents/skills/` | `~/.config/agents/skills/` | | Antigravity | `antigravity` | `.agent/skills/` | `~/.gemini/antigravity/skills/` | diff --git a/scripts/sync-agents.ts b/scripts/sync-agents.ts index fdf03a3..55b46d8 100644 --- a/scripts/sync-agents.ts +++ b/scripts/sync-agents.ts @@ -7,17 +7,17 @@ import { agents } from '../src/agents.ts'; const ROOT = join(import.meta.dirname, '..'); const README_PATH = join(ROOT, 'README.md'); -const AGENTS_DOC_PATH = join(ROOT, 'docs', 'supported-agents.md'); +const AGENTS_DOC_PATH = join(ROOT, 'docs', 'supported-targets.md'); const PACKAGE_PATH = join(ROOT, 'package.json'); function generateAgentList(): string { const agentList = Object.values(agents); const count = agentList.length; - return `Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [${count - 4} more](docs/supported-agents.md).`; + return `Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [${count - 4} more](docs/supported-targets.md).`; } function generateAgentNames(): string { - return 'Target specific agents (e.g., `claude-code`, `codex`). See [Supported Agents](docs/supported-agents.md)'; + return 'Limit to specific targets (e.g., `claude-code`, `codex`). See [Supported Targets](docs/supported-targets.md)'; } function generateAvailableAgentsTable(): string { @@ -56,7 +56,7 @@ function generateAvailableAgentsTable(): string { return `| ${names} | ${keys} | \`${group.skillsDir}/\` | ${globalPath} |`; }); return [ - '| Agent | `--agent` | Project Path | Global Path |', + '| Target | `--targets` | Project Path | Global Path |', '|-------|-----------|--------------|-------------|', ...rows, ].join('\n'); @@ -112,7 +112,7 @@ function main() { agentsDoc = replaceSection(agentsDoc, 'skill-discovery', generateSkillDiscoveryPaths()); writeFileSync(AGENTS_DOC_PATH, agentsDoc); - console.log('docs/supported-agents.md updated'); + console.log('docs/supported-targets.md updated'); const pkg = JSON.parse(readFileSync(PACKAGE_PATH, 'utf-8')); pkg.keywords = generateKeywords(); diff --git a/src/add-install.test.ts b/src/add-install.test.ts index c8f8806..694521e 100644 --- a/src/add-install.test.ts +++ b/src/add-install.test.ts @@ -71,27 +71,27 @@ describe('resolveInstallTargets', () => { vi.clearAllMocks(); }); - // ── --agents flag ── + // ── --targets flag ── - describe('--agents flag', () => { - it('--agents "*" selects all agents', async () => { - const options: AddOptions = { agents: ['*'], yes: true, copy: true }; + describe('--targets flag', () => { + it('--targets "*" selects all agents', async () => { + const options: AddOptions = { targets: ['*'], yes: true, copy: true }; const result = await resolveInstallTargets(options, makeSpinner()); expect(result).not.toBeNull(); expect(result!.targetAgents).toEqual(Object.keys(agents)); }); - it('--agents with specific names validates and returns them', async () => { - const options: AddOptions = { agents: ['claude-code', 'cursor'], yes: true, copy: true }; + it('--targets with specific names validates and returns them', async () => { + const options: AddOptions = { targets: ['claude-code', 'cursor'], yes: true, copy: true }; const result = await resolveInstallTargets(options, makeSpinner()); expect(result).not.toBeNull(); expect(result!.targetAgents).toEqual(['claude-code', 'cursor']); }); - it('--agents with invalid names throws CommandError', async () => { - const options: AddOptions = { agents: ['not-a-real-agent'], yes: true, copy: true }; + it('--targets with invalid names throws CommandError', async () => { + const options: AddOptions = { targets: ['not-a-real-agent'], yes: true, copy: true }; const error = await resolveInstallTargets(options, makeSpinner()).catch((e) => e); @@ -101,7 +101,7 @@ describe('resolveInstallTargets', () => { }); }); - // ── No --agents flag (detection flow) ── + // ── No --targets flag (detection flow) ── describe('agent detection', () => { it('no agents detected + --yes returns all agents', async () => { @@ -187,7 +187,7 @@ describe('resolveInstallTargets', () => { describe('installation scope', () => { it('--global sets installGlobally: true', async () => { - const options: AddOptions = { agents: ['claude-code'], global: true, yes: true, copy: true }; + const options: AddOptions = { targets: ['claude-code'], global: true, yes: true, copy: true }; const result = await resolveInstallTargets(options, makeSpinner()); expect(result).not.toBeNull(); @@ -195,7 +195,7 @@ describe('resolveInstallTargets', () => { }); it('no --global defaults to false with --yes', async () => { - const options: AddOptions = { agents: ['claude-code'], yes: true, copy: true }; + const options: AddOptions = { targets: ['claude-code'], yes: true, copy: true }; const result = await resolveInstallTargets(options, makeSpinner()); expect(result).not.toBeNull(); @@ -206,7 +206,7 @@ describe('resolveInstallTargets', () => { vi.mocked(p.select).mockResolvedValueOnce(true); // scope = global vi.mocked(p.select).mockResolvedValueOnce('symlink'); // mode - const options: AddOptions = { agents: ['claude-code'] }; + const options: AddOptions = { targets: ['claude-code'] }; const result = await resolveInstallTargets(options, makeSpinner()); expect(result).not.toBeNull(); @@ -218,7 +218,7 @@ describe('resolveInstallTargets', () => { it('scope prompt cancel returns null', async () => { vi.mocked(p.select).mockResolvedValueOnce(cancelSymbol); // scope cancelled - const options: AddOptions = { agents: ['claude-code'] }; + const options: AddOptions = { targets: ['claude-code'] }; const result = await resolveInstallTargets(options, makeSpinner()); expect(result).toBeNull(); @@ -229,7 +229,7 @@ describe('resolveInstallTargets', () => { describe('install mode', () => { it('--copy sets installMode to copy', async () => { - const options: AddOptions = { agents: ['claude-code'], yes: true, copy: true }; + const options: AddOptions = { targets: ['claude-code'], yes: true, copy: true }; const result = await resolveInstallTargets(options, makeSpinner()); expect(result).not.toBeNull(); @@ -237,7 +237,7 @@ describe('resolveInstallTargets', () => { }); it('no --copy + --yes defaults to symlink', async () => { - const options: AddOptions = { agents: ['claude-code'], yes: true }; + const options: AddOptions = { targets: ['claude-code'], yes: true }; const result = await resolveInstallTargets(options, makeSpinner()); expect(result).not.toBeNull(); @@ -248,7 +248,7 @@ describe('resolveInstallTargets', () => { vi.mocked(p.select).mockResolvedValueOnce(false); // scope = project vi.mocked(p.select).mockResolvedValueOnce('copy'); // mode = copy - const options: AddOptions = { agents: ['claude-code'] }; + const options: AddOptions = { targets: ['claude-code'] }; const result = await resolveInstallTargets(options, makeSpinner()); expect(result).not.toBeNull(); @@ -259,7 +259,7 @@ describe('resolveInstallTargets', () => { vi.mocked(p.select).mockResolvedValueOnce(false); // scope = project vi.mocked(p.select).mockResolvedValueOnce(cancelSymbol); // mode cancelled - const options: AddOptions = { agents: ['claude-code'] }; + const options: AddOptions = { targets: ['claude-code'] }; const result = await resolveInstallTargets(options, makeSpinner()); expect(result).toBeNull(); @@ -298,13 +298,13 @@ describe('resolveInstallTargets', () => { const options: AddOptions = { yes: true, copy: true }; await resolveInstallTargets(options, spinner); - expect(spinner.start).toHaveBeenCalledWith('Loading agents...'); + expect(spinner.start).toHaveBeenCalledWith('Loading targets...'); expect(spinner.stop).toHaveBeenCalled(); }); - it('does not use spinner when --agents flag is provided', async () => { + it('does not use spinner when --targets flag is provided', async () => { const spinner = makeSpinner(); - const options: AddOptions = { agents: ['claude-code'], yes: true, copy: true }; + const options: AddOptions = { targets: ['claude-code'], yes: true, copy: true }; await resolveInstallTargets(options, spinner); expect(spinner.start).not.toHaveBeenCalled(); diff --git a/src/add-install.ts b/src/add-install.ts index 61c4410..f1d5295 100644 --- a/src/add-install.ts +++ b/src/add-install.ts @@ -42,32 +42,32 @@ export async function resolveInstallTargets( let targetAgents: AgentType[]; const validAgents = Object.keys(agents); - if (options.agents?.includes('*')) { - // --agents '*' selects all agents + if (options.targets?.includes('*')) { + // --targets '*' selects all agents targetAgents = validAgents as AgentType[]; - p.log.info(`Installing to all ${targetAgents.length} agents`); - } else if (options.agents && options.agents.length > 0) { - const invalidAgents = options.agents.filter((a) => !validAgents.includes(a)); + p.log.info(`Installing to all ${targetAgents.length} targets`); + } else if (options.targets && options.targets.length > 0) { + const invalidAgents = options.targets.filter((a) => !validAgents.includes(a)); if (invalidAgents.length > 0) { - p.log.error(`Invalid agents: ${invalidAgents.join(', ')}`); - p.log.info(`Valid agents: ${validAgents.join(', ')}`); + p.log.error(`Invalid targets: ${invalidAgents.join(', ')}`); + p.log.info(`Valid targets: ${validAgents.join(', ')}`); throw new CommandError(1); } - targetAgents = options.agents as AgentType[]; + targetAgents = options.targets as AgentType[]; } else { - spinner.start('Loading agents...'); + spinner.start('Loading targets...'); const installedAgents = await detectInstalledAgents(); const totalAgents = Object.keys(agents).length; - spinner.stop(`${totalAgents} agents`); + spinner.stop(`${totalAgents} targets`); if (installedAgents.length === 0) { if (options.yes) { targetAgents = validAgents as AgentType[]; - p.log.info('Installing to all agents'); + p.log.info('Installing to all targets'); } else { - p.log.info('Select agents to install skills to'); + p.log.info('Select targets to install skills to'); const allAgentChoices = Object.entries(agents).map(([key, config]) => ({ value: key as AgentType, @@ -76,7 +76,7 @@ export async function resolveInstallTargets( // Use helper to prompt with search const selected = await promptForAgents( - 'Which agents do you want to install to?', + 'Which targets do you want to install to?', allAgentChoices ); diff --git a/src/add-options.test.ts b/src/add-options.test.ts index 1a951c0..80c9159 100644 --- a/src/add-options.test.ts +++ b/src/add-options.test.ts @@ -34,14 +34,14 @@ describe('parseAddOptions — rule-related flags', () => { expect(options.rule).toEqual(['code-style', 'security']); }); - it('parses --agents comma-separated', () => { - const { options } = parseAddOptions(['owner/repo', '--agents', 'copilot,claude,cursor']); - expect(options.agents).toEqual(['copilot', 'claude', 'cursor']); + it('parses --targets comma-separated', () => { + const { options } = parseAddOptions(['owner/repo', '--targets', 'copilot,claude,cursor']); + expect(options.targets).toEqual(['copilot', 'claude', 'cursor']); }); - it('parses --agents space-separated', () => { - const { options } = parseAddOptions(['owner/repo', '--agents', 'copilot', 'claude']); - expect(options.agents).toEqual(['copilot', 'claude']); + it('parses --targets space-separated', () => { + const { options } = parseAddOptions(['owner/repo', '--targets', 'copilot', 'claude']); + expect(options.targets).toEqual(['copilot', 'claude']); }); it('parses --dry-run flag', () => { @@ -54,19 +54,19 @@ describe('parseAddOptions — rule-related flags', () => { expect(options.force).toBe(true); }); - it('parses combined rule + agents + dry-run + force flags', () => { + it('parses combined rule + targets + dry-run + force flags', () => { const { source, options } = parseAddOptions([ 'owner/repo', '--rule', 'code-style', - '--agents', + '--targets', 'copilot,claude', '--dry-run', '--force', ]); expect(source).toEqual(['owner/repo']); expect(options.rule).toEqual(['code-style']); - expect(options.agents).toEqual(['copilot', 'claude']); + expect(options.targets).toEqual(['copilot', 'claude']); expect(options.dryRun).toBe(true); expect(options.force).toBe(true); }); @@ -87,7 +87,7 @@ describe('parseAddOptions — rule-related flags', () => { const { source, options } = parseAddOptions(['owner/repo']); expect(source).toEqual(['owner/repo']); expect(options.rule).toBeUndefined(); - expect(options.agents).toBeUndefined(); + expect(options.targets).toBeUndefined(); expect(options.dryRun).toBeUndefined(); expect(options.force).toBeUndefined(); expect(options.append).toBeUndefined(); @@ -98,12 +98,12 @@ describe('parseAddOptions — rule-related flags', () => { expect(options.append).toBe(true); }); - it('parses --append with --rule, --agents, --dry-run, --force', () => { + it('parses --append with --rule, --targets, --dry-run, --force', () => { const { source, options } = parseAddOptions([ 'owner/repo', '--rule', 'code-style', - '--agents', + '--targets', 'copilot,claude', '--append', '--dry-run', @@ -111,7 +111,7 @@ describe('parseAddOptions — rule-related flags', () => { ]); expect(source).toEqual(['owner/repo']); expect(options.rule).toEqual(['code-style']); - expect(options.agents).toEqual(['copilot', 'claude']); + expect(options.targets).toEqual(['copilot', 'claude']); expect(options.append).toBe(true); expect(options.dryRun).toBe(true); expect(options.force).toBe(true); diff --git a/src/add-options.ts b/src/add-options.ts index e3ab864..8804e69 100644 --- a/src/add-options.ts +++ b/src/add-options.ts @@ -3,8 +3,8 @@ import { consumeMultiValues } from './cli-parse.ts'; export interface AddOptions { global?: boolean; - /** Target agents — resolves to skill agents or transpilation targets depending on content type. */ - agents?: string[]; + /** Install targets — resolves to skill agents or transpilation targets depending on content type. */ + targets?: string[]; yes?: boolean; skill?: string[]; rule?: string[]; @@ -37,10 +37,10 @@ export function parseAddOptions(args: string[]): { source: string[]; options: Ad options.yes = true; } else if (arg === '--all') { options.all = true; - } else if (arg === '-a' || arg === '--agents') { - options.agents = options.agents || []; + } else if (arg === '-a' || arg === '--targets') { + options.targets = options.targets || []; const { values, nextIndex } = consumeMultiValues(args, i + 1, { splitCommas: true }); - options.agents.push(...values); + options.targets.push(...values); i = nextIndex - 1; // Back up one since the loop will increment } else if (arg === '-s' || arg === '--skill') { options.skill = options.skill || []; diff --git a/src/add-wellknown.test.ts b/src/add-wellknown.test.ts index 5fd0b56..53d08b5 100644 --- a/src/add-wellknown.test.ts +++ b/src/add-wellknown.test.ts @@ -110,7 +110,7 @@ function makeSpinner() { function defaultOptions(overrides: Partial = {}): AddOptions { return { yes: true, - agents: ['opencode'], + targets: ['opencode'], copy: true, ...overrides, }; @@ -222,13 +222,13 @@ describe('handleWellKnownSkills', () => { expect(installWellKnownSkillForAgent).toHaveBeenCalledTimes(1); }); - // --- --agents '*' selects all agents --- + // --- --targets '*' selects all agents --- - it('installs to all agents with --agents "*"', async () => { + it('installs to all agents with --targets "*"', async () => { await handleWellKnownSkills( 'https://example.com', 'https://example.com', - defaultOptions({ agents: ['*'] }), + defaultOptions({ targets: ['*'] }), makeSpinner() ); @@ -243,7 +243,7 @@ describe('handleWellKnownSkills', () => { const error = await handleWellKnownSkills( 'https://example.com', 'https://example.com', - defaultOptions({ agents: ['not-a-real-agent'] }), + defaultOptions({ targets: ['not-a-real-agent'] }), makeSpinner() ).catch((e) => e); @@ -251,15 +251,15 @@ describe('handleWellKnownSkills', () => { expect(error.exitCode).toBe(1); }); - // --- Agent detection when no --agents provided --- + // --- Agent detection when no --targets provided --- - it('detects installed agents when --agents not provided', async () => { + it('detects installed agents when --targets not provided', async () => { vi.mocked(detectInstalledAgents).mockResolvedValue(['opencode'] as AgentType[]); await handleWellKnownSkills( 'https://example.com', 'https://example.com', - defaultOptions({ agents: undefined, yes: true }), + defaultOptions({ targets: undefined, yes: true }), makeSpinner() ); @@ -421,7 +421,7 @@ describe('handleWellKnownSkills', () => { await handleWellKnownSkills( 'https://example.com', 'https://example.com', - defaultOptions({ agents: ['opencode', 'claude-code'] }), + defaultOptions({ targets: ['opencode', 'claude-code'] }), makeSpinner() ); diff --git a/src/add.test.ts b/src/add.test.ts index 71e52de..e40c6f5 100644 --- a/src/add.test.ts +++ b/src/add.test.ts @@ -50,7 +50,7 @@ This is a test skill. ` ); - const result = runCli(['add', testDir, '-y', '-g', '--agents', 'claude-code'], testDir); + const result = runCli(['add', testDir, '-y', '-g', '--targets', 'claude-code'], testDir); expect(result.stdout).toContain('test-skill'); expect(result.exitCode).toBe(0); }); @@ -83,7 +83,7 @@ Instructions here. const targetDir = join(testDir, 'project'); mkdirSync(targetDir, { recursive: true }); - const result = runCli(['add', testDir, '-y', '-g', '--agents', 'claude-code'], targetDir); + const result = runCli(['add', testDir, '-y', '-g', '--targets', 'claude-code'], targetDir); expect(result.stdout).toContain('my-skill'); expect(result.stdout).toContain('Done!'); expect(result.exitCode).toBe(0); @@ -109,7 +109,7 @@ Instructions here. mkdirSync(targetDir, { recursive: true }); const result = runCli( - ['add', testDir, '-y', '--dry-run', '--agents', 'claude-code'], + ['add', testDir, '-y', '--dry-run', '--targets', 'claude-code'], targetDir ); @@ -148,7 +148,7 @@ description: Second skill ); const result = runCli( - ['add', testDir, '--skill', 'skill-one', '-y', '-g', '--agents', 'claude-code'], + ['add', testDir, '--skill', 'skill-one', '-y', '-g', '--targets', 'claude-code'], testDir ); expect(result.stdout).toContain('skill-one'); @@ -168,8 +168,8 @@ description: Test ` ); - const result = runCli(['add', testDir, '-y', '--agents', 'invalid-agent'], testDir); - expect(result.stdout).toContain('Invalid agents'); + const result = runCli(['add', testDir, '-y', '--targets', 'invalid-agent'], testDir); + expect(result.stdout).toContain('Invalid targets'); expect(result.exitCode).toBe(1); }); @@ -230,7 +230,7 @@ This is an internal skill. // --type skill -y will discover and install all skills; internal should be excluded const result = runCli( - ['add', testDir, '--type', 'skill', '-y', '--agents', 'claude-code'], + ['add', testDir, '--type', 'skill', '-y', '--targets', 'claude-code'], testDir ); expect(result.stdout).not.toContain('internal-skill'); @@ -256,7 +256,7 @@ This is an internal skill. ); const result = runCli( - ['add', testDir, '--skill', 'internal-skill', '-y', '-g', '--agents', 'claude-code'], + ['add', testDir, '--skill', 'internal-skill', '-y', '-g', '--targets', 'claude-code'], testDir, { INSTALL_INTERNAL_SKILLS: '1' } ); @@ -283,7 +283,7 @@ This is an internal skill. ); const result = runCli( - ['add', testDir, '--skill', 'internal-skill', '-y', '-g', '--agents', 'claude-code'], + ['add', testDir, '--skill', 'internal-skill', '-y', '-g', '--targets', 'claude-code'], testDir, { INSTALL_INTERNAL_SKILLS: 'true' } ); @@ -321,7 +321,7 @@ description: A public skill // Without env var - only public skill visible const resultWithout = runCli( - ['add', testDir, '--type', 'skill', '-y', '--agents', 'claude-code'], + ['add', testDir, '--type', 'skill', '-y', '--targets', 'claude-code'], testDir ); expect(resultWithout.stdout).toContain('public-skill'); @@ -329,7 +329,7 @@ description: A public skill // With env var - both visible const resultWith = runCli( - ['add', testDir, '--type', 'skill', '-y', '--agents', 'claude-code'], + ['add', testDir, '--type', 'skill', '-y', '--targets', 'claude-code'], testDir, { INSTALL_INTERNAL_SKILLS: '1' } ); @@ -353,7 +353,7 @@ metadata: ); const result = runCli( - ['add', testDir, '--type', 'skill', '-y', '--agents', 'claude-code'], + ['add', testDir, '--type', 'skill', '-y', '--targets', 'claude-code'], testDir ); expect(result.stdout).toContain('not-internal-skill'); @@ -413,23 +413,23 @@ describe('parseAddOptions', () => { expect(result.options.skill).toEqual(['*']); }); - it('should parse --agent with wildcard', () => { - const result = parseAddOptions(['source', '--agents', '*']); + it('should parse --targets with wildcard', () => { + const result = parseAddOptions(['source', '--targets', '*']); expect(result.source).toEqual(['source']); - expect(result.options.agents).toEqual(['*']); + expect(result.options.targets).toEqual(['*']); }); - it('should parse --skill wildcard with specific agents', () => { - const result = parseAddOptions(['source', '--skill', '*', '--agents', 'claude-code']); + it('should parse --skill wildcard with specific targets', () => { + const result = parseAddOptions(['source', '--skill', '*', '--targets', 'claude-code']); expect(result.source).toEqual(['source']); expect(result.options.skill).toEqual(['*']); - expect(result.options.agents).toEqual(['claude-code']); + expect(result.options.targets).toEqual(['claude-code']); }); - it('should parse --agent wildcard with specific skills', () => { - const result = parseAddOptions(['source', '--agents', '*', '--skill', 'my-skill']); + it('should parse --targets wildcard with specific skills', () => { + const result = parseAddOptions(['source', '--targets', '*', '--skill', 'my-skill']); expect(result.source).toEqual(['source']); - expect(result.options.agents).toEqual(['*']); + expect(result.options.targets).toEqual(['*']); expect(result.options.skill).toEqual(['my-skill']); }); @@ -538,17 +538,17 @@ describe('parseAddOptions', () => { expect(result.options.customAgent).toEqual(['architect']); }); - it('should parse --custom-agent with --agents combination', () => { + it('should parse --custom-agent with --targets combination', () => { const result = parseAddOptions([ 'source', '--custom-agent', 'architect', - '--agents', + '--targets', 'copilot,claude', ]); expect(result.source).toEqual(['source']); expect(result.options.customAgent).toEqual(['architect']); - expect(result.options.agents).toEqual(['copilot', 'claude']); + expect(result.options.targets).toEqual(['copilot', 'claude']); }); it('should parse --type flag with single type', () => { @@ -598,13 +598,13 @@ describe('parseAddOptions', () => { 'source', '--type', 'rule', - '--agents', + '--targets', 'copilot,claude', '--force', ]); expect(result.source).toEqual(['source']); expect(result.options.type).toEqual(['rule']); - expect(result.options.agents).toEqual(['copilot', 'claude']); + expect(result.options.targets).toEqual(['copilot', 'claude']); expect(result.options.force).toBe(true); }); diff --git a/src/add.ts b/src/add.ts index 5445d45..ded5d76 100644 --- a/src/add.ts +++ b/src/add.ts @@ -42,26 +42,26 @@ const version = packageJson.version; setVersion(version); /** - * Resolve transpilation target agents from --agents flag, or default to all five. + * Resolve transpilation target agents from --targets flag, or default to all five. * - * When --agents is used for transpilation (rules/prompts/agents), values are + * When --targets is used for transpilation (rules/prompts/agents), values are * resolved as TargetAgent names or aliases (copilot, claude, cursor, windsurf, cline). * - * Throws CommandError if invalid or no valid agents are specified. + * Throws CommandError if invalid or no valid targets are specified. */ function resolveAgentsOrDefault(options: AddOptions): TargetAgent[] { - if (options.agents && options.agents.length > 0) { - const { agents: resolved, invalid } = resolveTargetAgents(options.agents); + if (options.targets && options.targets.length > 0) { + const { agents: resolved, invalid } = resolveTargetAgents(options.targets); if (invalid.length > 0) { - p.log.error(`Invalid target agents: ${invalid.join(', ')}`); - p.log.info(`Valid agents: ${TARGET_AGENTS.join(', ')}`); + p.log.error(`Invalid targets: ${invalid.join(', ')}`); + p.log.info(`Valid targets: ${TARGET_AGENTS.join(', ')}`); p.log.info(`Aliases: copilot, claude, cursor, windsurf, cline`); throw new CommandError(1); } if (resolved.length === 0) { - p.log.error('No valid target agents specified'); + p.log.error('No valid targets specified'); throw new CommandError(1); } @@ -104,7 +104,7 @@ const CONTEXT_CONFIGS: Record<'rule' | 'prompt' | 'agent', ContextInstallConfig> sourcePath, projectRoot, ruleNames: names, - agents, + targets: agents, dryRun: options.dryRun, force: options.force, append: options.append, @@ -122,7 +122,7 @@ const CONTEXT_CONFIGS: Record<'rule' | 'prompt' | 'agent', ContextInstallConfig> sourcePath, projectRoot, promptNames: names, - agents, + targets: agents, dryRun: options.dryRun, force: options.force, gitignore: options.gitignore, @@ -139,7 +139,7 @@ const CONTEXT_CONFIGS: Record<'rule' | 'prompt' | 'agent', ContextInstallConfig> sourcePath, projectRoot, agentNames: names, - agents, + targets: agents, dryRun: options.dryRun, force: options.force, gitignore: options.gitignore, @@ -152,7 +152,7 @@ const CONTEXT_CONFIGS: Record<'rule' | 'prompt' | 'agent', ContextInstallConfig> /** * Generic handler for rule, prompt, and agent install flows. * - * Resolves `--agents` names to TargetAgent[], runs the appropriate discovery → + * Resolves `--targets` names to TargetAgent[], runs the appropriate discovery → * transpile → install pipeline, and displays results using @clack/prompts. */ async function handleContextInstall( @@ -168,7 +168,7 @@ async function handleContextInstall( // 1. Resolve target agents from --targets flag (or default to all five) const targetAgents = resolveAgentsOrDefault(options); - p.log.info(`Target agents: ${targetAgents.map((a) => pc.cyan(a)).join(', ')}`); + p.log.info(`Targets: ${targetAgents.map((a) => pc.cyan(a)).join(', ')}`); // 2. Run install pipeline const names = config.getNames(options); @@ -251,10 +251,10 @@ export async function runAdd(args: string[], options: AddOptions = {}): Promise< throw new CommandError(1); } - // --all implies --skill '*' and --agents '*' and -y + // --all implies --skill '*' and --targets '*' and -y if (options.all) { options.skill = ['*']; - options.agents = ['*']; + options.targets = ['*']; options.yes = true; } @@ -1019,7 +1019,7 @@ async function promptForFindSkills( skill: ['find-skills'], global: true, yes: true, - agents: findSkillsAgents, + targets: findSkillsAgents, }); } catch { p.log.warn('Failed to install find-skills. You can try again with:'); diff --git a/src/cli.test.ts b/src/cli.test.ts index f38211e..cbc6a15 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -38,7 +38,7 @@ describe('dotai CLI', () => { it('should display essential add options', () => { const output = runCliOutput(['add', '--help']); expect(output).toContain('Usage: dotai add [options]'); - expect(output).toContain('-a, --agents'); + expect(output).toContain('-a, --targets'); expect(output).toContain('-t, --type'); expect(output).toContain('-g, --global'); expect(output).toContain('-y, --yes'); @@ -57,7 +57,7 @@ describe('dotai CLI', () => { expect(output).toContain('-s, --skill'); expect(output).toContain('-r, --rule'); expect(output).toContain('-p, --prompt'); - expect(output).toContain('-a, --agents'); + expect(output).toContain('-a, --targets'); expect(output).toContain('--dry-run'); expect(output).toContain('--force'); expect(output).toContain('--append'); @@ -72,7 +72,7 @@ describe('dotai CLI', () => { const output = runCliOutput(['list', '--help']); expect(output).toContain('Usage: dotai list [options]'); expect(output).toContain('-g, --global'); - expect(output).toContain('-a, --agents'); + expect(output).toContain('-a, --targets'); expect(output).toContain('-t, --type'); }); }); diff --git a/src/cli.ts b/src/cli.ts index 7edc334..81e589b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -142,15 +142,15 @@ ${BOLD}Description:${RESET} ${BOLD}Essentials:${RESET} GitHub shorthand (owner/repo), URL, or local path - -a, --agents Target agents (comma-separated; use '*' for all) - -t, --type Filter by type (skill, rule, prompt, agent; comma-separated) - -g, --global Install globally (user-level) - -y, --yes Skip confirmation prompts + -a, --targets Target agents (comma-separated; use '*' for all) + -t, --type Filter by type (skill, rule, prompt, agent; comma-separated) + -g, --global Install globally (user-level) + -y, --yes Skip confirmation prompts ${BOLD}Examples:${RESET} ${DIM}$${RESET} dotai add vercel-labs/agent-skills ${DIM}# interactive (all types)${RESET} ${DIM}$${RESET} dotai add owner/repo --rule code-style ${DIM}# install a specific rule${RESET} - ${DIM}$${RESET} dotai add owner/repo --agents copilot,claude ${DIM}# target specific agents${RESET} + ${DIM}$${RESET} dotai add owner/repo --targets copilot,claude ${DIM}# target specific agents${RESET} ${DIM}$${RESET} dotai add owner/repo --type rule,prompt -y ${DIM}# install all rules and prompts${RESET} Run ${BOLD}dotai add --help-all${RESET} for all options. @@ -175,9 +175,9 @@ ${BOLD}Content Selection:${RESET} -t, --type Filter by type (skill, rule, prompt, agent; comma-separated) ${BOLD}Target Options:${RESET} - -a, --agents Target agents (comma-separated; use '*' for all) - For skills: any of the ${DIM}41 supported agents${RESET} - For rules/prompts/agents: copilot, claude, cursor, windsurf, cline + -a, --targets Target agents (comma-separated; use '*' for all) + For skills: any of the ${DIM}41 supported agents${RESET} + For rules/prompts/agents: copilot, claude, cursor, windsurf, cline ${BOLD}Install Options:${RESET} -g, --global Install globally (user-level) @@ -187,13 +187,13 @@ ${BOLD}Install Options:${RESET} --append Append rules to AGENTS.md/CLAUDE.md instead of per-rule files --gitignore Add transpiled output paths to .gitignore --full-depth Search all subdirectories even when a root SKILL.md exists - --all Shorthand for --skill '*' --agents '*' -y + --all Shorthand for --skill '*' --targets '*' -y -y, --yes Skip confirmation prompts ${BOLD}Examples:${RESET} ${DIM}$${RESET} dotai add vercel-labs/agent-skills ${DIM}# interactive (all types)${RESET} ${DIM}$${RESET} dotai add owner/repo --rule code-style ${DIM}# install a specific rule${RESET} - ${DIM}$${RESET} dotai add owner/repo --agents copilot,claude ${DIM}# target specific agents${RESET} + ${DIM}$${RESET} dotai add owner/repo --targets copilot,claude ${DIM}# target specific agents${RESET} ${DIM}$${RESET} dotai add owner/repo --prompt review-code ${DIM}# install a prompt${RESET} ${DIM}$${RESET} dotai add owner/repo --type rule,prompt -y ${DIM}# install all rules and prompts${RESET} ${DIM}$${RESET} dotai add owner/repo --all -g ${DIM}# install everything globally${RESET} @@ -211,7 +211,7 @@ ${BOLD}Description:${RESET} ${BOLD}Options:${RESET} -g, --global List global context (default: project) - -a, --agents Filter by specific agents + -a, --targets Filter by specific targets -t, --type Filter by type (skill, rule, prompt, agent; comma-separated) ${BOLD}Examples:${RESET} @@ -238,18 +238,18 @@ ${BOLD}Arguments:${RESET} ${BOLD}Options:${RESET} -g, --global Remove from global scope (~/) instead of project scope - -a, --agents Remove from specific agents (use '*' for all agents) + -a, --targets Remove from specific targets (use '*' for all targets) -s, --skill Specify skills to remove (use '*' for all skills) -t, --type Filter by context type (skill, rule, prompt, agent; comma-separated) -y, --yes Skip confirmation prompts - --all Shorthand for --skill '*' --agents '*' -y + --all Shorthand for --skill '*' --targets '*' -y ${BOLD}Examples:${RESET} ${DIM}$${RESET} dotai remove ${DIM}# interactive selection${RESET} ${DIM}$${RESET} dotai remove my-skill ${DIM}# remove specific skill${RESET} ${DIM}$${RESET} dotai remove skill1 skill2 -y ${DIM}# remove multiple, skip confirm${RESET} ${DIM}$${RESET} dotai remove --global my-skill ${DIM}# remove from global scope${RESET} - ${DIM}$${RESET} dotai rm --agents claude-code my-skill ${DIM}# remove from specific agent${RESET} + ${DIM}$${RESET} dotai rm --targets claude-code my-skill ${DIM}# remove from specific target${RESET} ${DIM}$${RESET} dotai remove --type rule code-style ${DIM}# remove a specific rule${RESET} ${DIM}$${RESET} dotai remove --type prompt -y ${DIM}# remove all prompts${RESET} ${DIM}$${RESET} dotai remove --type rule,prompt ${DIM}# interactive rule/prompt removal${RESET} diff --git a/src/list.test.ts b/src/list.test.ts index bbc1086..1cdf5eb 100644 --- a/src/list.test.ts +++ b/src/list.test.ts @@ -38,28 +38,28 @@ describe('list command', () => { it('should parse -a flag with single agent', () => { const options = parseListOptions(['-a', 'claude-code']); - expect(options.agents).toEqual(['claude-code']); + expect(options.targets).toEqual(['claude-code']); }); - it('should parse --agents flag with single agent', () => { - const options = parseListOptions(['--agents', 'cursor']); - expect(options.agents).toEqual(['cursor']); + it('should parse --targets flag with single agent', () => { + const options = parseListOptions(['--targets', 'cursor']); + expect(options.targets).toEqual(['cursor']); }); it('should parse -a flag with multiple agents', () => { const options = parseListOptions(['-a', 'claude-code', 'cursor', 'codex']); - expect(options.agents).toEqual(['claude-code', 'cursor', 'codex']); + expect(options.targets).toEqual(['claude-code', 'cursor', 'codex']); }); it('should parse combined flags', () => { const options = parseListOptions(['-g', '-a', 'claude-code', 'cursor']); expect(options.global).toBe(true); - expect(options.agents).toEqual(['claude-code', 'cursor']); + expect(options.targets).toEqual(['claude-code', 'cursor']); }); it('should stop collecting agents at next flag', () => { const options = parseListOptions(['-a', 'claude-code', '-g']); - expect(options.agents).toEqual(['claude-code']); + expect(options.targets).toEqual(['claude-code']); expect(options.global).toBe(true); }); @@ -107,12 +107,14 @@ describe('list command', () => { const options = parseListOptions(['-g', '--type', 'rule', '-a', 'cursor']); expect(options.global).toBe(true); expect(options.type).toEqual(['rule']); - expect(options.agents).toEqual(['cursor']); + expect(options.targets).toEqual(['cursor']); }); - it('should parse --type agent standalone', () => { - const options = parseListOptions(['--type', 'agent']); - expect(options.type).toEqual(['agent']); + it('should parse --type with other flags', () => { + const options = parseListOptions(['-g', '--type', 'rule', '-a', 'cursor']); + expect(options.global).toBe(true); + expect(options.type).toEqual(['rule']); + expect(options.targets).toEqual(['cursor']); }); it('should normalize --type values to lowercase', () => { @@ -138,7 +140,7 @@ describe('list command', () => { it('should return empty agent array for -a with no following values', () => { const options = parseListOptions(['-a']); - expect(options.agents).toEqual([]); + expect(options.targets).toEqual([]); }); }); @@ -243,7 +245,7 @@ description: A project skill it('should show error for invalid agent filter', () => { const result = runCli(['list', '-a', 'invalid-agent'], testDir); - expect(result.stdout).toContain('Invalid agents'); + expect(result.stdout).toContain('Invalid targets'); expect(result.stdout).toContain('invalid-agent'); expect(result.exitCode).toBe(1); }); @@ -770,7 +772,7 @@ description: A global test skill it('should include list options in list --help', () => { const result = runCli(['list', '--help']); expect(result.stdout).toContain('-g, --global'); - expect(result.stdout).toContain('-a, --agents'); + expect(result.stdout).toContain('-a, --targets'); expect(result.stdout).toContain('-t, --type'); }); diff --git a/src/list.ts b/src/list.ts index c77f785..d7e0ab6 100644 --- a/src/list.ts +++ b/src/list.ts @@ -8,7 +8,7 @@ import { consumeMultiValues, parseTypeFlag } from './cli-parse.ts'; interface ListOptions { global?: boolean; - agents?: string[]; + targets?: string[]; type?: ContextType[]; } @@ -19,10 +19,10 @@ export function parseListOptions(args: string[]): ListOptions { const arg = args[i]; if (arg === '-g' || arg === '--global') { options.global = true; - } else if (arg === '-a' || arg === '--agents') { - options.agents = options.agents || []; + } else if (arg === '-a' || arg === '--targets') { + options.targets = options.targets || []; const { values, nextIndex } = consumeMultiValues(args, i + 1); - options.agents.push(...values); + options.targets.push(...values); i = nextIndex - 1; } else if (arg === '-t' || arg === '--type') { options.type = options.type || []; @@ -57,17 +57,17 @@ export async function runList(args: string[]): Promise { // Validate agent filter if provided let agentFilter: AgentType[] | undefined; - if (options.agents && options.agents.length > 0) { + if (options.targets && options.targets.length > 0) { const validAgents = Object.keys(agents); - const invalidAgents = options.agents.filter((a) => !validAgents.includes(a)); + const invalidAgents = options.targets.filter((a) => !validAgents.includes(a)); if (invalidAgents.length > 0) { - console.log(`${YELLOW}Invalid agents: ${invalidAgents.join(', ')}${RESET}`); - console.log(`${DIM}Valid agents: ${validAgents.join(', ')}${RESET}`); + console.log(`${YELLOW}Invalid targets: ${invalidAgents.join(', ')}${RESET}`); + console.log(`${DIM}Valid targets: ${validAgents.join(', ')}${RESET}`); process.exit(1); } - agentFilter = options.agents as AgentType[]; + agentFilter = options.targets as AgentType[]; } // ── Fetch skills ── @@ -180,7 +180,7 @@ export async function runList(args: string[]): Promise { const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET}`; console.log(`${prefix}${CYAN}${skill.name}${RESET} ${DIM}${shortPath}${RESET}`); - console.log(`${prefix} ${DIM}Agents:${RESET} ${agentInfo}`); + console.log(`${prefix} ${DIM}Targets:${RESET} ${agentInfo}`); } function printRule(entry: LockEntry): void { @@ -190,7 +190,7 @@ export async function runList(args: string[]): Promise { }); const agentInfo = agentNames.length > 0 ? formatList(agentNames) : `${YELLOW}none${RESET}`; console.log(`${CYAN}${entry.name}${RESET} ${DIM}${entry.source}${RESET}`); - console.log(` ${DIM}Agents:${RESET} ${agentInfo}`); + console.log(` ${DIM}Targets:${RESET} ${agentInfo}`); } // ── Skills section ── diff --git a/src/parser-parity.test.ts b/src/parser-parity.test.ts index 2ea9b81..b220d4b 100644 --- a/src/parser-parity.test.ts +++ b/src/parser-parity.test.ts @@ -4,7 +4,7 @@ import { parseRemoveOptions } from './remove.ts'; import { parseSyncOptions } from './sync.ts'; /** - * Parser parity tests: verify that shared flags (`--agents`, `--type`) produce + * Parser parity tests: verify that shared flags (`--targets`, `--type`) produce * consistent results across list, remove, and sync commands. * * Known intentional differences are documented inline: @@ -15,28 +15,28 @@ import { parseSyncOptions } from './sync.ts'; */ // --------------------------------------------------------------------------- -// --agents parity (list / remove / sync all use consumeMultiValues) +// --targets parity (list / remove / sync all use consumeMultiValues) // --------------------------------------------------------------------------- -describe('--agents parity across commands', () => { +describe('--targets parity across commands', () => { it('should parse -a with a single agent', () => { const list = parseListOptions(['-a', 'cursor']); const { options: remove } = parseRemoveOptions(['-a', 'cursor']); const { options: sync } = parseSyncOptions(['-a', 'cursor']); - expect(list.agents).toEqual(['cursor']); - expect(remove.agents).toEqual(['cursor']); - expect(sync.agents).toEqual(['cursor']); + expect(list.targets).toEqual(['cursor']); + expect(remove.targets).toEqual(['cursor']); + expect(sync.targets).toEqual(['cursor']); }); it('should parse --agent with a single agent', () => { - const list = parseListOptions(['--agents', 'claude-code']); - const { options: remove } = parseRemoveOptions(['--agents', 'claude-code']); - const { options: sync } = parseSyncOptions(['--agents', 'claude-code']); + const list = parseListOptions(['--targets', 'claude-code']); + const { options: remove } = parseRemoveOptions(['--targets', 'claude-code']); + const { options: sync } = parseSyncOptions(['--targets', 'claude-code']); - expect(list.agents).toEqual(['claude-code']); - expect(remove.agents).toEqual(['claude-code']); - expect(sync.agents).toEqual(['claude-code']); + expect(list.targets).toEqual(['claude-code']); + expect(remove.targets).toEqual(['claude-code']); + expect(sync.targets).toEqual(['claude-code']); }); it('should consume multiple space-separated agents', () => { @@ -44,9 +44,9 @@ describe('--agents parity across commands', () => { const { options: remove } = parseRemoveOptions(['-a', 'claude-code', 'cursor', 'codex']); const { options: sync } = parseSyncOptions(['-a', 'claude-code', 'cursor', 'codex']); - expect(list.agents).toEqual(['claude-code', 'cursor', 'codex']); - expect(remove.agents).toEqual(['claude-code', 'cursor', 'codex']); - expect(sync.agents).toEqual(['claude-code', 'cursor', 'codex']); + expect(list.targets).toEqual(['claude-code', 'cursor', 'codex']); + expect(remove.targets).toEqual(['claude-code', 'cursor', 'codex']); + expect(sync.targets).toEqual(['claude-code', 'cursor', 'codex']); }); it('should stop consuming agents at the next flag', () => { @@ -54,13 +54,13 @@ describe('--agents parity across commands', () => { const { options: remove } = parseRemoveOptions(['-a', 'claude-code', '-y']); const { options: sync } = parseSyncOptions(['-a', 'claude-code', '-y']); - expect(list.agents).toEqual(['claude-code']); + expect(list.targets).toEqual(['claude-code']); expect(list.global).toBe(true); - expect(remove.agents).toEqual(['claude-code']); + expect(remove.targets).toEqual(['claude-code']); expect(remove.yes).toBe(true); - expect(sync.agents).toEqual(['claude-code']); + expect(sync.targets).toEqual(['claude-code']); expect(sync.yes).toBe(true); }); @@ -69,41 +69,41 @@ describe('--agents parity across commands', () => { const { options: remove } = parseRemoveOptions(['-a']); const { options: sync } = parseSyncOptions(['-a']); - expect(list.agents).toEqual([]); - expect(remove.agents).toEqual([]); - expect(sync.agents).toEqual([]); + expect(list.targets).toEqual([]); + expect(remove.targets).toEqual([]); + expect(sync.targets).toEqual([]); }); it('should accumulate agents from repeated --agent flags', () => { - const list = parseListOptions(['-a', 'cursor', '--agents', 'codex']); - const { options: remove } = parseRemoveOptions(['-a', 'cursor', '--agents', 'codex']); - const { options: sync } = parseSyncOptions(['-a', 'cursor', '--agents', 'codex']); + const list = parseListOptions(['-a', 'cursor', '--targets', 'codex']); + const { options: remove } = parseRemoveOptions(['-a', 'cursor', '--targets', 'codex']); + const { options: sync } = parseSyncOptions(['-a', 'cursor', '--targets', 'codex']); - expect(list.agents).toEqual(['cursor', 'codex']); - expect(remove.agents).toEqual(['cursor', 'codex']); - expect(sync.agents).toEqual(['cursor', 'codex']); + expect(list.targets).toEqual(['cursor', 'codex']); + expect(remove.targets).toEqual(['cursor', 'codex']); + expect(sync.targets).toEqual(['cursor', 'codex']); }); it('should stop at --flag boundaries between groups', () => { - const list = parseListOptions(['-a', 'cursor', 'codex', '--agents', 'claude-code']); + const list = parseListOptions(['-a', 'cursor', 'codex', '--targets', 'claude-code']); const { options: remove } = parseRemoveOptions([ '-a', 'cursor', 'codex', - '--agents', + '--targets', 'claude-code', ]); const { options: sync } = parseSyncOptions([ '-a', 'cursor', 'codex', - '--agents', + '--targets', 'claude-code', ]); - expect(list.agents).toEqual(['cursor', 'codex', 'claude-code']); - expect(remove.agents).toEqual(['cursor', 'codex', 'claude-code']); - expect(sync.agents).toEqual(['cursor', 'codex', 'claude-code']); + expect(list.targets).toEqual(['cursor', 'codex', 'claude-code']); + expect(remove.targets).toEqual(['cursor', 'codex', 'claude-code']); + expect(sync.targets).toEqual(['cursor', 'codex', 'claude-code']); }); }); @@ -189,10 +189,10 @@ describe('--type parity between list and remove', () => { const { options: remove } = parseRemoveOptions(['--type', 'rule', '-a', 'cursor']); expect(list.type).toEqual(['rule']); - expect(list.agents).toEqual(['cursor']); + expect(list.targets).toEqual(['cursor']); expect(remove.type).toEqual(['rule']); - expect(remove.agents).toEqual(['cursor']); + expect(remove.targets).toEqual(['cursor']); }); }); @@ -261,17 +261,17 @@ describe('parseSyncOptions', () => { it('should parse -a with single agent', () => { const { options } = parseSyncOptions(['-a', 'cursor']); - expect(options.agents).toEqual(['cursor']); + expect(options.targets).toEqual(['cursor']); }); it('should parse -a with multiple agents', () => { const { options } = parseSyncOptions(['-a', 'cursor', 'codex', 'claude-code']); - expect(options.agents).toEqual(['cursor', 'codex', 'claude-code']); + expect(options.targets).toEqual(['cursor', 'codex', 'claude-code']); }); it('should stop consuming agents at next flag', () => { const { options } = parseSyncOptions(['-a', 'cursor', '-y']); - expect(options.agents).toEqual(['cursor']); + expect(options.targets).toEqual(['cursor']); expect(options.yes).toBe(true); }); @@ -279,12 +279,12 @@ describe('parseSyncOptions', () => { const { options } = parseSyncOptions(['-y', '-f', '-a', 'cursor', 'codex']); expect(options.yes).toBe(true); expect(options.force).toBe(true); - expect(options.agents).toEqual(['cursor', 'codex']); + expect(options.targets).toEqual(['cursor', 'codex']); }); it('should accumulate agents from repeated -a flags', () => { const { options } = parseSyncOptions(['-a', 'cursor', '-a', 'codex']); - expect(options.agents).toEqual(['cursor', 'codex']); + expect(options.targets).toEqual(['cursor', 'codex']); }); it('should ignore unknown flags', () => { @@ -294,6 +294,6 @@ describe('parseSyncOptions', () => { it('should handle -a with no following values', () => { const { options } = parseSyncOptions(['-a']); - expect(options.agents).toEqual([]); + expect(options.targets).toEqual([]); }); }); diff --git a/src/remove.test.ts b/src/remove.test.ts index 7ebbb23..3fb704a 100644 --- a/src/remove.test.ts +++ b/src/remove.test.ts @@ -197,34 +197,34 @@ This is a test skill. }); }); - describe('agent filtering', () => { + describe('target filtering', () => { beforeEach(() => { createTestSkill('test-skill'); createAgentSkillsDir('.claude'); createAgentSkillsDir('.cline'); }); - it('should show error for invalid agent name', () => { - const result = runCli(['remove', 'test-skill', '--agents', 'invalid-agent', '-y'], testDir); + it('should show error for invalid target name', () => { + const result = runCli(['remove', 'test-skill', '--targets', 'invalid-agent', '-y'], testDir); - expect(result.stdout).toContain('Invalid agents'); + expect(result.stdout).toContain('Invalid targets'); expect(result.stdout).toContain('invalid-agent'); - expect(result.stdout).toContain('Valid agents'); + expect(result.stdout).toContain('Valid targets'); expect(result.exitCode).toBe(1); }); - it('should accept valid agent names', () => { - // This should not error on agent validation - const result = runCli(['remove', 'test-skill', '--agents', 'claude-code', '-y'], testDir); - expect(result.stdout).not.toContain('Invalid agents'); + it('should accept valid target names', () => { + // This should not error on target validation + const result = runCli(['remove', 'test-skill', '--targets', 'claude-code', '-y'], testDir); + expect(result.stdout).not.toContain('Invalid targets'); }); - it('should accept multiple agent names', () => { + it('should accept multiple target names', () => { const result = runCli( - ['remove', 'test-skill', '--agents', 'claude-code', 'cursor', '-y'], + ['remove', 'test-skill', '--targets', 'claude-code', 'cursor', '-y'], testDir ); - expect(result.stdout).not.toContain('Invalid agents'); + expect(result.stdout).not.toContain('Invalid targets'); }); }); @@ -303,7 +303,7 @@ This is a test skill. expect(result.stdout).toContain('Usage'); expect(result.stdout).toContain('remove'); expect(result.stdout).toContain('--global'); - expect(result.stdout).toContain('--agents'); + expect(result.stdout).toContain('--targets'); expect(result.stdout).toContain('--yes'); expect(result.exitCode).toBe(0); }); @@ -331,17 +331,17 @@ This is a test skill. expect(result.exitCode).toBe(0); }); - it('should parse -a as agent', () => { + it('should parse -a as target', () => { const result = runCli(['remove', 'parse-test-skill', '-a', 'claude-code', '-y'], testDir); - expect(result.stdout).not.toContain('Invalid agents'); + expect(result.stdout).not.toContain('Invalid targets'); }); - it('should handle multiple values for --agent', () => { + it('should handle multiple values for --targets', () => { const result = runCli( - ['remove', 'parse-test-skill', '--agents', 'claude-code', 'cursor', '-y'], + ['remove', 'parse-test-skill', '--targets', 'claude-code', 'cursor', '-y'], testDir ); - expect(result.stdout).not.toContain('Invalid agents'); + expect(result.stdout).not.toContain('Invalid targets'); }); it('should show error for invalid --type value', () => { @@ -495,25 +495,25 @@ describe('removeCommand unit tests', () => { } }); - it('should throw CommandError with exit code 1 for invalid agent', async () => { + it('should throw CommandError with exit code 1 for invalid target', async () => { // Create a skill so we get past the "no skills found" early return const skillDir = join(skillsDir, 'test-skill'); mkdirSync(skillDir, { recursive: true }); writeFileSync(join(skillDir, 'SKILL.md'), '# Test'); await expect( - removeCommand(['test-skill'], { agents: ['not-a-real-agent'], yes: true }) + removeCommand(['test-skill'], { targets: ['not-a-real-agent'], yes: true }) ).rejects.toThrow(CommandError); try { - await removeCommand(['test-skill'], { agents: ['not-a-real-agent'], yes: true }); + await removeCommand(['test-skill'], { targets: ['not-a-real-agent'], yes: true }); } catch (error) { expect(error).toBeInstanceOf(CommandError); expect((error as CommandError).exitCode).toBe(1); } }); - it('should not throw for valid agent with matching skill', async () => { + it('should not throw for valid target with matching skill', async () => { // Create a skill in canonical location const skillDir = join(skillsDir, 'test-skill'); mkdirSync(skillDir, { recursive: true }); @@ -521,7 +521,7 @@ describe('removeCommand unit tests', () => { // Should complete without throwing await expect( - removeCommand(['test-skill'], { agents: ['claude-code'], yes: true }) + removeCommand(['test-skill'], { targets: ['claude-code'], yes: true }) ).resolves.toBeUndefined(); }); }); diff --git a/src/remove.ts b/src/remove.ts index 2fa12da..cb08da3 100644 --- a/src/remove.ts +++ b/src/remove.ts @@ -20,7 +20,7 @@ import { CommandError } from './command-result.ts'; export interface RemoveOptions { global?: boolean; - agents?: string[]; + targets?: string[]; yes?: boolean; all?: boolean; type?: ContextType[]; @@ -108,13 +108,13 @@ async function removeSkills(skillNames: string[], options: RemoveOptions) { } // Validate agent options BEFORE prompting for skill selection - if (options.agents && options.agents.length > 0) { + if (options.targets && options.targets.length > 0) { const validAgents = Object.keys(agents); - const invalidAgents = options.agents.filter((a) => !validAgents.includes(a)); + const invalidAgents = options.targets.filter((a) => !validAgents.includes(a)); if (invalidAgents.length > 0) { - p.log.error(`Invalid agents: ${invalidAgents.join(', ')}`); - p.log.info(`Valid agents: ${validAgents.join(', ')}`); + p.log.error(`Invalid targets: ${invalidAgents.join(', ')}`); + p.log.info(`Valid targets: ${validAgents.join(', ')}`); throw new CommandError(1); } } @@ -153,13 +153,13 @@ async function removeSkills(skillNames: string[], options: RemoveOptions) { } let targetAgents: AgentType[]; - if (options.agents && options.agents.length > 0) { - targetAgents = options.agents as AgentType[]; + if (options.targets && options.targets.length > 0) { + targetAgents = options.targets as AgentType[]; } else { // When removing, we should target all known agents to ensure // ghost symlinks are cleaned up, even if the agent is not detected. targetAgents = Object.keys(agents) as AgentType[]; - spinner.stop(`Targeting ${targetAgents.length} potential agent(s)`); + spinner.stop(`Targeting ${targetAgents.length} potential target(s)`); } if (!options.yes) { @@ -333,10 +333,10 @@ export function parseRemoveOptions(args: string[]): { skills: string[]; options: options.yes = true; } else if (arg === '--all') { options.all = true; - } else if (arg === '-a' || arg === '--agents') { - options.agents = options.agents || []; + } else if (arg === '-a' || arg === '--targets') { + options.targets = options.targets || []; const { values, nextIndex } = consumeMultiValues(args, i + 1); - options.agents.push(...values); + options.targets.push(...values); i = nextIndex - 1; } else if (arg === '-t' || arg === '--type') { options.type = options.type || []; @@ -397,8 +397,8 @@ async function removeDotaiManagedItems( let candidates = lock.items.filter((entry) => typeSet.has(entry.type)); // Apply agent filter if provided - if (options.agents && options.agents.length > 0) { - const agentSet = new Set(options.agents); + if (options.targets && options.targets.length > 0) { + const agentSet = new Set(options.targets); candidates = candidates.filter((entry) => entry.agents.some((a) => agentSet.has(a))); } diff --git a/src/restore.ts b/src/restore.ts index fad6146..5078b2c 100644 --- a/src/restore.ts +++ b/src/restore.ts @@ -87,7 +87,7 @@ async function restoreSkills(cwd: string, args: string[]): Promise { try { await runAdd([source], { skill: skills, - agents: universalAgentNames, + targets: universalAgentNames, yes: true, }); } catch (error) { @@ -104,7 +104,7 @@ async function restoreSkills(cwd: string, args: string[]): Promise { ); try { const { options: syncOptions } = parseSyncOptions(args); - await runSync(args, { ...syncOptions, yes: true, agents: universalAgentNames }); + await runSync(args, { ...syncOptions, yes: true, targets: universalAgentNames }); } catch (error) { p.log.error( `Failed to sync node_modules skills: ${error instanceof Error ? error.message : 'Unknown error'}` @@ -281,7 +281,7 @@ async function installEntries( force: true, // Overwrite existing — we're restoring from lock gitignore, append, - agents, + targets: agents, }); for (const msg of result.messages) { @@ -303,7 +303,7 @@ async function installEntries( promptNames: names, force: true, // Overwrite existing — we're restoring from lock gitignore, - agents, + targets: agents, }); for (const msg of result.messages) { @@ -325,7 +325,7 @@ async function installEntries( agentNames: names, force: true, // Overwrite existing — we're restoring from lock gitignore, - agents, + targets: agents, }); for (const msg of result.messages) { diff --git a/src/rule-add.ts b/src/rule-add.ts index d6914c0..bd88742 100644 --- a/src/rule-add.ts +++ b/src/rule-add.ts @@ -37,7 +37,7 @@ export interface RuleAddOptions { /** Rule names to install. Empty or ['*'] means all rules. */ ruleNames: string[]; /** Target agents to install for. Defaults to all five. */ - agents?: TargetAgent[]; + targets?: TargetAgent[]; /** Preview planned writes without executing them. */ dryRun?: boolean; /** Overwrite collisions instead of aborting. */ @@ -119,7 +119,7 @@ export interface PromptAddOptions { /** Prompt names to install. Empty or ['*'] means all prompts. */ promptNames: string[]; /** Target agents to install for. Defaults to all five. */ - agents?: TargetAgent[]; + targets?: TargetAgent[]; /** Preview planned writes without executing them. */ dryRun?: boolean; /** Overwrite collisions instead of aborting. */ @@ -213,11 +213,11 @@ export async function addRules(options: RuleAddOptions): Promise const { lock } = await readDotaiLock(options.projectRoot); // 5. Run install pipeline - const agents = options.agents ?? [...TARGET_AGENTS]; + const targets = options.targets ?? [...TARGET_AGENTS]; const modelOverrides = await loadModelOverrides(options.projectRoot); const result = await executeInstallPipeline(selectedRules, { projectRoot: options.projectRoot, - agents, + targets, source: options.source, lockEntries: lock.items, force: options.force, @@ -367,11 +367,11 @@ export async function addPrompts(options: PromptAddOptions): Promise { it('respects agent subset filter', () => { const items = [canonicalRule('code-style')]; const opts = baseOptions(tmpDir, { - agents: ['cursor', 'cline'] as const, + targets: ['cursor', 'cline'] as const, }); const { writes } = planRuleWrites(items, opts); @@ -300,7 +300,7 @@ describe('install-pipeline', () => { it('native passthrough produces no output for non-matching agents', () => { const items = [nativeRule('code-style', 'cursor')]; const opts = baseOptions(tmpDir, { - agents: ['windsurf'] as const, + targets: ['windsurf'] as const, }); const { writes, skipped } = planRuleWrites(items, opts); @@ -314,7 +314,7 @@ describe('install-pipeline', () => { it('resolves absolute paths correctly', () => { const items = [canonicalRule('code-style')]; const opts = baseOptions(tmpDir, { - agents: ['cursor'] as const, + targets: ['cursor'] as const, }); const { writes } = planRuleWrites(items, opts); @@ -328,7 +328,7 @@ describe('install-pipeline', () => { it('attaches correct metadata to planned writes', () => { const items = [canonicalRule('code-style')]; const opts = baseOptions(tmpDir, { - agents: ['cursor'] as const, + targets: ['cursor'] as const, source: 'acme/repo', }); @@ -450,7 +450,7 @@ describe('install-pipeline', () => { it('creates target directories that do not exist', async () => { const items = [canonicalRule('code-style')]; - const opts = baseOptions(tmpDir, { agents: ['cursor'] as const }); + const opts = baseOptions(tmpDir, { targets: ['cursor'] as const }); const result = await executeInstallPipeline(items, opts); @@ -460,7 +460,7 @@ describe('install-pipeline', () => { it('written files have correct transpiled content', async () => { const items = [canonicalRule('code-style', { activation: 'always' })]; - const opts = baseOptions(tmpDir, { agents: ['cursor'] as const }); + const opts = baseOptions(tmpDir, { targets: ['cursor'] as const }); const result = await executeInstallPipeline(items, opts); @@ -474,7 +474,7 @@ describe('install-pipeline', () => { it('writes multiple rules in one pass', async () => { const items = [canonicalRule('code-style'), canonicalRule('security')]; - const opts = baseOptions(tmpDir, { agents: ['cursor'] as const }); + const opts = baseOptions(tmpDir, { targets: ['cursor'] as const }); const result = await executeInstallPipeline(items, opts); @@ -580,7 +580,7 @@ describe('install-pipeline', () => { const items = [canonicalRule('code-style')]; const opts = baseOptions(tmpDir, { lockEntries: [existingEntry], - agents: ['cursor'] as const, + targets: ['cursor'] as const, }); const result = await executeInstallPipeline(items, opts); @@ -625,7 +625,7 @@ describe('install-pipeline', () => { it('installs only to specified agents', async () => { const items = [canonicalRule('code-style')]; const opts = baseOptions(tmpDir, { - agents: ['cursor', 'cline'] as const, + targets: ['cursor', 'cline'] as const, }); const result = await executeInstallPipeline(items, opts); @@ -645,7 +645,7 @@ describe('install-pipeline', () => { it('single agent install', async () => { const items = [canonicalRule('code-style')]; const opts = baseOptions(tmpDir, { - agents: ['github-copilot'] as const, + targets: ['github-copilot'] as const, }); const result = await executeInstallPipeline(items, opts); @@ -688,7 +688,7 @@ describe('install-pipeline', () => { skillItem('db-migrate'), nativeRule('lint', 'windsurf'), ]; - const opts = baseOptions(tmpDir, { agents: ['cursor', 'windsurf'] as const }); + const opts = baseOptions(tmpDir, { targets: ['cursor', 'windsurf'] as const }); const result = await executeInstallPipeline(items, opts); @@ -724,7 +724,7 @@ describe('install-pipeline', () => { it('respects agent subset filter for prompts', () => { const items = [canonicalPrompt('review-code')]; const opts = baseOptions(tmpDir, { - agents: ['github-copilot'] as const, + targets: ['github-copilot'] as const, }); const { writes } = planRuleWrites(items, opts); @@ -736,7 +736,7 @@ describe('install-pipeline', () => { it('produces no output for agents that do not support prompts', () => { const items = [canonicalPrompt('review-code')]; const opts = baseOptions(tmpDir, { - agents: ['cursor', 'cline'] as const, + targets: ['cursor', 'cline'] as const, }); const { writes, skipped } = planRuleWrites(items, opts); @@ -749,7 +749,7 @@ describe('install-pipeline', () => { it('resolves absolute paths correctly for prompts', () => { const items = [canonicalPrompt('review-code')]; const opts = baseOptions(tmpDir, { - agents: ['github-copilot'] as const, + targets: ['github-copilot'] as const, }); const { writes } = planRuleWrites(items, opts); @@ -763,7 +763,7 @@ describe('install-pipeline', () => { it('attaches correct metadata to prompt planned writes', () => { const items = [canonicalPrompt('review-code')]; const opts = baseOptions(tmpDir, { - agents: ['github-copilot'] as const, + targets: ['github-copilot'] as const, source: 'acme/repo', }); @@ -847,7 +847,7 @@ describe('install-pipeline', () => { it('written Copilot prompt has correct content', async () => { const items = [canonicalPrompt('review-code', { agent: 'plan' })]; - const opts = baseOptions(tmpDir, { agents: ['github-copilot'] as const }); + const opts = baseOptions(tmpDir, { targets: ['github-copilot'] as const }); const result = await executeInstallPipeline(items, opts); @@ -863,7 +863,7 @@ describe('install-pipeline', () => { it('written Claude Code prompt has correct content', async () => { const items = [canonicalPrompt('review-code')]; - const opts = baseOptions(tmpDir, { agents: ['claude-code'] as const }); + const opts = baseOptions(tmpDir, { targets: ['claude-code'] as const }); const result = await executeInstallPipeline(items, opts); @@ -899,7 +899,7 @@ describe('install-pipeline', () => { it('handles mixed rules + prompts in a single pipeline execution', async () => { const items = [canonicalRule('code-style'), canonicalPrompt('review-code')]; - const opts = baseOptions(tmpDir, { agents: ['github-copilot', 'claude-code'] as const }); + const opts = baseOptions(tmpDir, { targets: ['github-copilot', 'claude-code'] as const }); const result = await executeInstallPipeline(items, opts); @@ -943,7 +943,7 @@ describe('install-pipeline', () => { it('respects agent subset filter for agents', () => { const items = [canonicalAgent('architect')]; const opts = baseOptions(tmpDir, { - agents: ['github-copilot'] as const, + targets: ['github-copilot'] as const, }); const { writes } = planRuleWrites(items, opts); @@ -955,7 +955,7 @@ describe('install-pipeline', () => { it('produces no output for agents that do not support agent transpilation', () => { const items = [canonicalAgent('architect')]; const opts = baseOptions(tmpDir, { - agents: ['cursor', 'cline'] as const, + targets: ['cursor', 'cline'] as const, }); const { writes, skipped } = planRuleWrites(items, opts); @@ -968,7 +968,7 @@ describe('install-pipeline', () => { it('resolves absolute paths correctly for agents', () => { const items = [canonicalAgent('architect')]; const opts = baseOptions(tmpDir, { - agents: ['github-copilot'] as const, + targets: ['github-copilot'] as const, }); const { writes } = planRuleWrites(items, opts); @@ -982,7 +982,7 @@ describe('install-pipeline', () => { it('attaches correct metadata to agent planned writes', () => { const items = [canonicalAgent('architect')]; const opts = baseOptions(tmpDir, { - agents: ['github-copilot'] as const, + targets: ['github-copilot'] as const, source: 'acme/repo', }); @@ -1070,7 +1070,7 @@ describe('install-pipeline', () => { const items = [ canonicalAgent('architect', { model: 'claude-sonnet-4', tools: ['Read', 'Grep'] }), ]; - const opts = baseOptions(tmpDir, { agents: ['github-copilot'] as const }); + const opts = baseOptions(tmpDir, { targets: ['github-copilot'] as const }); const result = await executeInstallPipeline(items, opts); @@ -1096,7 +1096,7 @@ describe('install-pipeline', () => { background: false, }), ]; - const opts = baseOptions(tmpDir, { agents: ['claude-code'] as const }); + const opts = baseOptions(tmpDir, { targets: ['claude-code'] as const }); const result = await executeInstallPipeline(items, opts); @@ -1145,7 +1145,7 @@ describe('install-pipeline', () => { canonicalPrompt('review-code'), canonicalAgent('architect'), ]; - const opts = baseOptions(tmpDir, { agents: ['github-copilot', 'claude-code'] as const }); + const opts = baseOptions(tmpDir, { targets: ['github-copilot', 'claude-code'] as const }); const result = await executeInstallPipeline(items, opts); @@ -1203,7 +1203,7 @@ describe('install-pipeline', () => { const items = [canonicalRule('code-style')]; const opts = baseOptions(tmpDir, { append: true, - agents: ['github-copilot'] as const, + targets: ['github-copilot'] as const, }); const { writes } = planRuleWrites(items, opts); @@ -1246,7 +1246,7 @@ describe('install-pipeline', () => { const items = [canonicalRule('code-style')]; const opts = baseOptions(tmpDir, { append: true, - agents: ['github-copilot', 'claude-code'] as const, + targets: ['github-copilot', 'claude-code'] as const, }); const result = await executeInstallPipeline(items, opts); @@ -1270,7 +1270,7 @@ describe('install-pipeline', () => { const items = [canonicalRule('code-style')]; const opts = baseOptions(tmpDir, { append: true, - agents: ['github-copilot'] as const, + targets: ['github-copilot'] as const, force: true, }); @@ -1287,7 +1287,7 @@ describe('install-pipeline', () => { const items = [canonicalRule('code-style'), canonicalRule('security')]; const opts = baseOptions(tmpDir, { append: true, - agents: ['github-copilot'] as const, + targets: ['github-copilot'] as const, }); const result = await executeInstallPipeline(items, opts); @@ -1305,7 +1305,7 @@ describe('install-pipeline', () => { const items1 = [canonicalRule('code-style')]; const opts = baseOptions(tmpDir, { append: true, - agents: ['github-copilot'] as const, + targets: ['github-copilot'] as const, }); await executeInstallPipeline(items1, opts); @@ -1325,7 +1325,7 @@ describe('install-pipeline', () => { const items = [canonicalRule('code-style')]; const opts = baseOptions(tmpDir, { append: true, - agents: ['github-copilot'] as const, + targets: ['github-copilot'] as const, dryRun: true, }); diff --git a/src/rule-installer.ts b/src/rule-installer.ts index fa7640d..c861b72 100644 --- a/src/rule-installer.ts +++ b/src/rule-installer.ts @@ -37,7 +37,7 @@ export interface InstallPipelineOptions { /** Absolute path to the project root directory. */ projectRoot: string; /** Target agents to install for. Defaults to all five. */ - agents?: readonly TargetAgent[]; + targets?: readonly TargetAgent[]; /** Source identifier for lock/collision tracking (e.g., "owner/repo"). */ source: string; /** Existing lock entries for collision detection. */ @@ -103,7 +103,7 @@ export function planRuleWrites( items: DiscoveredItem[], options: InstallPipelineOptions ): { writes: PipelineWrite[]; skipped: Array<{ item: DiscoveredItem; reason: string }> } { - const agents = options.agents ?? ALL_AGENTS; + const agents = options.targets ?? ALL_AGENTS; const writes: PipelineWrite[] = []; const skipped: Array<{ item: DiscoveredItem; reason: string }> = []; diff --git a/src/sync.test.ts b/src/sync.test.ts index bfd5ced..07ae25e 100644 --- a/src/sync.test.ts +++ b/src/sync.test.ts @@ -41,14 +41,14 @@ describe('parseSyncOptions', () => { expect(options.force).toBe(true); }); - it('should parse --agent flag', () => { - const { options } = parseSyncOptions(['--agents', 'claude-code']); - expect(options.agents).toEqual(['claude-code']); + it('should parse --target flag', () => { + const { options } = parseSyncOptions(['--targets', 'claude-code']); + expect(options.targets).toEqual(['claude-code']); }); - it('should parse multiple agent values', () => { + it('should parse multiple target values', () => { const { options } = parseSyncOptions(['-a', 'claude-code', 'cursor']); - expect(options.agents).toEqual(['claude-code', 'cursor']); + expect(options.targets).toEqual(['claude-code', 'cursor']); }); it('should return empty options for no args', () => { @@ -60,7 +60,7 @@ describe('parseSyncOptions', () => { const { options } = parseSyncOptions(['-y', '-f', '-a', 'claude-code']); expect(options.yes).toBe(true); expect(options.force).toBe(true); - expect(options.agents).toEqual(['claude-code']); + expect(options.targets).toEqual(['claude-code']); }); }); @@ -104,11 +104,11 @@ Content here. it('should throw CommandError with exit code 1 for invalid agent', async () => { await expect( - runSync([], { agents: ['not-a-real-agent'], yes: true, force: true }) + runSync([], { targets: ['not-a-real-agent'], yes: true, force: true }) ).rejects.toThrow(CommandError); try { - await runSync([], { agents: ['not-a-real-agent'], yes: true, force: true }); + await runSync([], { targets: ['not-a-real-agent'], yes: true, force: true }); } catch (error) { expect(error).toBeInstanceOf(CommandError); expect((error as CommandError).exitCode).toBe(1); @@ -118,7 +118,7 @@ Content here. it('should not throw for valid agents', async () => { // With a valid agent and --yes, it should complete without throwing await expect( - runSync([], { agents: ['claude-code'], yes: true, force: true }) + runSync([], { targets: ['claude-code'], yes: true, force: true }) ).resolves.toBeUndefined(); }); }); diff --git a/src/sync.ts b/src/sync.ts index eea04a3..c468486 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -21,7 +21,7 @@ import { consumeMultiValues } from './cli-parse.ts'; const isCancelled = (value: unknown): value is symbol => typeof value === 'symbol'; export interface SyncOptions { - agents?: string[]; + targets?: string[]; yes?: boolean; force?: boolean; } @@ -189,22 +189,22 @@ export async function runSync(args: string[], options: SyncOptions = {}): Promis const validAgents = Object.keys(agents); const universalAgents = getUniversalAgents(); - if (options.agents?.includes('*')) { + if (options.targets?.includes('*')) { targetAgents = validAgents as AgentType[]; - p.log.info(`Installing to all ${targetAgents.length} agents`); - } else if (options.agents && options.agents.length > 0) { - const invalidAgents = options.agents.filter((a) => !validAgents.includes(a)); + p.log.info(`Installing to all ${targetAgents.length} targets`); + } else if (options.targets && options.targets.length > 0) { + const invalidAgents = options.targets.filter((a) => !validAgents.includes(a)); if (invalidAgents.length > 0) { - p.log.error(`Invalid agents: ${invalidAgents.join(', ')}`); - p.log.info(`Valid agents: ${validAgents.join(', ')}`); + p.log.error(`Invalid targets: ${invalidAgents.join(', ')}`); + p.log.info(`Valid targets: ${validAgents.join(', ')}`); throw new CommandError(1); } - targetAgents = options.agents as AgentType[]; + targetAgents = options.targets as AgentType[]; } else { - spinner.start('Loading agents...'); + spinner.start('Loading targets...'); const installedAgents = await detectInstalledAgents(); const totalAgents = Object.keys(agents).length; - spinner.stop(`${totalAgents} agents`); + spinner.stop(`${totalAgents} targets`); if (installedAgents.length === 0) { if (options.yes) { @@ -418,10 +418,10 @@ export function parseSyncOptions(args: string[]): { options: SyncOptions } { options.yes = true; } else if (arg === '-f' || arg === '--force') { options.force = true; - } else if (arg === '-a' || arg === '--agents') { - options.agents = options.agents || []; + } else if (arg === '-a' || arg === '--targets') { + options.targets = options.targets || []; const { values, nextIndex } = consumeMultiValues(args, i + 1); - options.agents.push(...values); + options.targets.push(...values); i = nextIndex - 1; } } diff --git a/tests/cli-subprocess.test.ts b/tests/cli-subprocess.test.ts index 0d32386..e3d0f79 100644 --- a/tests/cli-subprocess.test.ts +++ b/tests/cli-subprocess.test.ts @@ -82,7 +82,7 @@ describe('CLI --rule subprocess tests', () => { ]); const result = runCli( - ['add', sourceRepo, '--rule', 'code-style', '--agents', 'cursor,cline', '-y'], + ['add', sourceRepo, '--rule', 'code-style', '--targets', 'cursor,cline', '-y'], projectDir ); @@ -224,7 +224,7 @@ describe('CLI --custom-agent subprocess tests', () => { ); const result = runCli( - ['add', sourceRepo, '--custom-agent', 'architect', '--agents', 'copilot', '-y'], + ['add', sourceRepo, '--custom-agent', 'architect', '--targets', 'copilot', '-y'], projectDir ); @@ -344,7 +344,7 @@ Instructions here. ); const result = runCli( - ['add', sourceRepo, '--dry-run', '-y', '--agents', 'claude-code'], + ['add', sourceRepo, '--dry-run', '-y', '--targets', 'claude-code'], projectDir ); diff --git a/tests/e2e-canonical-install.test.ts b/tests/e2e-canonical-install.test.ts index 2641ad2..552beff 100644 --- a/tests/e2e-canonical-install.test.ts +++ b/tests/e2e-canonical-install.test.ts @@ -202,7 +202,7 @@ describe('E2E canonical install', () => { sourcePath: sourceRepo, projectRoot, ruleNames: ['*'], - agents: ['cursor', 'cline'], + targets: ['cursor', 'cline'], }); expect(result.success).toBe(true); diff --git a/tests/gitignore-integration.test.ts b/tests/gitignore-integration.test.ts index 5cd349b..57c53a5 100644 --- a/tests/gitignore-integration.test.ts +++ b/tests/gitignore-integration.test.ts @@ -181,7 +181,7 @@ describe('addRules --gitignore integration', () => { sourcePath: sourceRepo, projectRoot: projectDir, ruleNames: ['*'], - agents: ['cursor', 'cline'], + targets: ['cursor', 'cline'], gitignore: true, }); diff --git a/tests/install-integration.test.ts b/tests/install-integration.test.ts index f452863..b1609cd 100644 --- a/tests/install-integration.test.ts +++ b/tests/install-integration.test.ts @@ -186,7 +186,7 @@ describe('integration: discover → transpile → install', () => { projectRoot: projectDir, source: 'test/repo', lockEntries: [], - agents: ['cursor'] as const, + targets: ['cursor'] as const, }); expect(result.success).toBe(true); @@ -235,7 +235,7 @@ describe('integration: discover → transpile → install', () => { projectRoot: projectDir, source: 'test/repo', lockEntries: [], - agents: ['cursor'] as const, + targets: ['cursor'] as const, }); expect(result.success).toBe(true); @@ -297,7 +297,7 @@ describe('integration: discover → transpile → install', () => { projectRoot: projectDir, source: 'test/repo', lockEntries: [], - agents: ['windsurf'] as const, + targets: ['windsurf'] as const, }); expect(result.success).toBe(true); @@ -400,7 +400,7 @@ describe('integration: discover → transpile → install', () => { projectRoot: projectDir, source: 'test/repo', lockEntries: [], - agents: ['cursor', 'cline'] as const, + targets: ['cursor', 'cline'] as const, }); expect(result.success).toBe(true); @@ -535,7 +535,7 @@ describe('integration: discover → transpile → install', () => { projectRoot: projectDir, source: 'test/repo', lockEntries: [existingEntry], - agents: ['cursor'] as const, + targets: ['cursor'] as const, }); expect(result.success).toBe(true); @@ -626,7 +626,7 @@ describe('integration: discover → transpile → install', () => { projectRoot: projectDir, source: 'test/repo', lockEntries: [], - agents: ['cursor'] as const, + targets: ['cursor'] as const, }); expect(result.success).toBe(true); @@ -660,7 +660,7 @@ describe('integration: discover → transpile → install', () => { projectRoot: projectDir, source: 'test/repo', lockEntries: [], - agents: ['cursor'] as const, + targets: ['cursor'] as const, }); // One of them should collide @@ -682,7 +682,7 @@ describe('integration: discover → transpile → install', () => { projectRoot: projectDir, source: 'test/repo', lockEntries: [], - agents: ['cursor'] as const, + targets: ['cursor'] as const, }); expect(result1.success).toBe(true); @@ -711,7 +711,7 @@ describe('integration: discover → transpile → install', () => { projectRoot: projectDir, source: 'test/repo', lockEntries: [lockEntry], - agents: ['cursor'] as const, + targets: ['cursor'] as const, }); expect(result2.success).toBe(true); expect(result2.collisions).toHaveLength(0); diff --git a/tests/lock-integration.test.ts b/tests/lock-integration.test.ts index 8b9d05d..a2d4aba 100644 --- a/tests/lock-integration.test.ts +++ b/tests/lock-integration.test.ts @@ -118,7 +118,7 @@ describe('addRules → lock file integration', () => { sourcePath: sourceRepo, projectRoot: projectDir, ruleNames: ['*'], - agents: ['cursor', 'cline'], + targets: ['cursor', 'cline'], }); const lock = await readLockFileFromDisk(projectDir); diff --git a/tests/remove-canonical.test.ts b/tests/remove-canonical.test.ts index 7b57e4f..3329987 100644 --- a/tests/remove-canonical.test.ts +++ b/tests/remove-canonical.test.ts @@ -68,7 +68,7 @@ describe('removeCommand canonical protection', () => { // 3. Remove from Claude only // -a claude-code - await removeCommand([skillName], { agents: ['claude-code'], yes: true }); + await removeCommand([skillName], { targets: ['claude-code'], yes: true }); // 4. Verify results // Claude path should be gone @@ -96,7 +96,7 @@ describe('removeCommand canonical protection', () => { vi.mocked(agentsModule.detectInstalledAgents).mockResolvedValue(['claude-code']); // Remove from Claude - await removeCommand([skillName], { agents: ['claude-code'], yes: true }); + await removeCommand([skillName], { targets: ['claude-code'], yes: true }); // Both should be gone await expect(lstat(claudePath)).rejects.toThrow(); diff --git a/tests/restore.test.ts b/tests/restore.test.ts index b409e24..4929236 100644 --- a/tests/restore.test.ts +++ b/tests/restore.test.ts @@ -463,7 +463,7 @@ describe('dotai restore — restore rules and prompts from .dotai-lock.json', () sourcePath: sourceRepo, projectRoot: projectDir, ruleNames: ['*'], - agents: ['cursor'], + targets: ['cursor'], }); expect(addResult.success).toBe(true);