ma-agents 1.6.0 → 1.8.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/CLAUDE.md ADDED
@@ -0,0 +1,14 @@
1
+
2
+ <!-- MA-AGENTS-START -->
3
+ # AI Agent Skills - Planning Instruction
4
+
5
+ You have access to a library of skills in your skills directory. Before starting any task:
6
+
7
+ 1. Read the skill manifest at skills/MANIFEST.yaml
8
+ 2. Based on the task description, select which skills are relevant
9
+ 3. Read only the selected skill files
10
+ 4. Then proceed with the task
11
+
12
+ Always load skills marked with always_load: true.
13
+ Do not load skills that are not relevant to the current task.
14
+ <!-- MA-AGENTS-END -->
package/bin/cli.js CHANGED
@@ -120,60 +120,113 @@ async function installWizard(preselectedSkill, preselectedAgents, customPath, fo
120
120
  const skills = listSkills();
121
121
  const agents = listAgents();
122
122
 
123
+ let selectedSkillIds = preselectedSkill ? [preselectedSkill] : [];
124
+ let selectedAgentIds = preselectedAgents || [];
125
+ let installScope = 'project';
126
+ let installPath = customPath || '';
127
+
128
+ const existingStatus = getStatus([], '', 'project');
129
+ let isUpdate = false;
130
+
131
+ // Step 0: Check for existing installation ONLY in interactive mode (no preselected skill)
132
+ if (!preselectedSkill && !customPath && existingStatus.length > 0) {
133
+ console.log(chalk.cyan('\n Existing installation detected in project.'));
134
+
135
+ const { action } = await prompts({
136
+ type: 'select',
137
+ name: 'action',
138
+ message: 'What would you like to do?',
139
+ choices: [
140
+ { title: 'Update (add/remove skills)', value: 'update' },
141
+ { title: 'Clean reinstall (remove all first)', value: 'reinstall' },
142
+ { title: 'Uninstall all skills', value: 'uninstall' },
143
+ { title: 'Cancel', value: 'cancel' }
144
+ ]
145
+ });
146
+
147
+ if (!action || action === 'cancel') process.exit(0);
148
+
149
+ if (action === 'uninstall') {
150
+ const { confirmed } = await prompts({
151
+ type: 'confirm',
152
+ name: 'confirmed',
153
+ message: 'This will remove all ma-agents skills from this project. Are you sure?',
154
+ initial: false
155
+ });
156
+ if (!confirmed) process.exit(0);
157
+
158
+ for (const entry of existingStatus) {
159
+ for (const skillId of Object.keys(entry.skills)) {
160
+ await uninstallSkill(skillId, [entry.agent.id], '', 'project');
161
+ }
162
+ }
163
+ console.log(chalk.bold.green('\n All skills successfully removed!\n'));
164
+ return;
165
+ }
166
+
167
+ if (action === 'reinstall') {
168
+ for (const entry of existingStatus) {
169
+ for (const skillId of Object.keys(entry.skills)) {
170
+ await uninstallSkill(skillId, [entry.agent.id], '', 'project');
171
+ }
172
+ }
173
+ console.log(chalk.gray(' Clean slate prepared.'));
174
+ }
175
+
176
+ if (action === 'update') {
177
+ isUpdate = true;
178
+ // Pre-populate selections from existing status
179
+ const existingSkillIds = new Set();
180
+ const existingAgentIds = new Set();
181
+ existingStatus.forEach(entry => {
182
+ existingAgentIds.add(entry.agent.id);
183
+ Object.keys(entry.skills).forEach(id => existingSkillIds.add(id));
184
+ });
185
+ selectedSkillIds = Array.from(existingSkillIds);
186
+ selectedAgentIds = Array.from(existingAgentIds);
187
+ }
188
+ }
189
+
123
190
  // Step 1: Select skills
124
- let selectedSkillIds;
125
- if (preselectedSkill) {
126
- selectedSkillIds = [preselectedSkill];
127
- } else {
191
+ if (selectedSkillIds.length === 0 || isUpdate) {
128
192
  const { skills: chosen } = await prompts({
129
193
  type: 'multiselect',
130
194
  name: 'skills',
131
- message: 'Which skills do you want to install?',
195
+ message: 'Select the skills you want to have installed:',
132
196
  choices: skills.map(s => ({
133
197
  title: chalk.white(s.name) + chalk.gray(` v${s.version} - ${s.description}`),
134
198
  value: s.id,
135
- selected: false
199
+ selected: selectedSkillIds.includes(s.id)
136
200
  })),
137
201
  instructions: chalk.gray(' Use space to select, enter to confirm'),
138
202
  min: 1
139
203
  });
140
204
 
141
- if (!chosen || chosen.length === 0) {
142
- console.log(chalk.yellow('No skills selected. Exiting.'));
143
- process.exit(0);
144
- }
205
+ if (!chosen) process.exit(0);
145
206
  selectedSkillIds = chosen;
146
207
  }
147
208
 
148
209
  // Step 2: Select agents
149
- let selectedAgentIds;
150
- if (preselectedAgents && preselectedAgents.length > 0) {
151
- selectedAgentIds = preselectedAgents;
152
- } else {
210
+ if (selectedAgentIds.length === 0 || isUpdate) {
153
211
  const { agents: chosen } = await prompts({
154
212
  type: 'multiselect',
155
213
  name: 'agents',
156
- message: 'Which coding agents do you want to install to?',
214
+ message: 'Select which coding agents to include:',
157
215
  choices: agents.map(a => ({
158
216
  title: chalk.white(a.name) + chalk.gray(` v${a.version} - ${a.description}`),
159
217
  value: a.id,
160
- selected: false
218
+ selected: selectedAgentIds.includes(a.id)
161
219
  })),
162
220
  instructions: chalk.gray(' Use space to select, enter to confirm'),
163
221
  min: 1
164
222
  });
165
223
 
166
- if (!chosen || chosen.length === 0) {
167
- console.log(chalk.yellow('No agents selected. Exiting.'));
168
- process.exit(0);
169
- }
224
+ if (!chosen) process.exit(0);
170
225
  selectedAgentIds = chosen;
171
226
  }
172
227
 
173
- // Step 3: Installation scope
174
- let installPath = customPath || '';
175
- let installScope = 'project';
176
- if (!installPath) {
228
+ // Step 3: Scope (Skip if update, we already know it's project)
229
+ if (!isUpdate && !installPath) {
177
230
  const { pathChoice } = await prompts({
178
231
  type: 'select',
179
232
  name: 'pathChoice',
@@ -209,16 +262,33 @@ async function installWizard(preselectedSkill, preselectedAgents, customPath, fo
209
262
  const { confirmed } = await prompts({
210
263
  type: 'confirm',
211
264
  name: 'confirmed',
212
- message: 'Proceed with installation?',
265
+ message: isUpdate ? 'Apply changes?' : 'Proceed with installation?',
213
266
  initial: true
214
267
  });
215
268
 
216
269
  if (!confirmed) {
217
- console.log(chalk.yellow('Installation cancelled.'));
270
+ console.log(chalk.yellow('Operation cancelled.'));
218
271
  process.exit(0);
219
272
  }
220
273
 
221
- // Step 5: Install each skill (with upgrade detection)
274
+ // Step 5: Execution
275
+ if (isUpdate) {
276
+ // Identify removals
277
+ const currentlyInstalled = new Set();
278
+ existingStatus.forEach(entry => Object.keys(entry.skills).forEach(id => currentlyInstalled.add(id)));
279
+
280
+ const finalSkills = new Set(selectedSkillIds);
281
+ const toRemove = [...currentlyInstalled].filter(id => !finalSkills.has(id));
282
+
283
+ if (toRemove.length > 0) {
284
+ console.log(chalk.gray(`\n Cleaning up ${toRemove.length} removed skills...`));
285
+ for (const skillId of toRemove) {
286
+ await uninstallSkill(skillId, selectedAgentIds, installPath, 'project');
287
+ }
288
+ }
289
+ }
290
+
291
+ // Install requested skills
222
292
  for (const skillId of selectedSkillIds) {
223
293
  try {
224
294
  await installSkill(skillId, selectedAgentIds, installPath, installScope, { force: !!forceFlag });
@@ -227,7 +297,7 @@ async function installWizard(preselectedSkill, preselectedAgents, customPath, fo
227
297
  }
228
298
  }
229
299
 
230
- console.log(chalk.bold.green('\n Installation complete!\n'));
300
+ console.log(chalk.bold.green('\n Done!\n'));
231
301
  }
232
302
 
233
303
  // --- Command handlers ---
package/lib/agents.js CHANGED
@@ -31,7 +31,8 @@ const agents = [
31
31
  }
32
32
  },
33
33
  fileExtension: '.md',
34
- template: 'claude-code'
34
+ template: 'claude-code',
35
+ instructionFiles: ['CLAUDE.md']
35
36
  },
36
37
  {
37
38
  id: 'gemini',
@@ -50,7 +51,8 @@ const agents = [
50
51
  }
51
52
  },
52
53
  fileExtension: '.md',
53
- template: 'generic'
54
+ template: 'generic',
55
+ instructionFiles: [] // Gemini doesn't have a standard project-level instruction file yet
54
56
  },
55
57
  {
56
58
  id: 'copilot',
@@ -69,7 +71,8 @@ const agents = [
69
71
  }
70
72
  },
71
73
  fileExtension: '.md',
72
- template: 'generic'
74
+ template: 'generic',
75
+ instructionFiles: [] // Copilot uses path-scoped instructions, maybe support .github/instructions/ later
73
76
  },
74
77
  {
75
78
  id: 'kilocode',
@@ -88,7 +91,8 @@ const agents = [
88
91
  }
89
92
  },
90
93
  fileExtension: '.md',
91
- template: 'generic'
94
+ template: 'generic',
95
+ instructionFiles: []
92
96
  },
93
97
  {
94
98
  id: 'cline',
@@ -112,7 +116,8 @@ const agents = [
112
116
  resourceMap: {
113
117
  'references': 'docs',
114
118
  'assets': 'templates'
115
- }
119
+ },
120
+ instructionFiles: ['.clinerules']
116
121
  },
117
122
  {
118
123
  id: 'cursor',
@@ -131,7 +136,8 @@ const agents = [
131
136
  }
132
137
  },
133
138
  fileExtension: '.md',
134
- template: 'generic'
139
+ template: 'generic',
140
+ instructionFiles: [] // Cursor uses .cursorrules or individual .mdc files
135
141
  }
136
142
  ];
137
143
 
package/lib/installer.js CHANGED
@@ -52,6 +52,84 @@ function getInstalledSkillInfo(installPath, skillId) {
52
52
  return manifest.skills[skillId];
53
53
  }
54
54
 
55
+ async function generateSkillsManifest(installPath, agent) {
56
+ const skills = listSkills();
57
+ const manifest = readManifest(installPath);
58
+ if (!manifest || !manifest.skills) return;
59
+
60
+ const manifestYamlPath = path.join(installPath, 'MANIFEST.yaml');
61
+ let yamlContent = '# skills/MANIFEST.yaml\n\nskills:\n';
62
+
63
+ const skillIds = Object.keys(manifest.skills).sort();
64
+ for (const skillId of skillIds) {
65
+ const skill = skills.find(s => s.id === skillId);
66
+ if (!skill) continue;
67
+
68
+ yamlContent += ` - id: ${skillId}\n`;
69
+ yamlContent += ` file: skills/${skillId}/SKILL.md\n`;
70
+ yamlContent += ` description: ${skill.description}\n`;
71
+
72
+ if (skill.applies_when && Array.isArray(skill.applies_when)) {
73
+ yamlContent += ' applies_when:\n';
74
+ skill.applies_when.forEach(cond => {
75
+ yamlContent += ` - ${cond}\n`;
76
+ });
77
+ }
78
+
79
+ if (skill.always_load) {
80
+ yamlContent += ' always_load: true\n';
81
+ }
82
+
83
+ yamlContent += '\n';
84
+ }
85
+
86
+ await fs.writeFile(manifestYamlPath, yamlContent, 'utf-8');
87
+ console.log(chalk.cyan(` + Generated ${manifestYamlPath}`));
88
+ }
89
+
90
+ async function updateAgentInstructions(agent, projectRoot) {
91
+ if (!agent.instructionFiles || agent.instructionFiles.length === 0) return;
92
+
93
+ const planningInstruction = `
94
+ # AI Agent Skills - Planning Instruction
95
+
96
+ You have access to a library of skills in your skills directory. Before starting any task:
97
+
98
+ 1. Read the skill manifest at skills/MANIFEST.yaml
99
+ 2. Based on the task description, select which skills are relevant
100
+ 3. Read only the selected skill files
101
+ 4. Then proceed with the task
102
+
103
+ Always load skills marked with always_load: true.
104
+ Do not load skills that are not relevant to the current task.
105
+ `;
106
+
107
+ const markerStart = '<!-- MA-AGENTS-START -->';
108
+ const markerEnd = '<!-- MA-AGENTS-END -->';
109
+ const wrappedInstruction = `\n${markerStart}${planningInstruction}${markerEnd}\n`;
110
+
111
+ for (const fileName of agent.instructionFiles) {
112
+ const filePath = path.join(projectRoot, fileName);
113
+ let content = '';
114
+
115
+ if (fs.existsSync(filePath)) {
116
+ content = await fs.readFile(filePath, 'utf-8');
117
+
118
+ const regex = new RegExp(`${markerStart}[\\s\\S]*?${markerEnd}`, 'g');
119
+ if (regex.test(content)) {
120
+ content = content.replace(regex, wrappedInstruction.trim());
121
+ } else {
122
+ content += wrappedInstruction;
123
+ }
124
+ } else {
125
+ content = wrappedInstruction;
126
+ }
127
+
128
+ await fs.writeFile(filePath, content, 'utf-8');
129
+ console.log(chalk.cyan(` + Updated ${fileName}`));
130
+ }
131
+ }
132
+
55
133
  /**
56
134
  * Compare two semver strings. Returns -1, 0, or 1.
57
135
  */
@@ -252,6 +330,12 @@ async function installSkill(skillId, agentIds, customPath = '', scope = 'project
252
330
  delete manifest.skills[skillId];
253
331
  writeManifest(installPath, manifest);
254
332
  console.log(chalk.green(` - Removed ${skill.name} from ${agent.name}`));
333
+
334
+ // Generate MANIFEST.yaml and update agent instruction files
335
+ await generateSkillsManifest(installPath, agent);
336
+ if (scope === 'project') {
337
+ await updateAgentInstructions(agent, process.cwd());
338
+ }
255
339
  continue;
256
340
  }
257
341
 
@@ -281,6 +365,12 @@ async function installSkill(skillId, agentIds, customPath = '', scope = 'project
281
365
  agentVersion: agent.version
282
366
  };
283
367
  writeManifest(installPath, manifest);
368
+
369
+ // Generate MANIFEST.yaml and update agent instruction files
370
+ await generateSkillsManifest(installPath, agent);
371
+ if (scope === 'project') {
372
+ await updateAgentInstructions(agent, process.cwd());
373
+ }
284
374
  }
