@swarmify/agents-cli 1.3.12 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -121
- package/dist/index.js +195 -27
- package/dist/index.js.map +1 -1
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/lib/agents.js +7 -2
- package/dist/lib/agents.js.map +1 -1
- package/dist/lib/commands.d.ts +4 -0
- package/dist/lib/commands.d.ts.map +1 -1
- package/dist/lib/commands.js +9 -0
- package/dist/lib/commands.js.map +1 -1
- package/dist/lib/hooks.d.ts +4 -0
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/hooks.js +19 -0
- package/dist/lib/hooks.js.map +1 -1
- package/dist/lib/skills.d.ts +4 -0
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +7 -0
- package/dist/lib/skills.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
# @swarmify/agents-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Your virtual environment manager for AI coding agents.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install -g @swarmify/agents-cli
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
## The
|
|
9
|
+
## The Problem
|
|
10
10
|
|
|
11
|
-
You
|
|
11
|
+
You spend hours configuring Claude Code: MCP servers, slash commands, hooks, skills. Then you switch to Codex or Gemini and start from scratch. Or you get a new machine and lose everything.
|
|
12
|
+
|
|
13
|
+
## The Solution
|
|
14
|
+
|
|
15
|
+
Keep your agent configuration in a git repo. Pull it anywhere.
|
|
12
16
|
|
|
13
17
|
```bash
|
|
14
|
-
#
|
|
18
|
+
# New machine? One command.
|
|
15
19
|
agents pull gh:username/.agents
|
|
16
20
|
|
|
17
|
-
# See what
|
|
21
|
+
# See what's installed
|
|
18
22
|
agents status claude
|
|
19
23
|
```
|
|
20
24
|
|
|
@@ -40,145 +44,166 @@ Installed MCP Servers
|
|
|
40
44
|
User: Swarm@latest, GoDaddy
|
|
41
45
|
```
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
Your `.agents` repo becomes the source of truth for all your AI coding tools.
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|----------|----------|-----------|
|
|
47
|
-
| Slash commands | `~/.claude/commands/` | Claude, Codex, Gemini |
|
|
48
|
-
| MCP servers | `~/.claude/settings.json` | Claude, Codex, Gemini |
|
|
49
|
-
| Hooks | `~/.claude/hooks.json` | Claude, Gemini |
|
|
50
|
-
| Agent Skills | `~/.claude/skills/` | Claude, Codex, Gemini |
|
|
51
|
-
| CLI versions | npm/brew | All agents |
|
|
49
|
+
## What Gets Synced
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
| Resource | Description | Agents |
|
|
52
|
+
|----------|-------------|--------|
|
|
53
|
+
| Slash commands | `/debug`, `/plan`, custom prompts | Claude, Codex, Gemini, Cursor, OpenCode |
|
|
54
|
+
| MCP servers | Tools your agents can use | Claude, Codex, Gemini |
|
|
55
|
+
| Hooks | Pre/post execution scripts | Claude, Gemini |
|
|
56
|
+
| Skills | Reusable agent capabilities | Claude, Codex, Gemini |
|
|
57
|
+
| CLI versions | Which version of each agent | All |
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
## Quick Start
|
|
56
60
|
|
|
57
61
|
```bash
|
|
58
|
-
#
|
|
59
|
-
|
|
62
|
+
# 1. Install
|
|
63
|
+
npm install -g @swarmify/agents-cli
|
|
60
64
|
|
|
61
|
-
#
|
|
62
|
-
agents
|
|
65
|
+
# 2. Set your config repo
|
|
66
|
+
agents repo add gh:username/.agents
|
|
63
67
|
|
|
64
|
-
#
|
|
65
|
-
agents
|
|
68
|
+
# 3. Pull everything
|
|
69
|
+
agents pull
|
|
70
|
+
|
|
71
|
+
# 4. Check what's installed
|
|
72
|
+
agents status
|
|
66
73
|
```
|
|
67
74
|
|
|
68
|
-
|
|
75
|
+
## Your .agents Repo
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
.agents/
|
|
79
|
+
agents.yaml # CLI versions, MCP servers, defaults
|
|
80
|
+
shared/commands/ # Slash commands for all agents
|
|
81
|
+
claude/commands/ # Claude-specific commands
|
|
82
|
+
claude/hooks/ # Claude hooks
|
|
83
|
+
codex/prompts/ # Codex-specific prompts
|
|
84
|
+
gemini/commands/ # Gemini commands (auto-converted to TOML)
|
|
85
|
+
skills/ # Agent Skills (SKILL.md + rules/)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Example `agents.yaml`:
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
clis:
|
|
92
|
+
claude:
|
|
93
|
+
package: "@anthropic-ai/claude-code"
|
|
94
|
+
version: "latest"
|
|
95
|
+
codex:
|
|
96
|
+
package: "@openai/codex"
|
|
97
|
+
version: "latest"
|
|
98
|
+
|
|
99
|
+
mcp:
|
|
100
|
+
filesystem:
|
|
101
|
+
command: "npx -y @anthropic-ai/mcp-filesystem"
|
|
102
|
+
transport: stdio
|
|
103
|
+
scope: user
|
|
104
|
+
agents: [claude, codex, gemini]
|
|
105
|
+
|
|
106
|
+
memory:
|
|
107
|
+
command: "npx -y @anthropic-ai/mcp-memory"
|
|
108
|
+
transport: stdio
|
|
109
|
+
scope: user
|
|
110
|
+
agents: [claude, codex, gemini]
|
|
111
|
+
|
|
112
|
+
defaults:
|
|
113
|
+
method: symlink
|
|
114
|
+
scope: user
|
|
115
|
+
agents: [claude, codex, gemini]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Commands
|
|
119
|
+
|
|
120
|
+
### Status
|
|
69
121
|
|
|
70
122
|
```bash
|
|
71
|
-
#
|
|
72
|
-
agents
|
|
123
|
+
agents status # Full overview
|
|
124
|
+
agents status --agent claude
|
|
125
|
+
```
|
|
73
126
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
127
|
+
### Pull & Push
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
agents pull # Sync from your repo
|
|
131
|
+
agents pull --dry-run # Preview changes
|
|
132
|
+
agents push # Push local changes back
|
|
133
|
+
```
|
|
77
134
|
|
|
78
|
-
|
|
79
|
-
agents mcp add api https://api.example.com/mcp --transport http -H "Authorization:Bearer token"
|
|
135
|
+
### Slash Commands
|
|
80
136
|
|
|
81
|
-
|
|
82
|
-
agents
|
|
137
|
+
```bash
|
|
138
|
+
agents commands list
|
|
139
|
+
agents commands add gh:user/my-commands
|
|
140
|
+
agents commands remove my-command
|
|
141
|
+
agents commands push my-command # Promote project -> user scope
|
|
83
142
|
```
|
|
84
143
|
|
|
85
|
-
###
|
|
144
|
+
### MCP Servers
|
|
86
145
|
|
|
87
146
|
```bash
|
|
88
|
-
# List
|
|
89
|
-
agents
|
|
147
|
+
# List across all agents
|
|
148
|
+
agents mcp list
|
|
90
149
|
|
|
91
|
-
#
|
|
92
|
-
agents
|
|
150
|
+
# Add (use -- before the command)
|
|
151
|
+
agents mcp add memory -- npx -y @anthropic-ai/mcp-memory
|
|
152
|
+
agents mcp add api https://api.example.com --transport http
|
|
153
|
+
|
|
154
|
+
# Search registries
|
|
155
|
+
agents search filesystem
|
|
156
|
+
agents add mcp:@anthropic-ai/mcp-filesystem
|
|
93
157
|
|
|
94
158
|
# Remove
|
|
95
|
-
agents
|
|
159
|
+
agents mcp remove memory
|
|
96
160
|
```
|
|
97
161
|
|
|
98
162
|
### Skills
|
|
99
163
|
|
|
100
|
-
Agent Skills are reusable capabilities (SKILL.md + rules/) that agents can invoke.
|
|
101
|
-
|
|
102
164
|
```bash
|
|
103
|
-
# List installed skills
|
|
104
165
|
agents skills list
|
|
105
|
-
|
|
106
|
-
# Install from repo
|
|
107
166
|
agents skills add gh:user/my-skills
|
|
108
|
-
|
|
109
|
-
# Show details
|
|
110
167
|
agents skills info my-skill
|
|
111
168
|
```
|
|
112
169
|
|
|
113
|
-
###
|
|
114
|
-
|
|
115
|
-
Search public registries for MCP servers and skills:
|
|
170
|
+
### Hooks
|
|
116
171
|
|
|
117
172
|
```bash
|
|
118
|
-
|
|
119
|
-
agents
|
|
120
|
-
agents
|
|
121
|
-
|
|
122
|
-
# Filter by type
|
|
123
|
-
agents search github --type mcp
|
|
124
|
-
|
|
125
|
-
# Install from registry (auto-detected)
|
|
126
|
-
agents add mcp:io.github.bytedance/mcp-server-filesystem
|
|
127
|
-
|
|
128
|
-
# Or use identifiers
|
|
129
|
-
agents add skill:user/my-skill # Skill from git
|
|
130
|
-
agents add gh:user/my-repo # Git repo directly
|
|
173
|
+
agents hooks list
|
|
174
|
+
agents hooks add gh:user/my-hooks
|
|
175
|
+
agents hooks remove my-hook
|
|
131
176
|
```
|
|
132
177
|
|
|
133
|
-
###
|
|
178
|
+
### CLI Management
|
|
134
179
|
|
|
135
180
|
```bash
|
|
136
|
-
#
|
|
137
|
-
agents
|
|
138
|
-
|
|
139
|
-
#
|
|
140
|
-
agents registry add mcp myregistry https://api.example.com/v1
|
|
141
|
-
|
|
142
|
-
# Configure API key
|
|
143
|
-
agents registry config mcp myregistry --api-key YOUR_KEY
|
|
144
|
-
|
|
145
|
-
# Disable/enable
|
|
146
|
-
agents registry disable mcp myregistry
|
|
147
|
-
agents registry enable mcp myregistry
|
|
148
|
-
|
|
149
|
-
# Remove
|
|
150
|
-
agents registry remove mcp myregistry
|
|
181
|
+
agents cli list # Show installed versions
|
|
182
|
+
agents cli add claude # Install agent CLI
|
|
183
|
+
agents cli remove codex # Uninstall agent CLI
|
|
184
|
+
agents cli upgrade # Upgrade all to latest
|
|
151
185
|
```
|
|
152
186
|
|
|
153
|
-
|
|
154
|
-
- `official` (MCP): https://registry.modelcontextprotocol.io
|
|
155
|
-
|
|
156
|
-
### Sync
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
# Pull config from your .agents repo
|
|
160
|
-
agents pull gh:username/.agents
|
|
187
|
+
## Scopes
|
|
161
188
|
|
|
162
|
-
|
|
163
|
-
agents push
|
|
189
|
+
Resources can exist at two levels:
|
|
164
190
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
191
|
+
| Scope | Location | Use |
|
|
192
|
+
|-------|----------|-----|
|
|
193
|
+
| User | `~/.{agent}/` | Available everywhere |
|
|
194
|
+
| Project | `./.{agent}/` | This repo only, committed |
|
|
168
195
|
|
|
169
|
-
|
|
196
|
+
Promote project-scoped items to user scope:
|
|
170
197
|
|
|
171
198
|
```bash
|
|
172
|
-
|
|
173
|
-
agents
|
|
174
|
-
|
|
175
|
-
# Upgrade all to latest
|
|
176
|
-
agents cli upgrade --latest
|
|
199
|
+
agents commands push my-command
|
|
200
|
+
agents mcp push my-server
|
|
201
|
+
agents skills push my-skill
|
|
177
202
|
```
|
|
178
203
|
|
|
179
204
|
## Filtering
|
|
180
205
|
|
|
181
|
-
All commands support
|
|
206
|
+
All list commands support filters:
|
|
182
207
|
|
|
183
208
|
```bash
|
|
184
209
|
agents commands list --agent claude
|
|
@@ -186,44 +211,38 @@ agents mcp list --scope project
|
|
|
186
211
|
agents skills list --agent codex --scope user
|
|
187
212
|
```
|
|
188
213
|
|
|
189
|
-
##
|
|
214
|
+
## Registries
|
|
190
215
|
|
|
191
|
-
|
|
192
|
-
|-------|----------|----------|
|
|
193
|
-
| User | `~/.{agent}/` | Available everywhere |
|
|
194
|
-
| Project | `./.{agent}/` | This repo only |
|
|
195
|
-
|
|
196
|
-
Promote project-scoped items to user scope:
|
|
216
|
+
Search and install from public registries:
|
|
197
217
|
|
|
198
218
|
```bash
|
|
199
|
-
|
|
200
|
-
agents
|
|
219
|
+
# Search
|
|
220
|
+
agents search github --type mcp
|
|
221
|
+
|
|
222
|
+
# Install from registry
|
|
223
|
+
agents add mcp:@anthropic-ai/mcp-filesystem
|
|
224
|
+
|
|
225
|
+
# Manage registries
|
|
226
|
+
agents registry list
|
|
227
|
+
agents registry add mcp myregistry https://api.example.com
|
|
228
|
+
agents registry config mcp myregistry --api-key KEY
|
|
201
229
|
```
|
|
202
230
|
|
|
203
231
|
## Supported Agents
|
|
204
232
|
|
|
205
|
-
| Agent |
|
|
206
|
-
|
|
233
|
+
| Agent | Commands | MCP | Hooks | Skills |
|
|
234
|
+
|-------|----------|-----|-------|--------|
|
|
207
235
|
| Claude Code | Yes | Yes | Yes | Yes |
|
|
208
236
|
| Codex | Yes | Yes | - | Yes |
|
|
209
|
-
| Gemini CLI | Yes
|
|
210
|
-
| Cursor | Yes |
|
|
211
|
-
| OpenCode | Yes |
|
|
237
|
+
| Gemini CLI | Yes | Yes | Yes | Yes |
|
|
238
|
+
| Cursor | Yes | Yes | - | - |
|
|
239
|
+
| OpenCode | Yes | Yes | - | - |
|
|
212
240
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
```
|
|
216
|
-
.agents/
|
|
217
|
-
agents.yaml # CLIs, MCP servers, defaults
|
|
218
|
-
shared/commands/ # Prompts for all agents
|
|
219
|
-
claude/commands/ # Claude-specific prompts
|
|
220
|
-
claude/hooks/ # Claude hooks
|
|
221
|
-
skills/ # Agent Skills
|
|
222
|
-
```
|
|
241
|
+
Format conversion is automatic. Write commands in markdown, they're converted to TOML for Gemini.
|
|
223
242
|
|
|
224
243
|
## Related
|
|
225
244
|
|
|
226
|
-
- [@swarmify/agents-mcp](https://www.npmjs.com/package/@swarmify/agents-mcp) -
|
|
245
|
+
- [@swarmify/agents-mcp](https://www.npmjs.com/package/@swarmify/agents-mcp) - MCP server for multi-agent orchestration
|
|
227
246
|
|
|
228
247
|
## License
|
|
229
248
|
|
package/dist/index.js
CHANGED
|
@@ -3,14 +3,14 @@ import { Command } from 'commander';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import { checkbox, confirm, select } from '@inquirer/prompts';
|
|
6
|
-
import { AGENTS, ALL_AGENT_IDS, MCP_CAPABLE_AGENTS, SKILLS_CAPABLE_AGENTS, HOOKS_CAPABLE_AGENTS, getAllCliStates, isCliInstalled, isMcpRegistered, registerMcp, unregisterMcp, listInstalledMcpsWithScope, promoteMcpToUser, } from './lib/agents.js';
|
|
6
|
+
import { AGENTS, ALL_AGENT_IDS, MCP_CAPABLE_AGENTS, SKILLS_CAPABLE_AGENTS, HOOKS_CAPABLE_AGENTS, getAllCliStates, isCliInstalled, getCliVersion, isMcpRegistered, registerMcp, unregisterMcp, listInstalledMcpsWithScope, promoteMcpToUser, } from './lib/agents.js';
|
|
7
7
|
import { readManifest, writeManifest, createDefaultManifest, MANIFEST_FILENAME, } from './lib/manifest.js';
|
|
8
8
|
import { readState, ensureAgentsDir, getRepoLocalPath, getScope, setScope, removeScope, getScopesByPriority, getScopePriority, } from './lib/state.js';
|
|
9
9
|
import { SCOPE_PRIORITIES, DEFAULT_SYSTEM_REPO } from './lib/types.js';
|
|
10
10
|
import { cloneRepo, parseSource } from './lib/git.js';
|
|
11
|
-
import { discoverCommands, resolveCommandSource, installCommand, uninstallCommand, listInstalledCommandsWithScope, promoteCommandToUser, } from './lib/commands.js';
|
|
12
|
-
import { discoverHooksFromRepo, installHooks, listInstalledHooksWithScope, promoteHookToUser, removeHook, } from './lib/hooks.js';
|
|
13
|
-
import { discoverSkillsFromRepo, installSkill, uninstallSkill, listInstalledSkillsWithScope, promoteSkillToUser, getSkillInfo, getSkillRules, } from './lib/skills.js';
|
|
11
|
+
import { discoverCommands, resolveCommandSource, installCommand, uninstallCommand, listInstalledCommandsWithScope, promoteCommandToUser, commandExists, } from './lib/commands.js';
|
|
12
|
+
import { discoverHooksFromRepo, installHooks, listInstalledHooksWithScope, promoteHookToUser, removeHook, hookExists, } from './lib/hooks.js';
|
|
13
|
+
import { discoverSkillsFromRepo, installSkill, uninstallSkill, listInstalledSkillsWithScope, promoteSkillToUser, getSkillInfo, getSkillRules, skillExists, } from './lib/skills.js';
|
|
14
14
|
import { DEFAULT_REGISTRIES } from './lib/types.js';
|
|
15
15
|
import { search as searchRegistries, getRegistries, setRegistry, removeRegistry, resolvePackage, } from './lib/registry.js';
|
|
16
16
|
const program = new Command();
|
|
@@ -195,17 +195,14 @@ program
|
|
|
195
195
|
}
|
|
196
196
|
console.log();
|
|
197
197
|
});
|
|
198
|
-
// =============================================================================
|
|
199
|
-
// PULL COMMAND
|
|
200
|
-
// =============================================================================
|
|
201
198
|
program
|
|
202
199
|
.command('pull [source]')
|
|
203
200
|
.description('Pull and sync from remote .agents repo')
|
|
204
201
|
.option('-y, --yes', 'Skip interactive prompts')
|
|
205
|
-
.option('-f, --force', 'Overwrite
|
|
202
|
+
.option('-f, --force', 'Overwrite existing resources without asking')
|
|
206
203
|
.option('-s, --scope <scope>', 'Target scope (default: user)', 'user')
|
|
207
204
|
.option('--dry-run', 'Show what would change')
|
|
208
|
-
.option('--skip-clis', 'Skip CLI
|
|
205
|
+
.option('--skip-clis', 'Skip CLI version sync')
|
|
209
206
|
.option('--skip-mcp', 'Skip MCP registration')
|
|
210
207
|
.action(async (source, options) => {
|
|
211
208
|
const scopeName = options.scope;
|
|
@@ -233,6 +230,7 @@ program
|
|
|
233
230
|
// Prevent modification of readonly scopes (but allow syncing from them)
|
|
234
231
|
const targetScopeConfig = meta.scopes[effectiveScope];
|
|
235
232
|
const isReadonly = targetScopeConfig?.readonly || effectiveScope === 'system';
|
|
233
|
+
const isUserScope = effectiveScope === 'user';
|
|
236
234
|
const parsed = parseSource(targetSource);
|
|
237
235
|
const spinner = ora(`Syncing from ${effectiveScope} scope...`).start();
|
|
238
236
|
try {
|
|
@@ -321,47 +319,176 @@ program
|
|
|
321
319
|
default: manifest?.defaults?.method || 'symlink',
|
|
322
320
|
});
|
|
323
321
|
}
|
|
324
|
-
|
|
322
|
+
// Detect conflicts
|
|
323
|
+
const conflicts = [];
|
|
324
|
+
// Check command conflicts
|
|
325
|
+
for (const command of commands) {
|
|
326
|
+
for (const agentId of selectedAgents) {
|
|
327
|
+
if (!isCliInstalled(agentId) && agentId !== 'cursor')
|
|
328
|
+
continue;
|
|
329
|
+
const sourcePath = resolveCommandSource(localPath, command.name, agentId);
|
|
330
|
+
if (sourcePath && commandExists(agentId, command.name)) {
|
|
331
|
+
conflicts.push({ type: 'command', name: command.name, agent: agentId });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Check skill conflicts
|
|
336
|
+
for (const skill of skills) {
|
|
337
|
+
const skillAgents = SKILLS_CAPABLE_AGENTS.filter((id) => selectedAgents.includes(id) && (isCliInstalled(id) || id === 'cursor'));
|
|
338
|
+
for (const agentId of skillAgents) {
|
|
339
|
+
if (skillExists(agentId, skill.name)) {
|
|
340
|
+
conflicts.push({ type: 'skill', name: skill.name, agent: agentId });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Check hook conflicts
|
|
345
|
+
const hookAgents = selectedAgents.filter((id) => HOOKS_CAPABLE_AGENTS.includes(id) && isCliInstalled(id));
|
|
346
|
+
for (const hookName of discoveredHooks.shared) {
|
|
347
|
+
for (const agentId of hookAgents) {
|
|
348
|
+
if (hookExists(agentId, hookName)) {
|
|
349
|
+
conflicts.push({ type: 'hook', name: hookName, agent: agentId });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
for (const [agentIdStr, hookNames] of Object.entries(discoveredHooks.agentSpecific)) {
|
|
354
|
+
const agentId = agentIdStr;
|
|
355
|
+
if (hookAgents.includes(agentId)) {
|
|
356
|
+
for (const hookName of hookNames) {
|
|
357
|
+
if (hookExists(agentId, hookName)) {
|
|
358
|
+
conflicts.push({ type: 'hook', name: hookName, agent: agentId });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Check MCP conflicts
|
|
364
|
+
if (!options.skipMcp && manifest?.mcp) {
|
|
365
|
+
for (const [name, config] of Object.entries(manifest.mcp)) {
|
|
366
|
+
if (config.transport === 'http' || !config.command)
|
|
367
|
+
continue;
|
|
368
|
+
for (const agentId of config.agents) {
|
|
369
|
+
if (!isCliInstalled(agentId))
|
|
370
|
+
continue;
|
|
371
|
+
if (isMcpRegistered(agentId, name)) {
|
|
372
|
+
conflicts.push({ type: 'mcp', name, agent: agentId });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// Handle conflicts
|
|
378
|
+
let conflictAction = 'overwrite';
|
|
379
|
+
if (conflicts.length > 0 && !options.force) {
|
|
380
|
+
// Deduplicate conflicts by name (show each resource once, not per-agent)
|
|
381
|
+
const uniqueConflicts = new Map();
|
|
382
|
+
for (const c of conflicts) {
|
|
383
|
+
const key = `${c.type}:${c.name}`;
|
|
384
|
+
if (!uniqueConflicts.has(key)) {
|
|
385
|
+
uniqueConflicts.set(key, c);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
console.log(chalk.yellow(`\nFound ${uniqueConflicts.size} existing resources that would be overwritten:\n`));
|
|
389
|
+
const byType = {
|
|
390
|
+
command: [],
|
|
391
|
+
skill: [],
|
|
392
|
+
hook: [],
|
|
393
|
+
mcp: [],
|
|
394
|
+
};
|
|
395
|
+
for (const c of uniqueConflicts.values()) {
|
|
396
|
+
byType[c.type].push(c.name);
|
|
397
|
+
}
|
|
398
|
+
if (byType.command.length > 0) {
|
|
399
|
+
console.log(` Commands: ${byType.command.join(', ')}`);
|
|
400
|
+
}
|
|
401
|
+
if (byType.skill.length > 0) {
|
|
402
|
+
console.log(` Skills: ${byType.skill.join(', ')}`);
|
|
403
|
+
}
|
|
404
|
+
if (byType.hook.length > 0) {
|
|
405
|
+
console.log(` Hooks: ${byType.hook.join(', ')}`);
|
|
406
|
+
}
|
|
407
|
+
if (byType.mcp.length > 0) {
|
|
408
|
+
console.log(` MCP Servers: ${byType.mcp.join(', ')}`);
|
|
409
|
+
}
|
|
410
|
+
if (!options.yes) {
|
|
411
|
+
conflictAction = await select({
|
|
412
|
+
message: 'How do you want to handle existing resources?',
|
|
413
|
+
choices: [
|
|
414
|
+
{ name: 'Overwrite all', value: 'overwrite' },
|
|
415
|
+
{ name: 'Skip existing (only install new)', value: 'skip' },
|
|
416
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
417
|
+
],
|
|
418
|
+
});
|
|
419
|
+
if (conflictAction === 'cancel') {
|
|
420
|
+
console.log(chalk.yellow('\nSync cancelled'));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const skipExisting = conflictAction === 'skip';
|
|
426
|
+
const conflictNames = new Set(conflicts.map((c) => `${c.type}:${c.name}:${c.agent}`));
|
|
427
|
+
// Install commands
|
|
325
428
|
const installSpinner = ora('Installing commands...').start();
|
|
326
429
|
let installed = 0;
|
|
430
|
+
let skipped = 0;
|
|
327
431
|
for (const command of commands) {
|
|
328
|
-
for (const agentId of
|
|
432
|
+
for (const agentId of selectedAgents) {
|
|
329
433
|
if (!isCliInstalled(agentId) && agentId !== 'cursor')
|
|
330
434
|
continue;
|
|
331
435
|
const sourcePath = resolveCommandSource(localPath, command.name, agentId);
|
|
332
|
-
if (sourcePath)
|
|
333
|
-
|
|
334
|
-
|
|
436
|
+
if (!sourcePath)
|
|
437
|
+
continue;
|
|
438
|
+
const conflictKey = `command:${command.name}:${agentId}`;
|
|
439
|
+
if (skipExisting && conflictNames.has(conflictKey)) {
|
|
440
|
+
skipped++;
|
|
441
|
+
continue;
|
|
335
442
|
}
|
|
443
|
+
installCommand(sourcePath, agentId, command.name, method);
|
|
444
|
+
installed++;
|
|
336
445
|
}
|
|
337
446
|
}
|
|
338
|
-
|
|
447
|
+
if (skipped > 0) {
|
|
448
|
+
installSpinner.succeed(`Installed ${installed} commands (skipped ${skipped} existing)`);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
installSpinner.succeed(`Installed ${installed} command instances`);
|
|
452
|
+
}
|
|
339
453
|
// Install skills
|
|
340
454
|
if (skills.length > 0) {
|
|
341
455
|
const skillSpinner = ora('Installing skills...').start();
|
|
342
456
|
let skillsInstalled = 0;
|
|
457
|
+
let skillsSkipped = 0;
|
|
343
458
|
for (const skill of skills) {
|
|
344
459
|
const skillAgents = SKILLS_CAPABLE_AGENTS.filter((id) => selectedAgents.includes(id) && (isCliInstalled(id) || id === 'cursor'));
|
|
460
|
+
// Check if should skip
|
|
461
|
+
const shouldSkip = skipExisting && skillAgents.some((agentId) => conflictNames.has(`skill:${skill.name}:${agentId}`));
|
|
462
|
+
if (shouldSkip) {
|
|
463
|
+
skillsSkipped++;
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
345
466
|
if (skillAgents.length > 0) {
|
|
346
467
|
const result = installSkill(skill.path, skill.name, skillAgents);
|
|
347
468
|
if (result.success)
|
|
348
469
|
skillsInstalled++;
|
|
349
470
|
}
|
|
350
471
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (hookAgents.length > 0) {
|
|
357
|
-
const hookSpinner = ora('Installing hooks...').start();
|
|
358
|
-
const result = await installHooks(localPath, hookAgents, { scope: 'user' });
|
|
359
|
-
hookSpinner.succeed(`Installed ${result.installed.length} hooks`);
|
|
472
|
+
if (skillsSkipped > 0) {
|
|
473
|
+
skillSpinner.succeed(`Installed ${skillsInstalled} skills (skipped ${skillsSkipped} existing)`);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
skillSpinner.succeed(`Installed ${skillsInstalled} skills`);
|
|
360
477
|
}
|
|
361
478
|
}
|
|
479
|
+
// Install hooks
|
|
480
|
+
if (totalHooks > 0 && hookAgents.length > 0) {
|
|
481
|
+
const hookSpinner = ora('Installing hooks...').start();
|
|
482
|
+
// Note: installHooks handles its own conflict detection internally
|
|
483
|
+
// For now, pass the full list - in the future we could filter
|
|
484
|
+
const result = await installHooks(localPath, hookAgents, { scope: 'user' });
|
|
485
|
+
hookSpinner.succeed(`Installed ${result.installed.length} hooks`);
|
|
486
|
+
}
|
|
487
|
+
// Register MCP servers
|
|
362
488
|
if (!options.skipMcp && manifest?.mcp) {
|
|
363
489
|
const mcpSpinner = ora('Registering MCP servers...').start();
|
|
364
490
|
let registered = 0;
|
|
491
|
+
let mcpSkipped = 0;
|
|
365
492
|
for (const [name, config] of Object.entries(manifest.mcp)) {
|
|
366
493
|
// Skip HTTP transport MCPs for now (need different registration)
|
|
367
494
|
if (config.transport === 'http' || !config.command)
|
|
@@ -369,14 +496,55 @@ program
|
|
|
369
496
|
for (const agentId of config.agents) {
|
|
370
497
|
if (!isCliInstalled(agentId))
|
|
371
498
|
continue;
|
|
372
|
-
|
|
373
|
-
|
|
499
|
+
const conflictKey = `mcp:${name}:${agentId}`;
|
|
500
|
+
if (conflictNames.has(conflictKey)) {
|
|
501
|
+
if (skipExisting) {
|
|
502
|
+
mcpSkipped++;
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
// If overwriting, unregister first then re-register
|
|
506
|
+
unregisterMcp(agentId, name);
|
|
507
|
+
}
|
|
374
508
|
const result = registerMcp(agentId, name, config.command, config.scope);
|
|
375
509
|
if (result.success)
|
|
376
510
|
registered++;
|
|
377
511
|
}
|
|
378
512
|
}
|
|
379
|
-
|
|
513
|
+
if (mcpSkipped > 0) {
|
|
514
|
+
mcpSpinner.succeed(`Registered ${registered} MCP servers (skipped ${mcpSkipped} existing)`);
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
mcpSpinner.succeed(`Registered ${registered} MCP servers`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// Sync CLI versions (user scope only, unless --skip-clis)
|
|
521
|
+
if (isUserScope && !options.skipClis && manifest?.clis) {
|
|
522
|
+
const cliSpinner = ora('Checking CLI versions...').start();
|
|
523
|
+
const cliUpdates = [];
|
|
524
|
+
for (const [agentIdStr, cliConfig] of Object.entries(manifest.clis)) {
|
|
525
|
+
const agentId = agentIdStr;
|
|
526
|
+
const agent = AGENTS[agentId];
|
|
527
|
+
if (!agent || !cliConfig.package)
|
|
528
|
+
continue;
|
|
529
|
+
const currentVersion = getCliVersion(agentId);
|
|
530
|
+
const targetVersion = cliConfig.version;
|
|
531
|
+
// Skip if same version or if target is "latest" and CLI is installed
|
|
532
|
+
if (currentVersion === targetVersion)
|
|
533
|
+
continue;
|
|
534
|
+
if (targetVersion === 'latest' && currentVersion)
|
|
535
|
+
continue;
|
|
536
|
+
cliUpdates.push(`${agent.name}: ${currentVersion || 'not installed'} -> ${targetVersion}`);
|
|
537
|
+
}
|
|
538
|
+
if (cliUpdates.length > 0) {
|
|
539
|
+
cliSpinner.info('CLI version differences detected');
|
|
540
|
+
console.log(chalk.gray(' Run `agents cli upgrade` to update CLIs'));
|
|
541
|
+
for (const update of cliUpdates) {
|
|
542
|
+
console.log(chalk.gray(` ${update}`));
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
cliSpinner.succeed('CLI versions match');
|
|
547
|
+
}
|
|
380
548
|
}
|
|
381
549
|
// Update scope config (only if not readonly)
|
|
382
550
|
if (!isReadonly) {
|