@swarmify/agents-cli 1.6.12 → 1.6.14

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.
Files changed (55) hide show
  1. package/dist/commands/pull.d.ts.map +1 -1
  2. package/dist/commands/pull.js +2 -1
  3. package/dist/commands/pull.js.map +1 -1
  4. package/dist/commands/sync.d.ts +3 -0
  5. package/dist/commands/sync.d.ts.map +1 -0
  6. package/dist/commands/sync.js +62 -0
  7. package/dist/commands/sync.js.map +1 -0
  8. package/dist/commands/versions.d.ts.map +1 -1
  9. package/dist/commands/versions.js +4 -1
  10. package/dist/commands/versions.js.map +1 -1
  11. package/dist/commands/view.d.ts.map +1 -1
  12. package/dist/commands/view.js +11 -7
  13. package/dist/commands/view.js.map +1 -1
  14. package/dist/index.js +2 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/lib/agents.d.ts.map +1 -1
  17. package/dist/lib/agents.js +2 -0
  18. package/dist/lib/agents.js.map +1 -1
  19. package/dist/lib/commands.d.ts.map +1 -1
  20. package/dist/lib/commands.js +36 -25
  21. package/dist/lib/commands.js.map +1 -1
  22. package/dist/lib/convert.js +3 -3
  23. package/dist/lib/convert.js.map +1 -1
  24. package/dist/lib/hooks.d.ts.map +1 -1
  25. package/dist/lib/hooks.js +30 -20
  26. package/dist/lib/hooks.js.map +1 -1
  27. package/dist/lib/mcp.d.ts +8 -3
  28. package/dist/lib/mcp.d.ts.map +1 -1
  29. package/dist/lib/mcp.js +31 -24
  30. package/dist/lib/mcp.js.map +1 -1
  31. package/dist/lib/memory.d.ts.map +1 -1
  32. package/dist/lib/memory.js +16 -6
  33. package/dist/lib/memory.js.map +1 -1
  34. package/dist/lib/resources.d.ts.map +1 -1
  35. package/dist/lib/resources.js +14 -2
  36. package/dist/lib/resources.js.map +1 -1
  37. package/dist/lib/shims.d.ts +12 -0
  38. package/dist/lib/shims.d.ts.map +1 -1
  39. package/dist/lib/shims.js +127 -0
  40. package/dist/lib/shims.js.map +1 -1
  41. package/dist/lib/skills.d.ts.map +1 -1
  42. package/dist/lib/skills.js +45 -26
  43. package/dist/lib/skills.js.map +1 -1
  44. package/dist/lib/state.d.ts +5 -0
  45. package/dist/lib/state.d.ts.map +1 -1
  46. package/dist/lib/state.js +32 -0
  47. package/dist/lib/state.js.map +1 -1
  48. package/dist/lib/types.d.ts +2 -0
  49. package/dist/lib/types.d.ts.map +1 -1
  50. package/dist/lib/types.js.map +1 -1
  51. package/dist/lib/versions.d.ts +8 -3
  52. package/dist/lib/versions.d.ts.map +1 -1
  53. package/dist/lib/versions.js +188 -97
  54. package/dist/lib/versions.js.map +1 -1
  55. package/package.json +1 -1
@@ -6,7 +6,7 @@ import { promisify } from 'util';
6
6
  import chalk from 'chalk';
7
7
  import * as TOML from 'smol-toml';
8
8
  import { checkbox, select } from '@inquirer/prompts';
9
- import { getVersionsDir, ensureAgentsDir, readMeta, writeMeta, getCommandsDir, getSkillsDir, getHooksDir, getMemoryDir, getPermissionsDir, getSubagentsDir, clearVersionResources, recordVersionResources, getMcpDir } from './state.js';
9
+ import { getVersionsDir, ensureAgentsDir, readMeta, writeMeta, getCommandsDir, getSkillsDir, getHooksDir, getMemoryDir, clearVersionResources, recordVersionResources, getProjectAgentsDir } from './state.js';
10
10
  import { AGENTS, getAccountEmail, MCP_CAPABLE_AGENTS, COMMANDS_CAPABLE_AGENTS, getMcpConfigPathForHome, parseMcpConfig } from './agents.js';