285
375
  } catch (error) {
286
376
  console.log(chalk.red(` x Failed: ${error.message}`));
@@ -330,6 +420,12 @@ async function uninstallSkill(skillId, agentIds, customPath = '', scope = 'proje
330
420
  writeManifest(installPath, manifest);
331
421
 
332
422
  console.log(chalk.green(` - Removed ${skillId} v${installed.version} from ${agent.name}`));
423
+
424
+ // Generate MANIFEST.yaml and update agent instruction files
425
+ await generateSkillsManifest(installPath, agent);
426
+ if (scope === 'project') {
427
+ await updateAgentInstructions(agent, process.cwd());
428
+ }
333
429
  } catch (error) {
334
430
  console.log(chalk.red(` x Failed: ${error.message}`));
335
431
  }
@@ -353,9 +449,9 @@ function getStatus(agentIds, customPath = '', scope = 'project') {
353
449
  const pathsToCheck = customPath
354
450
  ? [{ path: customPath, scope: 'custom' }]
355
451
  : [
356
- { path: projectPath, scope: 'project' },
357
- { path: globalPath, scope: 'global' }
358
- ];
452
+ { path: projectPath, scope: 'project' },
453
+ { path: globalPath, scope: 'global' }
454
+ ];
359
455
 
360
456
  for (const { path: checkPath, scope: checkScope } of pathsToCheck) {
361
457
  const manifest = readManifest(checkPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ma-agents",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "NPX tool to install skills for AI coding agents (Claude Code, Gemini, Copilot, Kilocode, Cline, Cursor)",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -11,5 +11,11 @@
11
11
  "conventional-commits",
12
12
  "pull-requests",
13
13
  "multi-agent"
14
- ]
14
+ ],
15
+ "applies_when": [
16
+ "committing changes",
17
+ "creating branches or PRs",
18
+ "any code writing or modification task"
19
+ ],
20
+ "always_load": true
15
21
  }
