@silverassist/agents-toolkit 2.0.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.
Files changed (51) hide show
  1. package/LICENSE +135 -0
  2. package/README.md +388 -0
  3. package/bin/cli.js +940 -0
  4. package/package.json +52 -0
  5. package/src/index.js +58 -0
  6. package/templates/agents/AGENTS.codex.md +195 -0
  7. package/templates/agents/AGENTS.md +195 -0
  8. package/templates/agents/CLAUDE.md +213 -0
  9. package/templates/agents/copilot-instructions.md +83 -0
  10. package/templates/shared/instructions/css-styling.instructions.md +142 -0
  11. package/templates/shared/instructions/documentation-language.instructions.md +216 -0
  12. package/templates/shared/instructions/github-workflow.instructions.md +245 -0
  13. package/templates/shared/instructions/php-standards.instructions.md +293 -0
  14. package/templates/shared/instructions/react-components.instructions.md +196 -0
  15. package/templates/shared/instructions/server-actions.instructions.md +429 -0
  16. package/templates/shared/instructions/testing-standards.instructions.md +264 -0
  17. package/templates/shared/instructions/tests.instructions.md +103 -0
  18. package/templates/shared/instructions/typescript.instructions.md +106 -0
  19. package/templates/shared/instructions/wordpress-plugin-architecture.instructions.md +238 -0
  20. package/templates/shared/prompts/README.md +129 -0
  21. package/templates/shared/prompts/_partials/README.md +57 -0
  22. package/templates/shared/prompts/_partials/documentation.md +203 -0
  23. package/templates/shared/prompts/_partials/git-operations.md +171 -0
  24. package/templates/shared/prompts/_partials/github-integration.md +100 -0
  25. package/templates/shared/prompts/_partials/jira-integration.md +155 -0
  26. package/templates/shared/prompts/_partials/pr-template.md +216 -0
  27. package/templates/shared/prompts/_partials/validations.md +87 -0
  28. package/templates/shared/prompts/add-tests.prompt.md +151 -0
  29. package/templates/shared/prompts/analyze-github-issue.prompt.md +73 -0
  30. package/templates/shared/prompts/analyze-ticket.prompt.md +63 -0
  31. package/templates/shared/prompts/create-plan.prompt.md +118 -0
  32. package/templates/shared/prompts/create-pr.prompt.md +139 -0
  33. package/templates/shared/prompts/finalize-pr.prompt.md +150 -0
  34. package/templates/shared/prompts/fix-issues.prompt.md +92 -0
  35. package/templates/shared/prompts/new-wp-component.prompt.md +51 -0
  36. package/templates/shared/prompts/new-wp-plugin.prompt.md +46 -0
  37. package/templates/shared/prompts/prepare-pr.prompt.md +123 -0
  38. package/templates/shared/prompts/prepare-release.prompt.md +74 -0
  39. package/templates/shared/prompts/quality-check.prompt.md +45 -0
  40. package/templates/shared/prompts/review-code.prompt.md +81 -0
  41. package/templates/shared/prompts/work-github-issue.prompt.md +96 -0
  42. package/templates/shared/prompts/work-ticket.prompt.md +98 -0
  43. package/templates/shared/skills/README.md +53 -0
  44. package/templates/shared/skills/component-architecture/SKILL.md +321 -0
  45. package/templates/shared/skills/create-component/SKILL.md +714 -0
  46. package/templates/shared/skills/domain-driven-design/SKILL.md +277 -0
  47. package/templates/shared/skills/plugin-creation/SKILL.md +1094 -0
  48. package/templates/shared/skills/quality-checks/SKILL.md +493 -0
  49. package/templates/shared/skills/release-management/SKILL.md +649 -0
  50. package/templates/shared/skills/testing/SKILL.md +682 -0
  51. package/templates/shared/skills/testing-patterns/SKILL.md +243 -0