11
11
  import { applyPermissionsToVersion as applyPermsToVersion, PERMISSIONS_CAPABLE_AGENTS, discoverPermissionGroups, buildPermissionsFromGroups, CODEX_RULES_FILENAME } from './permissions.js';
12
12
  import { installMcpServers } from './mcp.js';
@@ -20,7 +20,7 @@ const execAsync = promisify(exec);
20
20
  /**
21
21
  * Get all available resources from ~/.agents/.
22
22
  */
23
- export function getAvailableResources() {
23
+ export function getAvailableResources(cwd = process.cwd()) {
24
24
  const result = {
25
25
  commands: [],
26
26
  skills: [],
@@ -31,60 +31,114 @@ export function getAvailableResources() {
31
31
  subagents: [],
32
32
  plugins: [],
33
33
  };
34
+ const projectAgentsDir = getProjectAgentsDir(cwd);
35
+ const userBase = path.dirname(getCommandsDir());
36
+ const resourceBases = [];
37
+ if (projectAgentsDir) {
38
+ resourceBases.push({ scope: 'project', base: projectAgentsDir });
39
+ }
40
+ resourceBases.push({ scope: 'user', base: userBase });
34
41
  // Commands (*.md files)
35
- const commandsDir = getCommandsDir();
36
- if (fs.existsSync(commandsDir)) {
37
- result.commands = fs.readdirSync(commandsDir)
42
+ const commandNames = new Set();
43
+ for (const { base } of resourceBases) {
44
+ const commandsDir = path.join(base, 'commands');
45
+ if (!fs.existsSync(commandsDir))
46
+ continue;
47
+ const names = fs.readdirSync(commandsDir)
38
48
  .filter(f => f.endsWith('.md'))
39
49
  .map(f => f.replace(/\.md$/, ''));
50
+ for (const name of names) {
51
+ commandNames.add(name);
52
+ }
40
53
  }
54
+ result.commands = Array.from(commandNames);
41
55
  // Skills (directories, excluding hidden)
42
- const skillsDir = getSkillsDir();
43
- if (fs.existsSync(skillsDir)) {
44
- result.skills = fs.readdirSync(skillsDir, { withFileTypes: true })
56
+ const skillNames = new Set();
57
+ for (const { base } of resourceBases) {
58
+ const skillsDir = path.join(base, 'skills');
59
+ if (!fs.existsSync(skillsDir))
60
+ continue;
61
+ const names = fs.readdirSync(skillsDir, { withFileTypes: true })
45
62
  .filter(d => d.isDirectory() && !d.name.startsWith('.'))
46
63
  .map(d => d.name);
64
+ for (const name of names) {
65
+ skillNames.add(name);
66
+ }
47
67
  }
48
- // Hooks (executable files)
49
- const hooksDir = getHooksDir();
50
- if (fs.existsSync(hooksDir)) {
51
- result.hooks = fs.readdirSync(hooksDir)
52
- .filter(f => !f.startsWith('.'));
68
+ result.skills = Array.from(skillNames);
69
+ // Hooks (files)
70
+ const hookNames = new Set();
71
+ for (const { base } of resourceBases) {
72
+ const hooksDir = path.join(base, 'hooks');
73
+ if (!fs.existsSync(hooksDir))
74
+ continue;
75
+ const names = fs.readdirSync(hooksDir).filter(f => !f.startsWith('.'));
76
+ for (const name of names) {
77
+ hookNames.add(name);
78
+ }
53
79
  }
80
+ result.hooks = Array.from(hookNames);
54
81
  // Memory (*.md files, excluding symlinks)
55
- const memoryDir = getMemoryDir();
56
- if (fs.existsSync(memoryDir)) {
57
- result.memory = fs.readdirSync(memoryDir)
82
+ const memoryNames = new Set();
83
+ for (const { base } of resourceBases) {
84
+ const memoryDir = path.join(base, 'memory');
85
+ if (!fs.existsSync(memoryDir))
86
+ continue;
87
+ const names = fs.readdirSync(memoryDir)
58
88
  .filter(f => {
59
89
  if (!f.endsWith('.md'))
60
90
  return false;
61
- // Skip symlinks (e.g., CLAUDE.md -> AGENTS.md)
62
91
  const stat = fs.lstatSync(path.join(memoryDir, f));
63
92
  return !stat.isSymbolicLink();
64
93
  })
65
94
  .map(f => f.replace(/\.md$/, ''));
95
+ for (const name of names) {
96
+ memoryNames.add(name);
97
+ }
66
98
  }
99
+ result.memory = Array.from(memoryNames);
67
100
  // MCP servers (*.yaml files)
68
- const mcpDir = getMcpDir();
69
- if (fs.existsSync(mcpDir)) {
70
- result.mcp = fs.readdirSync(mcpDir)
101
+ const mcpNames = new Set();
102
+ for (const { base } of resourceBases) {
103
+ const mcpDir = path.join(base, 'mcp');
104
+ if (!fs.existsSync(mcpDir))
105
+ continue;
106
+ const names = fs.readdirSync(mcpDir)
71
107
  .filter(f => f.endsWith('.yaml') || f.endsWith('.yml'))
72
108
  .map(f => f.replace(/\.(yaml|yml)$/, ''));
109
+ for (const name of names) {
110
+ mcpNames.add(name);
111
+ }
73
112
  }
113
+ result.mcp = Array.from(mcpNames);
74
114
  // Permission groups (from permissions/groups/*.yaml)
75
- const permsGroupsDir = path.join(getPermissionsDir(), 'groups');
76
- if (fs.existsSync(permsGroupsDir)) {
77
- result.permissions = fs.readdirSync(permsGroupsDir)
115
+ const permissionNames = new Set();
116
+ for (const { base } of resourceBases) {
117
+ const permsGroupsDir = path.join(base, 'permissions', 'groups');
118
+ if (!fs.existsSync(permsGroupsDir))
119
+ continue;
120
+ const names = fs.readdirSync(permsGroupsDir)
78
121
  .filter(f => f.endsWith('.yaml') || f.endsWith('.yml'))
79
122
  .map(f => f.replace(/\.(yaml|yml)$/, ''));
123
+ for (const name of names) {
124
+ permissionNames.add(name);
125
+ }
80
126
  }
127
+ result.permissions = Array.from(permissionNames);
81
128
  // Subagents (directories with AGENT.md)
82
- const subagentsDir = getSubagentsDir();
83
- if (fs.existsSync(subagentsDir)) {
84
- result.subagents = fs.readdirSync(subagentsDir, { withFileTypes: true })
129
+ const subagentNames = new Set();
130
+ for (const { base } of resourceBases) {
131
+ const subagentsDir = path.join(base, 'subagents');
132
+ if (!fs.existsSync(subagentsDir))
133
+ continue;
134
+ const names = fs.readdirSync(subagentsDir, { withFileTypes: true })
85
135
  .filter(d => d.isDirectory() && fs.existsSync(path.join(subagentsDir, d.name, 'AGENT.md')))
86
136
  .map(d => d.name);
137
+ for (const name of names) {
138
+ subagentNames.add(name);
139
+ }
87
140
  }
141
+ result.subagents = Array.from(subagentNames);
88
142
  // Plugins (directories with .claude-plugin/plugin.json)
89
143
  const allPlugins = discoverPlugins();
90
144
  result.plugins = allPlugins.map(p => p.name);
@@ -119,10 +173,11 @@ function skillDirsMatch(src, dest) {
119
173
  * Get what's ACTUALLY synced to a version by inspecting the version home.
120
174
  * This is the source of truth - not the tracking in agents.yaml.
121
175
  */
122
- export function getActuallySyncedResources(agent, version) {
176
+ export function getActuallySyncedResources(agent, version, options = {}) {
123
177
  const agentConfig = AGENTS[agent];
124
178
  const versionHome = path.join(getVersionsDir(), agent, version, 'home');
125
179
  const configDir = path.join(versionHome, `.${agent}`);
180
+ const projectAgentsDir = getProjectAgentsDir(options.cwd || process.cwd());
126
181
  const result = {
127
182
  commands: [],
128
183
  skills: [],
@@ -144,20 +199,23 @@ export function getActuallySyncedResources(agent, version) {
144
199
  // Skills - check what directories exist AND content matches central source
145
200
  const skillsDir = path.join(configDir, 'skills');
146
201
  const centralSkillsDir = getSkillsDir();
202
+ const projectSkillsDir = projectAgentsDir ? path.join(projectAgentsDir, 'skills') : null;
147
203
  if (fs.existsSync(skillsDir)) {
148
204
  const installedSkills = fs.readdirSync(skillsDir, { withFileTypes: true })
149
205
  .filter(d => d.isDirectory() && !d.name.startsWith('.'))
150
206
  .map(d => d.name);
151
207
  for (const skill of installedSkills) {
152
- const centralSkillDir = path.join(centralSkillsDir, skill);
153
208
  const versionSkillDir = path.join(skillsDir, skill);
154
- // If no central source, consider it synced (user-local skill)
155
- if (!fs.existsSync(centralSkillDir)) {
209
+ const projectSourceDir = projectSkillsDir ? path.join(projectSkillsDir, skill) : null;
210
+ const centralSkillDir = path.join(centralSkillsDir, skill);
211
+ const hasProjectSource = projectSourceDir ? fs.existsSync(projectSourceDir) : false;
212
+ const hasUserSource = fs.existsSync(centralSkillDir);
213
+ if (!hasProjectSource && !hasUserSource) {
156
214
  result.skills.push(skill);
157
215
  continue;
158
216
  }
159
- // Content-match: every file in central must exist in version with same content
160
- const allMatch = skillDirsMatch(centralSkillDir, versionSkillDir);
217
+ const sourceDir = hasProjectSource ? projectSourceDir : centralSkillDir;
218
+ const allMatch = skillDirsMatch(sourceDir, versionSkillDir);
161
219
  if (allMatch) {
162
220
  result.skills.push(skill);
163
221
  }
@@ -166,19 +224,22 @@ export function getActuallySyncedResources(agent, version) {
166
224
  // Hooks - check what files exist AND content matches central source
167
225
  const hooksDir = path.join(configDir, 'hooks');
168
226
  const centralHooksDir = getHooksDir();
227
+ const projectHooksDir = projectAgentsDir ? path.join(projectAgentsDir, 'hooks') : null;
169
228
  if (fs.existsSync(hooksDir)) {
170
229
  const installedHooks = fs.readdirSync(hooksDir).filter(f => !f.startsWith('.'));
171
230
  for (const hook of installedHooks) {
231
+ const projectFile = projectHooksDir ? path.join(projectHooksDir, hook) : null;
172
232
  const centralFile = path.join(centralHooksDir, hook);
173
233
  const versionFile = path.join(hooksDir, hook);
174
- // If no central source, consider it synced (user-local hook)
175
- if (!fs.existsSync(centralFile)) {
234
+ const hasProject = projectFile ? fs.existsSync(projectFile) : false;
235
+ const hasCentral = fs.existsSync(centralFile);
236
+ const sourceFile = hasProject ? projectFile : centralFile;
237
+ if (!hasProject && !hasCentral) {
176
238
  result.hooks.push(hook);
177
239
  continue;
178
240
  }
179
- // Content-match: version hook must match central hook
180
241
  try {
181
- const centralContent = fs.readFileSync(centralFile, 'utf-8');
242
+ const centralContent = fs.readFileSync(sourceFile, 'utf-8');
182
243
  const versionContent = fs.readFileSync(versionFile, 'utf-8');
183
244
  if (centralContent === versionContent) {
184
245
  result.hooks.push(hook);
@@ -191,22 +252,39 @@ export function getActuallySyncedResources(agent, version) {
191
252
  }
192
253
  // Memory - check which memory files are actually in sync (content matches)
193
254
  const memoryDir = getMemoryDir();
255
+ const projectMemoryDir = projectAgentsDir ? path.join(projectAgentsDir, 'memory') : null;
256
+ const memoryFiles = new Set();
194
257
  if (fs.existsSync(memoryDir)) {
195
- const centralFiles = fs.readdirSync(memoryDir).filter(f => f.endsWith('.md'));
196
- for (const file of centralFiles) {
197
- const memName = file.replace(/\.md$/, '');
198
- // AGENTS.md maps to agent's instructionsFile (e.g., CLAUDE.md)
199
- const targetName = file === 'AGENTS.md' ? agentConfig.instructionsFile : file;
200
- const versionFile = path.join(configDir, targetName);
201
- if (fs.existsSync(versionFile)) {
202
- // Only "synced" if content matches
203
- const centralContent = fs.readFileSync(path.join(memoryDir, file), 'utf-8');
204
- const versionContent = fs.readFileSync(versionFile, 'utf-8');
205
- if (centralContent === versionContent) {
206
- result.memory.push(memName);
207
- }
258
+ fs.readdirSync(memoryDir).filter(f => f.endsWith('.md')).forEach(f => memoryFiles.add(f));
259
+ }
260
+ if (projectMemoryDir && fs.existsSync(projectMemoryDir)) {
261
+ fs.readdirSync(projectMemoryDir).filter(f => f.endsWith('.md')).forEach(f => memoryFiles.add(f));
262
+ }
263
+ for (const file of memoryFiles) {
264
+ const memName = file.replace(/\.md$/, '');
265
+ const targetName = file === 'AGENTS.md' ? agentConfig.instructionsFile : file;
266
+ const versionFile = path.join(configDir, targetName);
267
+ if (!fs.existsSync(versionFile))
268
+ continue;
269
+ const projectFile = projectMemoryDir ? path.join(projectMemoryDir, file) : null;
270
+ const centralFile = path.join(memoryDir, file);
271
+ const hasProject = projectFile ? fs.existsSync(projectFile) : false;
272
+ const hasCentral = fs.existsSync(centralFile);
273
+ const sourceFile = hasProject ? projectFile : centralFile;
274
+ if (!hasProject && !hasCentral) {
275
+ result.memory.push(memName);
276
+ continue;
277
+ }
278
+ try {
279
+ const centralContent = fs.readFileSync(sourceFile, 'utf-8');
280
+ const versionContent = fs.readFileSync(versionFile, 'utf-8');
281
+ if (centralContent === versionContent) {
282
+ result.memory.push(memName);
208
283
  }
209
284
  }
285
+ catch {
286
+ // Ignore
287
+ }
210
288
  }
211
289
  // MCP - use canonical config path + parser per agent
212
290
  if (MCP_CAPABLE_AGENTS.includes(agent)) {
@@ -1006,19 +1084,21 @@ export function getResourceDiff(agent, version) {
1006
1084
  diff.commands.dangling = ['commands/'];
1007
1085
  }
1008
1086
  }
1009
- // Skills: check directory symlink
1010
- const centralSkills = getSkillsDir();
1011
- const skillsTarget = path.join(agentDir, 'skills');
1012
- const skillsStatus = getSymlinkStatus(skillsTarget);
1013
- if (skillsStatus === 'none' && fs.existsSync(centralSkills)) {
1014
- const dirs = fs.readdirSync(centralSkills).filter(f => {
1015
- const stat = fs.statSync(path.join(centralSkills, f));
1016
- return stat.isDirectory() && !f.startsWith('.');
1017
- });
1018
- diff.skills.added = dirs;
1019
- }
1020
- else if (skillsStatus === 'dangling') {
1021
- diff.skills.dangling = ['skills/'];
1087
+ // Skills: check directory symlink (skip if agent natively reads ~/.agents/skills/)
1088
+ if (!agentConfig.nativeAgentsSkillsDir) {
1089
+ const centralSkills = getSkillsDir();
1090
+ const skillsTarget = path.join(agentDir, 'skills');
1091
+ const skillsStatus = getSymlinkStatus(skillsTarget);
1092
+ if (skillsStatus === 'none' && fs.existsSync(centralSkills)) {
1093
+ const dirs = fs.readdirSync(centralSkills).filter(f => {
1094
+ const stat = fs.statSync(path.join(centralSkills, f));
1095
+ return stat.isDirectory() && !f.startsWith('.');
1096
+ });
1097
+ diff.skills.added = dirs;
1098
+ }
1099
+ else if (skillsStatus === 'dangling') {
1100
+ diff.skills.dangling = ['skills/'];
1101
+ }
1022
1102
  }
1023
1103
  // Hooks: check directory symlink (if agent supports hooks)
1024
1104
  if (agentConfig.supportsHooks) {
@@ -1066,13 +1146,15 @@ export function getResourceDiff(agent, version) {
1066
1146
  *
1067
1147
  * For Gemini: commands are converted from markdown to TOML.
1068
1148
  */
1069
- export function syncResourcesToVersion(agent, version, selection) {
1149
+ export function syncResourcesToVersion(agent, version, selection, options = {}) {
1070
1150
  const agentConfig = AGENTS[agent];
1071
1151
  const versionHome = getVersionHomePath(agent, version);
1072
1152
  const agentDir = path.join(versionHome, `.${agent}`);
1073
1153
  fs.mkdirSync(agentDir, { recursive: true });
1074
1154
  const result = { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [] };
1075
- const available = getAvailableResources();
1155
+ const cwd = options.cwd || process.cwd();
1156
+ const projectAgentsDir = options.projectDir || getProjectAgentsDir(cwd);
1157
+ const available = getAvailableResources(cwd);
1076
1158
  // Helper: remove a path (symlink or real) if it exists
1077
1159
  const removePath = (p) => {
1078
1160
  try {
@@ -1115,22 +1197,22 @@ export function syncResourcesToVersion(agent, version, selection) {
1115
1197
  : available.commands; // No selection = sync all
1116
1198
  if (commandsToSync.length > 0 && COMMANDS_CAPABLE_AGENTS.includes(agent)) {
1117
1199
  const centralCommands = getCommandsDir();
1200
+ const projectCommandsDir = projectAgentsDir ? path.join(projectAgentsDir, 'commands') : null;
1118
1201
  const commandsTarget = path.join(agentDir, agentConfig.commandsSubdir);
1119
- // Don't remove existing - just ensure dir exists and add/overwrite selected items
1120
1202
  fs.mkdirSync(commandsTarget, { recursive: true });
1121
1203
  const syncedCommands = [];
1122
1204
  for (const cmd of commandsToSync) {
1123
- const srcFile = path.join(centralCommands, `${cmd}.md`);
1205
+ const projectSource = projectCommandsDir ? path.join(projectCommandsDir, `${cmd}.md`) : null;
1206
+ const userSource = path.join(centralCommands, `${cmd}.md`);
1207
+ const srcFile = projectSource && fs.existsSync(projectSource) ? projectSource : userSource;
1124
1208
  if (!fs.existsSync(srcFile))
1125
1209
  continue;
1126
1210
  if (agentConfig.format === 'toml') {
1127
- // Gemini: convert markdown to TOML
1128
1211
  const content = fs.readFileSync(srcFile, 'utf-8');
1129
1212
  const tomlContent = markdownToToml(cmd, content);
1130
1213
  fs.writeFileSync(path.join(commandsTarget, `${cmd}.toml`), tomlContent);
1131
1214
  }
1132
1215
  else {
1133
- // Copy markdown file
1134
1216
  fs.copyFileSync(srcFile, path.join(commandsTarget, `${cmd}.md`));
1135
1217
  }
1136
1218
  syncedCommands.push(cmd);
@@ -1140,29 +1222,36 @@ export function syncResourcesToVersion(agent, version, selection) {
1140
1222
  recordVersionResources(agent, version, 'commands', syncedCommands);
1141
1223
  }
1142
1224
  }
1143
- // Sync skills
1144
- const skillsToSync = selection
1145
- ? resolveSelection(selection.skills, available.skills)
1146
- : available.skills;
1147
- if (skillsToSync.length > 0) {
1148
- const centralSkills = getSkillsDir();
1225
+ // Sync skills (skip if agent natively reads ~/.agents/skills/)
1226
+ if (agentConfig.nativeAgentsSkillsDir) {
1227
+ // Clean up stale skills symlink/dir — agent reads from ~/.agents/skills/ directly
1149
1228
  const skillsTarget = path.join(agentDir, 'skills');
1150
- // Don't remove existing - just ensure dir exists and add/overwrite selected items
1151
- fs.mkdirSync(skillsTarget, { recursive: true });
1152
- const syncedSkills = [];
1153
- for (const skill of skillsToSync) {
1154
- const srcDir = path.join(centralSkills, skill);
1155
- if (!fs.existsSync(srcDir))
1156
- continue;
1157
- const destDir = path.join(skillsTarget, skill);
1158
- // Remove just this skill's dir to ensure clean state, then copy fresh
1159
- removePath(destDir);
1160
- copyDir(srcDir, destDir);
1161
- syncedSkills.push(skill);
1162
- }
1163
- result.skills = syncedSkills.length > 0;
1164
- if (syncedSkills.length > 0) {
1165
- recordVersionResources(agent, version, 'skills', syncedSkills);
1229
+ removePath(skillsTarget);
1230
+ }
1231
+ else {
1232
+ const skillsToSync = selection
1233
+ ? resolveSelection(selection.skills, available.skills)
1234
+ : available.skills;
1235
+ if (skillsToSync.length > 0) {
1236
+ const centralSkills = getSkillsDir();
1237
+ const projectSkills = projectAgentsDir ? path.join(projectAgentsDir, 'skills') : null;
1238
+ const skillsTarget = path.join(agentDir, 'skills');
1239
+ fs.mkdirSync(skillsTarget, { recursive: true });
1240
+ const syncedSkills = [];
1241
+ for (const skill of skillsToSync) {
1242
+ const projectSource = projectSkills ? path.join(projectSkills, skill) : null;
1243
+ const srcDir = projectSource && fs.existsSync(projectSource) ? projectSource : path.join(centralSkills, skill);
1244
+ if (!fs.existsSync(srcDir))
1245
+ continue;
1246
+ const destDir = path.join(skillsTarget, skill);
1247
+ removePath(destDir);
1248
+ copyDir(srcDir, destDir);
1249
+ syncedSkills.push(skill);
1250
+ }
1251
+ result.skills = syncedSkills.length > 0;
1252
+ if (syncedSkills.length > 0) {
1253
+ recordVersionResources(agent, version, 'skills', syncedSkills);
1254
+ }
1166
1255
  }
1167
1256
  }
1168
1257
  // Sync hooks (if agent supports them)
@@ -1172,17 +1261,17 @@ export function syncResourcesToVersion(agent, version, selection) {
1172
1261
  : available.hooks;
1173
1262
  if (hooksToSync.length > 0) {
1174
1263
  const centralHooks = getHooksDir();
1264
+ const projectHooksDir = projectAgentsDir ? path.join(projectAgentsDir, 'hooks') : null;
1175
1265
  const hooksTarget = path.join(agentDir, 'hooks');
1176
- // Don't remove existing - just ensure dir exists and add/overwrite selected items
1177
1266
  fs.mkdirSync(hooksTarget, { recursive: true });
1178
1267
  const syncedHooks = [];
1179
1268
  for (const hook of hooksToSync) {
1180
- const srcFile = path.join(centralHooks, hook);
1269
+ const projectSource = projectHooksDir ? path.join(projectHooksDir, hook) : null;
1270
+ const srcFile = projectSource && fs.existsSync(projectSource) ? projectSource : path.join(centralHooks, hook);
1181
1271
  if (!fs.existsSync(srcFile))
1182
1272
  continue;
1183
1273
  const destFile = path.join(hooksTarget, hook);
1184
1274
  fs.copyFileSync(srcFile, destFile);
1185
- // Preserve executable permission
1186
1275
  fs.chmodSync(destFile, 0o755);
1187
1276
  syncedHooks.push(hook);
1188
1277
  }
@@ -1190,7 +1279,6 @@ export function syncResourcesToVersion(agent, version, selection) {
1190
1279
  if (syncedHooks.length > 0) {
1191
1280
  recordVersionResources(agent, version, 'hooks', syncedHooks);
1192
1281
  }
1193
- // Register hooks as lifecycle events in settings.json
1194
1282
  if (agent === 'claude') {
1195
1283
  registerHooksToSettings(agent, versionHome);
1196
1284
  }
@@ -1202,12 +1290,15 @@ export function syncResourcesToVersion(agent, version, selection) {
1202
1290
  : available.memory;
1203
1291
  if (memoryToSync.length > 0 && COMMANDS_CAPABLE_AGENTS.includes(agent)) {
1204
1292
  const centralMemory = getMemoryDir();
1293
+ const projectMemoryDir = projectAgentsDir ? path.join(projectAgentsDir, 'memory') : null;
1205
1294
  const syncedMemory = [];
1206
1295
  for (const mem of memoryToSync) {
1207
- const srcFile = path.join(centralMemory, `${mem}.md`);
1296
+ const projectSource = projectMemoryDir ? path.join(projectMemoryDir, `${mem}.md`) : null;
1297
+ const srcFile = projectSource && fs.existsSync(projectSource)
1298
+ ? projectSource
1299
+ : path.join(centralMemory, `${mem}.md`);
1208
1300
  if (!fs.existsSync(srcFile))
1209
1301
  continue;
1210
- // AGENTS.md gets renamed to the agent's instructionsFile name
1211
1302
  const targetName = mem === 'AGENTS' ? agentConfig.instructionsFile : `${mem}.md`;
1212
1303
  const destFile = path.join(agentDir, targetName);
1213
1304
  removePath(destFile);
@@ -1244,7 +1335,7 @@ export function syncResourcesToVersion(agent, version, selection) {
1244
1335
  ? resolveSelection(selection.mcp, available.mcp)
1245
1336
  : (MCP_CAPABLE_AGENTS.includes(agent) ? available.mcp : []);
1246
1337
  if (mcpToSync.length > 0 && MCP_CAPABLE_AGENTS.includes(agent)) {
1247
- const mcpResult = installMcpServers(agent, version, versionHome, mcpToSync);
1338
+ const mcpResult = installMcpServers(agent, version, versionHome, mcpToSync, { cwd });
1248
1339
  result.mcp = mcpResult.applied;
1249
1340
  if (mcpResult.applied.length > 0) {
1250
1341
  recordVersionResources(agent, version, 'mcp', mcpResult.applied);