@@ -3,5 +3,16 @@
3
3
  "description": "Verify security of JavaScript and TypeScript codebases against OWASP Top 10 2025 standards",
4
4
  "version": "1.0.0",
5
5
  "author": "AI Agent Skills",
6
- "tags": ["javascript", "typescript", "security", "OWASP", "vulnerability-scanning"]
7
- }
6
+ "tags": [
7
+ "javascript",
8
+ "typescript",
9
+ "security",
10
+ "OWASP",
11
+ "vulnerability-scanning"
12
+ ],
13
+ "applies_when": [
14
+ "writing or reviewing security-critical code",
15
+ "analyzing third-party dependencies",
16
+ "performing security audits"
17
+ ]
18
+ }
@@ -9,5 +9,10 @@
9
9
  "json",
10
10
  "opentelemetry",
11
11
  "quality"
12
+ ],
13
+ "applies_when": [
14
+ "writing new service classes",
15
+ "reviewing or modifying existing log statements",
16
+ "adding error handling"
12
17
  ]
13
18
  }
@@ -8,5 +8,10 @@
8
8
  "quality",
9
9
  "policy",
10
10
  "tdd"
11
+ ],
12
+ "applies_when": [
13
+ "writing or reviewing public APIs",
14
+ "adding new public methods to a class",
15
+ "code review tasks"
11
16
  ]
12
17
  }
@@ -3,5 +3,15 @@
3
3
  "description": "Generates comprehensive unit and integration tests",
4
4
  "version": "1.0.0",
5
5
  "author": "AI Agent Skills",
6
- "tags": ["testing", "unit-tests", "integration-tests", "quality"]
7
- }
6
+ "tags": [
7
+ "testing",
8
+ "unit-tests",
9
+ "integration-tests",
10
+ "quality"
11
+ ],
12
+ "applies_when": [
13
+ "creating new code files",
14
+ "adding complex logic",
15
+ "refactoring existing code"
16
+ ]
17
+ }
@@ -4,5 +4,17 @@
4
4
  "version": "1.0.0",
5
5
  "author": "vercel-labs",
6
6
  "source": "https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices",
7
- "tags": ["react", "nextjs", "performance", "optimization", "vercel", "best-practices"]
8
- }
7
+ "tags": [
8
+ "react",
9
+ "nextjs",
10
+ "performance",
11
+ "optimization",
12
+ "vercel",
13
+ "best-practices"
14
+ ],
15
+ "applies_when": [
16
+ "working on React or Next.js components",
17
+ "optimizing page performance",
18
+ "reviewing frontend code architecture"
19
+ ]
20
+ }