package/bin/cli.js ADDED
@@ -0,0 +1,940 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI tool for installing and managing AI agent prompts
5
+ * @module agents-toolkit/cli
6
+ */
7
+
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
16
+ const COLORS = {
17
+ reset: '\x1b[0m',
18
+ bright: '\x1b[1m',
19
+ green: '\x1b[32m',
20
+ yellow: '\x1b[33m',
21
+ blue: '\x1b[34m',
22
+ red: '\x1b[31m',
23
+ cyan: '\x1b[36m',
24
+ };
25
+
26
+ const DEFAULT_CONFIG = {
27
+ stack: 'all',
28
+ tracker: 'all',
29
+ jira: {
30
+ projectKey: 'PROJECT',
31
+ baseUrl: 'https://your-org.atlassian.net'
32
+ },
33
+ git: {
34
+ defaultBranch: 'dev',
35
+ branchPrefix: {
36
+ feature: 'feature/',
37
+ bugfix: 'bugfix/',
38
+ hotfix: 'hotfix/'
39
+ }
40
+ },
41
+ pr: {
42
+ targetBranch: 'dev',
43
+ template: 'default'
44
+ }
45
+ };
46
+
47
+ /**
48
+ * File categorization for stack/tracker filtering
49
+ */
50
+ const FILE_CATEGORIES = {
51
+ instructions: {
52
+ react: ['css-styling', 'react-components', 'server-actions', 'tests', 'typescript'],
53
+ wordpress: ['php-standards', 'wordpress-plugin-architecture', 'testing-standards'],
54
+ universal: ['documentation-language', 'github-workflow'],
55
+ },
56
+ prompts: {
57
+ react: [],
58
+ wordpress: ['new-wp-component', 'new-wp-plugin', 'quality-check'],
59
+ universal: [
60
+ 'analyze-ticket', 'work-ticket', 'analyze-github-issue', 'work-github-issue',
61
+ 'create-plan', 'create-pr', 'prepare-pr', 'finalize-pr',
62
+ 'review-code', 'fix-issues', 'add-tests', 'prepare-release',
63
+ ],
64
+ jira: ['analyze-ticket', 'work-ticket', 'create-pr', 'finalize-pr'],
65
+ github: ['analyze-github-issue', 'work-github-issue'],
66
+ },
67
+ partials: {
68
+ jira: ['jira-integration'],
69
+ github: ['github-integration'],
70
+ universal: ['git-operations', 'pr-template', 'validations', 'documentation'],
71
+ },
72
+ skills: {
73
+ react: ['component-architecture', 'testing-patterns'],
74
+ wordpress: ['create-component', 'plugin-creation', 'quality-checks', 'testing'],
75
+ universal: ['domain-driven-design', 'release-management'],
76
+ },
77
+ };
78
+
79
+ /**
80
+ * Determine if a file should be included based on stack/tracker filters
81
+ * @param {string} filename - File basename without extension
82
+ * @param {string} category - Category: instructions, prompts, partials, or skills
83
+ * @param {{ stack: string, tracker: string }} filters - Active filters
84
+ * @returns {boolean} Whether to include the file
85
+ */
86
+ function shouldIncludeFile(filename, category, { stack, tracker }) {
87
+ const cats = FILE_CATEGORIES[category];
88
+ if (!cats) return true;
89
+
90
+ if (cats.universal && cats.universal.includes(filename)) {
91
+ if (tracker !== 'all' && cats.jira && cats.jira.includes(filename) && tracker !== 'jira') return false;
92
+ if (tracker !== 'all' && cats.github && cats.github.includes(filename) && tracker !== 'github') return false;
93
+ return true;
94
+ }
95
+
96
+ if (stack !== 'all') {
97
+ if (cats.react && cats.react.includes(filename)) return stack === 'react';
98
+ if (cats.wordpress && cats.wordpress.includes(filename)) return stack === 'wordpress';
99
+ }
100
+
101
+ if (tracker !== 'all') {
102
+ if (cats.jira && cats.jira.includes(filename)) return tracker === 'jira';
103
+ if (cats.github && cats.github.includes(filename)) return tracker === 'github';
104
+ }
105
+
106
+ return true;
107
+ }
108
+
109
+ /**
110
+ * Print colored message to console
111
+ * @param {string} message - Message to print
112
+ * @param {string} color - Color key from COLORS
113
+ */
114
+ function log(message, color = 'reset') {
115
+ console.log(`${COLORS[color]}${message}${COLORS.reset}`);
116
+ }
117
+
118
+ /**
119
+ * Print success message
120
+ * @param {string} message - Message to print
121
+ */
122
+ function success(message) {
123
+ log(`✅ ${message}`, 'green');
124
+ }
125
+
126
+ /**
127
+ * Print warning message
128
+ * @param {string} message - Message to print
129
+ */
130
+ function warn(message) {
131
+ log(`⚠️ ${message}`, 'yellow');
132
+ }
133
+
134
+ /**
135
+ * Print error message
136
+ * @param {string} message - Message to print
137
+ */
138
+ function error(message) {
139
+ log(`❌ ${message}`, 'red');
140
+ }
141
+
142
+ /**
143
+ * Print info message
144
+ * @param {string} message - Message to print
145
+ */
146
+ function info(message) {
147
+ log(`ℹ️ ${message}`, 'blue');
148
+ }
149
+
150
+ /**
151
+ * Get the target directory for Copilot installation
152
+ * @returns {string} Path to .github directory
153
+ */
154
+ function getTargetDir() {
155
+ return path.join(process.cwd(), '.github');
156
+ }
157
+
158
+ /**
159
+ * Get the target directory for Claude Code installation
160
+ * @returns {string} Path to .claude directory
161
+ */
162
+ function getClaudeTargetDir() {
163
+ return path.join(process.cwd(), '.claude');
164
+ }
165
+
166
+ /**
167
+ * Strip GitHub Copilot frontmatter from a prompt file
168
+ * Removes the ---\nagent: ...\ndescription: ...\n--- block
169
+ * @param {string} content - File content
170
+ * @returns {string} Content without Copilot frontmatter
171
+ */
172
+ function stripCopilotFrontmatter(content) {
173
+ return content.replace(/^---\n(?:[\s\S]*?\n)?---\n\n?/, '');
174
+ }
175
+
176
+ /**
177
+ * Adapt path references in prompt content for Claude Code
178
+ * @param {string} content - File content
179
+ * @returns {string} Content with updated paths
180
+ */
181
+ function adaptPathsForClaude(content) {
182
+ return content
183
+ .replace(/\.github\/copilot-instructions\.md/g, 'CLAUDE.md')
184
+ .replace(/\.github\/prompts\/_partials\//g, '.claude/commands/_partials/');
185
+ }
186
+
187
+ /**
188
+ * Copy a directory recursively
189
+ * @param {string} src - Source directory
190
+ * @param {string} dest - Destination directory
191
+ * @param {Object} options - Copy options
192
+ * @param {boolean} options.force - Overwrite existing files
193
+ * @param {boolean} options.dryRun - Only show what would be copied
194
+ * @param {(name: string) => string} [options.renameFile] - Optional file rename function
195
+ * @param {(content: string) => string} [options.transformContent] - Optional content transform
196
+ * @param {(name: string) => boolean} [options.filter] - Optional file filter function
197
+ * @returns {{ written: number, planned: number }} Number of written/planned files
198
+ */
199
+ function copyDir(src, dest, options = {}) {
200
+ const {
201
+ force = false,
202
+ dryRun = false,
203
+ renameFile = (name) => name,
204
+ transformContent = null,
205
+ filter = null,
206
+ dirFilter = null,
207
+ } = options;
208
+ const totals = { written: 0, planned: 0 };
209
+
210
+ if (!fs.existsSync(src)) {
211
+ return totals;
212
+ }
213
+
214
+ if (!dryRun && !fs.existsSync(dest)) {
215
+ fs.mkdirSync(dest, { recursive: true });
216
+ }
217
+
218
+ const entries = fs.readdirSync(src, { withFileTypes: true });
219
+
220
+ for (const entry of entries) {
221
+ const srcPath = path.join(src, entry.name);
222
+
223
+ if (entry.isDirectory()) {
224
+ if (dirFilter && !dirFilter(entry.name)) {
225
+ continue;
226
+ }
227
+ const nestedOptions = { ...options };
228
+ if (entry.name === '_partials' && options.partialsFilter) {
229
+ nestedOptions.filter = options.partialsFilter;
230
+ }
231
+ const nested = copyDir(srcPath, path.join(dest, entry.name), nestedOptions);
232
+ totals.written += nested.written;
233
+ totals.planned += nested.planned;
234
+ } else {
235
+ if (filter && !filter(entry.name)) {
236
+ continue;
237
+ }
238
+
239
+ const destName = renameFile(entry.name);
240
+ const destPath = path.join(dest, destName);
241
+ const exists = fs.existsSync(destPath);
242
+
243
+ if (exists && !force) {
244
+ warn(`Skipping existing file: ${path.relative(process.cwd(), destPath)}`);
245
+ continue;
246
+ }
247
+
248
+ totals.planned++;
249
+
250
+ if (dryRun) {
251
+ info(`Would copy: ${path.relative(process.cwd(), destPath)}`);
252
+ } else {
253
+ if (transformContent) {
254
+ const rawContent = fs.readFileSync(srcPath, 'utf-8');
255
+ fs.writeFileSync(destPath, transformContent(rawContent));
256
+ } else {
257
+ fs.copyFileSync(srcPath, destPath);
258
+ }
259
+ totals.written++;
260
+ }
261
+ }
262
+ }
263
+
264
+ return totals;
265
+ }
266
+
267
+ function getInstallScope(options = {}) {
268
+ const {
269
+ promptsOnly = false,
270
+ partialsOnly = false,
271
+ skillsOnly = false,
272
+ instructionsOnly = false,
273
+ } = options;
274
+
275
+ const hasSpecificFlag = promptsOnly || partialsOnly || skillsOnly || instructionsOnly;
276
+
277
+ return {
278
+ shouldInstallPrompts: !hasSpecificFlag || promptsOnly || partialsOnly,
279
+ shouldInstallInstructions: !hasSpecificFlag || instructionsOnly,
280
+ shouldInstallSkills: !hasSpecificFlag || skillsOnly,
281
+ };
282
+ }
283
+
284
+ function getChangeCount(result, dryRun) {
285
+ return dryRun ? result.planned : result.written;
286
+ }
287
+
288
+ function ensureConfigFile({ dryRun = false } = {}) {
289
+ const configPath = path.join(process.cwd(), '.agents-toolkit.json');
290
+
291
+ if (fs.existsSync(configPath)) {
292
+ return { written: 0, planned: 0 };
293
+ }
294
+
295
+ if (dryRun) {
296
+ info('Would create .agents-toolkit.json');
297
+ return { written: 0, planned: 1 };
298
+ }
299
+
300
+ fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
301
+ success('Created .agents-toolkit.json config file');
302
+ return { written: 1, planned: 1 };
303
+ }
304
+
305
+ function installCopilotInstructions({ targetDir, dryRun = false } = {}) {
306
+ const result = { written: 0, planned: 0 };
307
+ const copilotInstructionsPath = path.join(targetDir, 'copilot-instructions.md');
308
+ const templatePath = path.join(TEMPLATES_DIR, 'agents', 'copilot-instructions.md');
309
+
310
+ if (!fs.existsSync(templatePath)) {
311
+ return result;
312
+ }
313
+
314
+ const templateContent = fs.readFileSync(templatePath, 'utf-8');
315
+
316
+ if (fs.existsSync(copilotInstructionsPath)) {
317
+ const existingContent = fs.readFileSync(copilotInstructionsPath, 'utf-8');
318
+ const marker = '## 🔄 Copilot Agent Workflow';
319
+
320
+ if (existingContent.includes(marker)) {
321
+ info('copilot-instructions.md already contains key sections');
322
+ return result;
323
+ }
324
+
325
+ result.planned++;
326
+ if (dryRun) {
327
+ info('Would append key sections to existing copilot-instructions.md');
328
+ return result;
329
+ }
330
+
331
+ const sectionsToAppend = templateContent.split('\n').slice(4).join('\n');
332
+ const newContent = `${existingContent}\n\n<!-- Added by agents-toolkit -->\n${sectionsToAppend}`;
333
+ fs.writeFileSync(copilotInstructionsPath, newContent);
334
+ success('Appended key sections to existing copilot-instructions.md');
335
+ result.written++;
336
+ return result;
337
+ }
338
+
339
+ result.planned++;
340
+ if (dryRun) {
341
+ info('Would create copilot-instructions.md');
342
+ return result;
343
+ }
344
+
345
+ fs.writeFileSync(copilotInstructionsPath, templateContent);
346
+ success('Created copilot-instructions.md with key sections');
347
+ result.written++;
348
+ return result;
349
+ }
350
+
351
+ function getAgentsTemplateBody(templateContent) {
352
+ const lines = templateContent.split('\n');
353
+ const dividerIndex = lines.indexOf('---');
354
+
355
+ if (dividerIndex === -1) {
356
+ return templateContent;
357
+ }
358
+
359
+ return lines.slice(dividerIndex + 1).join('\n').trimStart();
360
+ }
361
+
362
+ function installAgentsFile(options = {}) {
363
+ const {
364
+ templatePath,
365
+ force = false,
366
+ append = false,
367
+ dryRun = false,
368
+ } = options;
369
+
370
+ const result = { written: 0, planned: 0 };
371
+ const agentsPath = path.join(process.cwd(), 'AGENTS.md');
372
+
373
+ if (!fs.existsSync(templatePath)) {
374
+ return result;
375
+ }
376
+
377
+ const agentsExists = fs.existsSync(agentsPath);
378
+
379
+ if (!agentsExists || force) {
380
+ result.planned++;
381
+ if (dryRun) {
382
+ info(agentsExists ? 'Would update AGENTS.md in project root' : 'Would create AGENTS.md in project root');
383
+ return result;
384
+ }
385
+
386
+ fs.copyFileSync(templatePath, agentsPath);
387
+ success(agentsExists ? 'Updated AGENTS.md in project root' : 'Created AGENTS.md in project root');
388
+ result.written++;
389
+ return result;
390
+ }
391
+
392
+ if (!append) {
393
+ info('AGENTS.md already exists in project root (use --force to overwrite or --append to merge)');
394
+ return result;
395
+ }
396
+
397
+ const existingContent = fs.readFileSync(agentsPath, 'utf-8');
398
+ const mergeMarker = '## 🔄 Agent Workflow (Complex Tasks)';
399
+
400
+ if (existingContent.includes(mergeMarker)) {
401
+ info('AGENTS.md already contains workflow sections');
402
+ return result;
403
+ }
404
+
405
+ result.planned++;
406
+ if (dryRun) {
407
+ info('Would append missing sections to AGENTS.md');
408
+ return result;
409
+ }
410
+
411
+ const templateContent = fs.readFileSync(templatePath, 'utf-8');
412
+ const templateBody = getAgentsTemplateBody(templateContent);
413
+ const mergedContent = `${existingContent}\n\n<!-- Added by agents-toolkit (--append) -->\n\n${templateBody}`;
414
+ fs.writeFileSync(agentsPath, mergedContent);
415
+ success('Appended missing sections to AGENTS.md');
416
+ result.written++;
417
+ return result;
418
+ }
419
+
420
+ function installGitBasedTarget(options = {}, target = 'copilot') {
421
+ const {
422
+ force = false,
423
+ append = false,
424
+ dryRun = false,
425
+ filters = { stack: 'all', tracker: 'all' },
426
+ } = options;
427
+ const isCodex = target === 'codex';
428
+ const targetDir = getTargetDir();
429
+ const scope = getInstallScope(options);
430
+ let totalChanges = 0;
431
+
432
+ const makeFilter = (category) => (name) => {
433
+ const basename = name.replace(/\.(prompt\.md|instructions\.md|md)$/, '');
434
+ return shouldIncludeFile(basename, category, filters);
435
+ };
436
+
437
+ const promptsFilter = makeFilter('prompts');
438
+ const partialsFilter = makeFilter('partials');
439
+
440
+ log(isCodex ? '\n⚡ Codex Installer\n' : '\n📦 Agents Toolkit Installer\n', 'bright');
441
+
442
+ if (dryRun) {
443
+ info('Dry run mode - no files will be copied\n');
444
+ }
445
+
446
+ if (scope.shouldInstallPrompts) {
447
+ info('Installing prompts...');
448
+ const result = copyDir(path.join(TEMPLATES_DIR, 'shared', 'prompts'), path.join(targetDir, 'prompts'), { force, dryRun, filter: promptsFilter, partialsFilter });
449
+ totalChanges += getChangeCount(result, dryRun);
450
+
451
+ if (!dryRun && result.written > 0) {
452
+ success(`Installed ${result.written} prompt files`);
453
+ }
454
+ }
455
+
456
+ if (scope.shouldInstallInstructions) {
457
+ info('Installing instructions...');
458
+ const result = copyDir(path.join(TEMPLATES_DIR, 'shared', 'instructions'), path.join(targetDir, 'instructions'), { force, dryRun, filter: makeFilter('instructions') });
459
+ totalChanges += getChangeCount(result, dryRun);
460
+
461
+ if (!dryRun && result.written > 0) {
462
+ success(`Installed ${result.written} instruction files`);
463
+ }
464
+ }
465
+
466
+ if (scope.shouldInstallSkills) {
467
+ info('Installing skills...');
468
+ const result = copyDir(path.join(TEMPLATES_DIR, 'shared', 'skills'), path.join(targetDir, 'skills'), { force, dryRun, dirFilter: makeFilter('skills') });
469
+ totalChanges += getChangeCount(result, dryRun);
470
+
471
+ if (!dryRun && result.written > 0) {
472
+ success(`Installed ${result.written} skill files`);
473
+ }
474
+ }
475
+
476
+ const configResult = ensureConfigFile({ dryRun });
477
+ totalChanges += getChangeCount(configResult, dryRun);
478
+
479
+ if (scope.shouldInstallInstructions && !isCodex) {
480
+ const copilotInstructionsResult = installCopilotInstructions({ targetDir, dryRun });
481
+ totalChanges += getChangeCount(copilotInstructionsResult, dryRun);
482
+ }
483
+
484
+ if (scope.shouldInstallInstructions) {
485
+ const agentsTemplatePath = isCodex
486
+ ? path.join(TEMPLATES_DIR, 'agents', 'AGENTS.codex.md')
487
+ : path.join(TEMPLATES_DIR, 'agents', 'AGENTS.md');
488
+ const agentsResult = installAgentsFile({ templatePath: agentsTemplatePath, force, append, dryRun });
489
+ totalChanges += getChangeCount(agentsResult, dryRun);
490
+ }
491
+
492
+ console.log('');
493
+ if (dryRun) {
494
+ info(`Dry run complete. ${totalChanges} files would be installed.`);
495
+ } else if (totalChanges > 0) {
496
+ success(`Installation complete! ${totalChanges} files installed.`);
497
+ console.log('');
498
+ info('Next steps:');
499
+ console.log(' 1. Update .agents-toolkit.json with your Jira project key');
500
+ if (isCodex) {
501
+ console.log(' 2. Review AGENTS.md in the project root');
502
+ console.log(' 3. Run Codex from this project root');
503
+ } else {
504
+ console.log(' 2. Configure Atlassian MCP in VS Code');
505
+ console.log(' 3. Run prompts via Command Palette > "GitHub Copilot: Run Prompt"');
506
+ }
507
+ } else {
508
+ warn('No new files installed. Use --force to overwrite existing files.');
509
+ }
510
+ console.log('');
511
+ }
512
+
513
+ /**
514
+ * Install prompts to target directory
515
+ * @param {Object} options - Install options
516
+ */
517
+ function install(options = {}) {
518
+ installGitBasedTarget(options, 'copilot');
519
+ }
520
+
521
+ /**
522
+ * Install files for Codex
523
+ * @param {Object} options - Install options
524
+ */
525
+ function installCodex(options = {}) {
526
+ installGitBasedTarget(options, 'codex');
527
+ }
528
+
529
+ /**
530
+ * Install Claude Code files (CLAUDE.md + .claude/commands/)
531
+ * @param {Object} options - Install options
532
+ */
533
+ function installClaude(options = {}) {
534
+ const { force = false, dryRun = false, filters = { stack: 'all', tracker: 'all' } } = options;
535
+ const scope = getInstallScope(options);
536
+ const claudeDir = getClaudeTargetDir();
537
+ const githubDir = getTargetDir();
538
+ let totalChanges = 0;
539
+
540
+ const makeFilter = (category) => (name) => {
541
+ const basename = name.replace(/\.(prompt\.md|instructions\.md|md)$/, '');
542
+ return shouldIncludeFile(basename, category, filters);
543
+ };
544
+
545
+ const promptsFilter = makeFilter('prompts');
546
+ const partialsFilter = makeFilter('partials');
547
+
548
+ log('\n🤖 Claude Code Installer\n', 'bright');
549
+
550
+ if (dryRun) {
551
+ info('Dry run mode - no files will be copied\n');
552
+ }
553
+
554
+ if (scope.shouldInstallPrompts) {
555
+ info('Installing slash commands...');
556
+ const result = copyDir(path.join(TEMPLATES_DIR, 'shared', 'prompts'), path.join(claudeDir, 'commands'), {
557
+ force,
558
+ dryRun,
559
+ filter: promptsFilter,
560
+ partialsFilter,
561
+ renameFile: (name) => name.replace(/\.prompt\.md$/, '.md'),
562
+ transformContent: (content) => adaptPathsForClaude(stripCopilotFrontmatter(content)),
563
+ });
564
+ totalChanges += getChangeCount(result, dryRun);
565
+
566
+ if (!dryRun && result.written > 0) {
567
+ success(`Installed ${result.written} command files to .claude/commands/`);
568
+ }
569
+ }
570
+
571
+ if (scope.shouldInstallInstructions) {
572
+ info('Installing instructions...');
573
+ const result = copyDir(path.join(TEMPLATES_DIR, 'shared', 'instructions'), path.join(githubDir, 'instructions'), { force, dryRun, filter: makeFilter('instructions') });
574
+ totalChanges += getChangeCount(result, dryRun);
575
+
576
+ if (!dryRun && result.written > 0) {
577
+ success(`Installed ${result.written} instruction files`);
578
+ }
579
+ }
580
+
581
+ if (scope.shouldInstallSkills) {
582
+ info('Installing skills...');
583
+ const result = copyDir(path.join(TEMPLATES_DIR, 'shared', 'skills'), path.join(githubDir, 'skills'), { force, dryRun, dirFilter: makeFilter('skills') });
584
+ totalChanges += getChangeCount(result, dryRun);
585
+
586
+ if (!dryRun && result.written > 0) {
587
+ success(`Installed ${result.written} skill files`);
588
+ }
589
+ }
590
+
591
+ if (scope.shouldInstallInstructions) {
592
+ const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md');
593
+ const claudeMdTemplate = path.join(TEMPLATES_DIR, 'agents', 'CLAUDE.md');
594
+
595
+ if (fs.existsSync(claudeMdTemplate)) {
596
+ const exists = fs.existsSync(claudeMdPath);
597
+ if (exists && !force) {
598
+ info('CLAUDE.md already exists (use --force to overwrite)');
599
+ } else {
600
+ totalChanges += 1;
601
+ if (dryRun) {
602
+ info(exists ? 'Would update CLAUDE.md' : 'Would create CLAUDE.md');
603
+ } else {
604
+ fs.copyFileSync(claudeMdTemplate, claudeMdPath);
605
+ success(exists ? 'Updated CLAUDE.md' : 'Created CLAUDE.md');
606
+ }
607
+ }
608
+ }
609
+ }
610
+
611
+ const configResult = ensureConfigFile({ dryRun });
612
+ totalChanges += getChangeCount(configResult, dryRun);
613
+
614
+ console.log('');
615
+ if (dryRun) {
616
+ info(`Dry run complete. ${totalChanges} files would be installed.`);
617
+ } else if (totalChanges > 0) {
618
+ success(`Installation complete! ${totalChanges} files installed.`);
619
+ console.log('');
620
+ info('Next steps:');
621
+ console.log(' 1. Update .agents-toolkit.json with your Jira project key');
622
+ console.log(' 2. Configure Atlassian MCP in Claude Code settings');
623
+ console.log(' 3. Run slash commands with /analyze-ticket, /work-ticket, etc.');
624
+ } else {
625
+ warn('No new files installed. Use --force to overwrite existing files.');
626
+ }
627
+ console.log('');
628
+ }
629
+
630
+ /**
631
+ * List available prompts
632
+ */
633
+ function list() {
634
+ log('\n📋 Available Prompts\n', 'bright');
635
+
636
+ const promptsDir = path.join(TEMPLATES_DIR, 'shared', 'prompts');
637
+
638
+ if (!fs.existsSync(promptsDir)) {
639
+ error('Templates directory not found');
640
+ return;
641
+ }
642
+
643
+ const prompts = fs.readdirSync(promptsDir)
644
+ .filter(f => f.endsWith('.prompt.md'))
645
+ .map(f => f.replace('.prompt.md', ''));
646
+
647
+ log('Workflow Prompts:', 'cyan');
648
+ const workflowPrompts = ['analyze-ticket', 'create-plan', 'work-ticket', 'prepare-pr', 'create-pr', 'finalize-pr'];
649
+ workflowPrompts.forEach((p, i) => {
650
+ if (prompts.includes(p)) {
651
+ console.log(` ${i + 1}. ${p}`);
652
+ }
653
+ });
654
+
655
+ console.log('');
656
+ log('Utility Prompts:', 'cyan');
657
+ const utilityPrompts = prompts.filter(p => !workflowPrompts.includes(p));
658
+ utilityPrompts.forEach(p => {
659
+ console.log(` • ${p}`);
660
+ });
661
+
662
+ console.log('');
663
+ log('Partials:', 'cyan');
664
+ const partialsDir = path.join(promptsDir, '_partials');
665
+ if (fs.existsSync(partialsDir)) {
666
+ const partials = fs.readdirSync(partialsDir)
667
+ .filter(f => f.endsWith('.md') && f !== 'README.md');
668
+ partials.forEach(p => {
669
+ console.log(` • ${p.replace('.md', '')}`);
670
+ });
671
+ }
672
+
673
+ console.log('');
674
+ log('Skills:', 'cyan');
675
+ const skillsDir = path.join(TEMPLATES_DIR, 'shared', 'skills');
676
+ if (fs.existsSync(skillsDir)) {
677
+ const skills = fs.readdirSync(skillsDir, { withFileTypes: true })
678
+ .filter(d => d.isDirectory())
679
+ .map(d => d.name);
680
+ skills.forEach(s => {
681
+ console.log(` • ${s}`);
682
+ });
683
+ }
684
+ console.log('');
685
+ }
686
+
687
+ /**
688
+ * Show help message
689
+ */
690
+ function showHelp() {
691
+ log('\n📦 Agents Toolkit\n', 'bright');
692
+ console.log('Usage: agents-toolkit <command> [options]\n');
693
+
694
+ log('Commands:', 'cyan');
695
+ console.log(' install Install prompts (default target: copilot)');
696
+ console.log(' list List available prompts');
697
+ console.log(' update Update existing prompts (alias for install --force)');
698
+ console.log(' help Show this help message');
699
+
700
+ console.log('');
701
+ log('Options:', 'cyan');
702
+ console.log(' --force, -f Overwrite existing files');
703
+ console.log(' --target <name> Target installer: copilot | claude | codex');
704
+ console.log(' --stack <name> Filter by stack: react | wordpress | all (default: all)');
705
+ console.log(' --tracker <name> Filter by tracker: jira | github | all (default: all)');
706
+ console.log(' --claude Install for Claude Code (.claude/commands/ + CLAUDE.md)');
707
+ console.log(' --codex Install for Codex (AGENTS.md + shared .github files)');
708
+ console.log(' --append Append missing AGENTS.md sections instead of overwriting');
709
+ console.log(' --prompts-only Only install prompts (no instructions/skills)');
710
+ console.log(' --instructions-only Only install instructions');
711
+ console.log(' --partials-only Only install partials');
712
+ console.log(' --skills-only Only install skills');
713
+ console.log(' --dry-run Show what would be installed');
714
+
715
+ console.log('');
716
+ log('Examples:', 'cyan');
717
+ console.log(' npx agents-toolkit install # All content');
718
+ console.log(' npx agents-toolkit install --stack react # React/TS only');
719
+ console.log(' npx agents-toolkit install --stack wordpress # PHP/WordPress only');
720
+ console.log(' npx agents-toolkit install --tracker github # GitHub Issues workflow');
721
+ console.log(' npx agents-toolkit install --tracker jira # Jira workflow');
722
+ console.log(' npx agents-toolkit install --target codex');
723
+ console.log(' npx agents-toolkit install --target=claude');
724
+ console.log(' npx agents-toolkit install --force');
725
+ console.log(' npx agents-toolkit install --append --instructions-only');
726
+ console.log(' npx agents-toolkit install --claude --force');
727
+ console.log(' npx agents-toolkit install --codex --force');
728
+ console.log(' npx agents-toolkit install --prompts-only');
729
+ console.log(' npx agents-toolkit list');
730
+ console.log('');
731
+ }
732
+
733
+ /**
734
+ * Parse command line arguments
735
+ * @returns {Object} Parsed arguments
736
+ */
737
+ function parseArgs() {
738
+ const args = process.argv.slice(2);
739
+ const command = args[0] || 'help';
740
+ const flags = args.slice(1);
741
+
742
+ let target = null;
743
+ let stack = null;
744
+ let tracker = null;
745
+ for (let i = 0; i < flags.length; i++) {
746
+ const arg = flags[i];
747
+ if (arg === '--target') {
748
+ const value = flags[i + 1];
749
+ if (value && !value.startsWith('-')) {
750
+ target = value;
751
+ i++;
752
+ } else {
753
+ target = '';
754
+ }
755
+ } else if (arg.startsWith('--target=')) {
756
+ target = arg.split('=').slice(1).join('=');
757
+ } else if (arg === '--stack') {
758
+ const value = flags[i + 1];
759
+ if (value && !value.startsWith('-')) {
760
+ stack = value;
761
+ i++;
762
+ } else {
763
+ stack = '';
764
+ }
765
+ } else if (arg.startsWith('--stack=')) {
766
+ stack = arg.split('=').slice(1).join('=');
767
+ } else if (arg === '--tracker') {
768
+ const value = flags[i + 1];
769
+ if (value && !value.startsWith('-')) {
770
+ tracker = value;
771
+ i++;
772
+ } else {
773
+ tracker = '';
774
+ }
775
+ } else if (arg.startsWith('--tracker=')) {
776
+ tracker = arg.split('=').slice(1).join('=');
777
+ }
778
+ }
779
+
780
+ const options = {
781
+ force: flags.includes('--force') || flags.includes('-f'),
782
+ promptsOnly: flags.includes('--prompts-only'),
783
+ partialsOnly: flags.includes('--partials-only'),
784
+ skillsOnly: flags.includes('--skills-only'),
785
+ instructionsOnly: flags.includes('--instructions-only'),
786
+ dryRun: flags.includes('--dry-run'),
787
+ claude: flags.includes('--claude'),
788
+ codex: flags.includes('--codex'),
789
+ append: flags.includes('--append'),
790
+ target,
791
+ stack,
792
+ tracker,
793
+ };
794
+
795
+ return { command, options };
796
+ }
797
+
798
+ function resolveInstallTarget(options = {}) {
799
+ const legacyTargets = [];
800
+
801
+ if (options.claude) {
802
+ legacyTargets.push('claude');
803
+ }
804
+ if (options.codex) {
805
+ legacyTargets.push('codex');
806
+ }
807
+
808
+ let explicitTarget = null;
809
+ if (options.target !== null && options.target !== undefined) {
810
+ explicitTarget = options.target.trim().toLowerCase();
811
+ if (!explicitTarget) {
812
+ error('Missing value for --target. Use copilot, claude, or codex.');
813
+ process.exit(1);
814
+ }
815
+ if (!['copilot', 'claude', 'codex'].includes(explicitTarget)) {
816
+ error(`Invalid --target value: ${options.target}. Use copilot, claude, or codex.`);
817
+ process.exit(1);
818
+ }
819
+ }
820
+
821
+ if (legacyTargets.length > 1) {
822
+ error('Use either --claude or --codex, not both.');
823
+ process.exit(1);
824
+ }
825
+
826
+ if (explicitTarget && legacyTargets.length > 0 && legacyTargets[0] !== explicitTarget) {
827
+ error(`Conflicting target flags: --target ${explicitTarget} and --${legacyTargets[0]}.`);
828
+ process.exit(1);
829
+ }
830
+
831
+ if (explicitTarget) {
832
+ return explicitTarget;
833
+ }
834
+
835
+ if (legacyTargets.length === 1) {
836
+ return legacyTargets[0];
837
+ }
838
+
839
+ return 'copilot';
840
+ }
841
+
842
+ /**
843
+ * Resolve stack and tracker filters from flags or config file
844
+ * @param {Object} options - Parsed CLI options
845
+ * @returns {{ stack: string, tracker: string }} Resolved filters
846
+ */
847
+ function resolveFilters(options = {}) {
848
+ const validStacks = ['react', 'wordpress', 'all'];
849
+ const validTrackers = ['jira', 'github', 'all'];
850
+
851
+ let stack = 'all';
852
+ let tracker = 'all';
853
+
854
+ const configPath = path.join(process.cwd(), '.agents-toolkit.json');
855
+ if (fs.existsSync(configPath)) {
856
+ try {
857
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
858
+ if (config.stack) stack = config.stack;
859
+ if (config.tracker) tracker = config.tracker;
860
+ } catch {
861
+ // Ignore invalid config
862
+ }
863
+ }
864
+
865
+ if (options.stack !== null && options.stack !== undefined) {
866
+ const value = options.stack.trim().toLowerCase();
867
+ if (!value) {
868
+ error('Missing value for --stack. Use react, wordpress, or all.');
869
+ process.exit(1);
870
+ }
871
+ if (!validStacks.includes(value)) {
872
+ error(`Invalid --stack value: ${options.stack}. Use react, wordpress, or all.`);
873
+ process.exit(1);
874
+ }
875
+ stack = value;
876
+ }
877
+
878
+ if (options.tracker !== null && options.tracker !== undefined) {
879
+ const value = options.tracker.trim().toLowerCase();
880
+ if (!value) {
881
+ error('Missing value for --tracker. Use jira, github, or all.');
882
+ process.exit(1);
883
+ }
884
+ if (!validTrackers.includes(value)) {
885
+ error(`Invalid --tracker value: ${options.tracker}. Use jira, github, or all.`);
886
+ process.exit(1);
887
+ }
888
+ tracker = value;
889
+ }
890
+
891
+ return { stack, tracker };
892
+ }
893
+
894
+ /**
895
+ * Main CLI entry point
896
+ */
897
+ function main() {
898
+ const { command, options } = parseArgs();
899
+ const target = (command === 'install' || command === 'update')
900
+ ? resolveInstallTarget(options)
901
+ : null;
902
+ const filters = (command === 'install' || command === 'update')
903
+ ? resolveFilters(options)
904
+ : { stack: 'all', tracker: 'all' };
905
+
906
+ switch (command) {
907
+ case 'install':
908
+ if (target === 'claude') {
909
+ installClaude({ ...options, filters });
910
+ } else if (target === 'codex') {
911
+ installCodex({ ...options, filters });
912
+ } else {
913
+ install({ ...options, filters });
914
+ }
915
+ break;
916
+ case 'update':
917
+ if (target === 'claude') {
918
+ installClaude({ ...options, force: true, filters });
919
+ } else if (target === 'codex') {
920
+ installCodex({ ...options, force: true, filters });
921
+ } else {
922
+ install({ ...options, force: true, filters });
923
+ }
924
+ break;
925
+ case 'list':
926
+ list();
927
+ break;
928
+ case 'help':
929
+ case '--help':
930
+ case '-h':
931
+ showHelp();
932
+ break;
933
+ default:
934
+ error(`Unknown command: ${command}`);
935
+ showHelp();
936
+ process.exit(1);
937
+ }
938
+ }
939
+
940
+ main();