agents-config 1.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 (90) hide show
  1. package/AGENTS.md +490 -0
  2. package/LICENSE +21 -0
  3. package/README.md +254 -0
  4. package/adapters/claude.template.md +77 -0
  5. package/adapters/codex.template.md +72 -0
  6. package/adapters/copilot.template.md +68 -0
  7. package/adapters/cursor.template.md +69 -0
  8. package/adapters/gemini.template.md +73 -0
  9. package/adapters/windsurf.template.md +81 -0
  10. package/bin/agents-init.js +699 -0
  11. package/bin/postinstall.js +28 -0
  12. package/instructions/development-standards.instructions.md +47 -0
  13. package/instructions/github-issue.instructions.md +324 -0
  14. package/instructions/github-release-notes.instructions.md +888 -0
  15. package/instructions/mui.instructions.md +50 -0
  16. package/instructions/storybook.instructions.md +55 -0
  17. package/instructions/web-interface-guidelines.instructions.md +331 -0
  18. package/package.json +56 -0
  19. package/prompts/create-pr.prompt.md +78 -0
  20. package/prompts/scaffold-component.prompt.md +57 -0
  21. package/rules/accessibility.md +36 -0
  22. package/rules/component-architecture.md +34 -0
  23. package/rules/gemini.md +547 -0
  24. package/rules/mui.md +491 -0
  25. package/rules/react-19-compiler.md +26 -0
  26. package/rules/spec-driven-development.md +36 -0
  27. package/rules/supabase.md +40 -0
  28. package/rules/tailwind-v4.md +29 -0
  29. package/rules/three-js-react.md +76 -0
  30. package/rules/web-performance.md +29 -0
  31. package/schemas/agents-project.schema.json +78 -0
  32. package/skills/accessibility-audit/SKILL.md +39 -0
  33. package/skills/integrate-gemini/SKILL.md +124 -0
  34. package/skills/scaffold-component/SKILL.md +77 -0
  35. package/skills/vercel-react-best-practices/AGENTS.md +2719 -0
  36. package/skills/vercel-react-best-practices/SKILL.md +125 -0
  37. package/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  38. package/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
  39. package/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
  40. package/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
  41. package/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
  42. package/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
  43. package/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  44. package/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
  45. package/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
  46. package/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  47. package/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  48. package/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
  49. package/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
  50. package/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
  51. package/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
  52. package/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
  53. package/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
  54. package/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
  55. package/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
  56. package/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
  57. package/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
  58. package/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
  59. package/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
  60. package/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
  61. package/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
  62. package/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
  63. package/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
  64. package/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  65. package/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
  66. package/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  67. package/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
  68. package/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
  69. package/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  70. package/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  71. package/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
  72. package/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  73. package/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
  74. package/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
  75. package/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
  76. package/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  77. package/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  78. package/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  79. package/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
  80. package/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  81. package/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
  82. package/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
  83. package/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
  84. package/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
  85. package/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
  86. package/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
  87. package/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
  88. package/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
  89. package/skills/workflows/sdd-workflow.md +49 -0
  90. package/skills/workflows/setup-orchestration.md +18 -0
@@ -0,0 +1,699 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * agents-init - Initialize AI agent configuration for a project
5
+ *
6
+ * Usage:
7
+ * npx agents-init # Interactive mode
8
+ * npx agents-init --dry-run # Preview without writing files
9
+ * npx agents-init --force # Overwrite existing files
10
+ */
11
+
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import readline from 'readline';
15
+ import { fileURLToPath } from 'url';
16
+
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
19
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
20
+
21
+ // ANSI colors for terminal output
22
+ const colors = {
23
+ reset: '\x1b[0m',
24
+ bright: '\x1b[1m',
25
+ dim: '\x1b[2m',
26
+ green: '\x1b[32m',
27
+ yellow: '\x1b[33m',
28
+ blue: '\x1b[34m',
29
+ cyan: '\x1b[36m',
30
+ red: '\x1b[31m',
31
+ };
32
+
33
+ const log = {
34
+ info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),
35
+ success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`),
36
+ warn: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`),
37
+ error: (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`),
38
+ header: (msg) => console.log(`\n${colors.bright}${colors.cyan}${msg}${colors.reset}\n`),
39
+ file: (msg) => console.log(` ${colors.dim}${msg}${colors.reset}`),
40
+ };
41
+
42
+ // Available agents and their config files
43
+ const AGENTS = {
44
+ copilot: {
45
+ name: 'GitHub Copilot',
46
+ file: '.github/copilot-instructions.md',
47
+ template: 'copilot.template.md',
48
+ },
49
+ claude: {
50
+ name: 'Claude (Anthropic)',
51
+ file: 'CLAUDE.md',
52
+ template: 'claude.template.md',
53
+ },
54
+ cursor: {
55
+ name: 'Cursor',
56
+ file: '.cursorrules',
57
+ template: 'cursor.template.md',
58
+ },
59
+ gemini: {
60
+ name: 'Gemini (Google)',
61
+ file: '.gemini/config.md',
62
+ template: 'gemini.template.md',
63
+ },
64
+ codex: {
65
+ name: 'Codex (OpenAI)',
66
+ file: '.codex/AGENTS.md',
67
+ template: 'codex.template.md',
68
+ },
69
+ windsurf: {
70
+ name: 'Windsurf/Codeium',
71
+ file: '.windsurfrules',
72
+ template: 'windsurf.template.md',
73
+ },
74
+ };
75
+
76
+ // Available frameworks/technologies
77
+ const FRAMEWORKS = {
78
+ next: 'Next.js',
79
+ react: 'React (Vite/CRA)',
80
+ remix: 'Remix',
81
+ astro: 'Astro',
82
+ };
83
+
84
+ const STYLING = {
85
+ tailwind: 'Tailwind CSS',
86
+ mui: 'Material-UI (MUI)',
87
+ vanilla: 'Vanilla CSS/CSS Modules',
88
+ styled: 'Styled Components',
89
+ };
90
+
91
+ const DATABASES = {
92
+ supabase: 'Supabase',
93
+ firebase: 'Firebase',
94
+ prisma: 'Prisma',
95
+ none: 'None/Other',
96
+ };
97
+
98
+ // Core rules always included
99
+ const CORE_RULES = ['accessibility', 'component-architecture', 'spec-driven-development'];
100
+
101
+ // Core skills always included
102
+ const CORE_SKILLS = ['accessibility-audit', 'scaffold-component'];
103
+
104
+ // Core instructions always included
105
+ const CORE_INSTRUCTIONS = ['development-standards'];
106
+
107
+ // Parse command line arguments
108
+ const args = process.argv.slice(2);
109
+ const isDryRun = args.includes('--dry-run');
110
+ const isForce = args.includes('--force');
111
+ const isHelp = args.includes('--help') || args.includes('-h');
112
+
113
+ if (isHelp) {
114
+ console.log(`
115
+ ${colors.bright}agents-init${colors.reset} - Initialize AI agent configuration
116
+
117
+ ${colors.bright}Usage:${colors.reset}
118
+ npx agents-init Interactive mode
119
+ npx agents-init --dry-run Preview without writing files
120
+ npx agents-init --force Overwrite existing files
121
+ npx agents-init --help Show this help message
122
+
123
+ ${colors.bright}Description:${colors.reset}
124
+ Sets up AI agent configuration files for your project:
125
+
126
+ 1. Creates a .agents/ folder with relevant rules, skills, and instructions
127
+ 2. Creates adapter files for your AI coding assistants
128
+ 3. Creates .agents-project.json for project-specific config
129
+
130
+ ${colors.bright}What gets copied to .agents/:${colors.reset}
131
+ - AGENTS.md (core guidelines)
132
+ - rules/ (coding standards based on your stack)
133
+ - skills/ (specialized workflows)
134
+ - instructions/ (process guidelines)
135
+
136
+ ${colors.bright}Supported Agents:${colors.reset}
137
+ - GitHub Copilot (.github/copilot-instructions.md)
138
+ - Claude (CLAUDE.md)
139
+ - Cursor (.cursorrules)
140
+ - Gemini (.gemini/config.md)
141
+ - Codex (.codex/AGENTS.md)
142
+ - Windsurf (.windsurfrules)
143
+
144
+ ${colors.bright}Package:${colors.reset}
145
+ npm: https://www.npmjs.com/package/agents-config
146
+
147
+ ${colors.bright}More Info:${colors.reset}
148
+ https://github.com/ericthayer/agents-config
149
+ `);
150
+ process.exit(0);
151
+ }
152
+
153
+ // Create readline interface for interactive prompts
154
+ function createPrompt() {
155
+ return readline.createInterface({
156
+ input: process.stdin,
157
+ output: process.stdout,
158
+ });
159
+ }
160
+
161
+ async function question(rl, query) {
162
+ return new Promise((resolve) => {
163
+ rl.question(query, resolve);
164
+ });
165
+ }
166
+
167
+ async function multiSelect(rl, prompt, options) {
168
+ console.log(`\n${colors.bright}${prompt}${colors.reset}`);
169
+ console.log(`${colors.cyan}(Enter comma-separated numbers, or 'all')${colors.reset}\n`);
170
+
171
+ const keys = Object.keys(options);
172
+ keys.forEach((key, i) => {
173
+ console.log(` ${i + 1}. ${options[key]}`);
174
+ });
175
+
176
+ const answer = await question(rl, '\n> ');
177
+
178
+ if (answer.toLowerCase() === 'all') {
179
+ return keys;
180
+ }
181
+
182
+ const indices = answer.split(',').map(s => parseInt(s.trim()) - 1);
183
+ return indices.filter(i => i >= 0 && i < keys.length).map(i => keys[i]);
184
+ }
185
+
186
+ async function singleSelect(rl, prompt, options) {
187
+ console.log(`\n${colors.bright}${prompt}${colors.reset}\n`);
188
+
189
+ const keys = Object.keys(options);
190
+ keys.forEach((key, i) => {
191
+ console.log(` ${i + 1}. ${options[key]}`);
192
+ });
193
+
194
+ const answer = await question(rl, '\n> ');
195
+ const index = parseInt(answer.trim()) - 1;
196
+
197
+ if (index >= 0 && index < keys.length) {
198
+ return keys[index];
199
+ }
200
+ return keys[0]; // Default to first option
201
+ }
202
+
203
+ async function confirm(rl, prompt) {
204
+ const answer = await question(rl, `${colors.bright}${prompt} (y/n)${colors.reset} `);
205
+ return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
206
+ }
207
+
208
+ function detectExistingConfig(projectDir) {
209
+ const detected = {
210
+ agents: [],
211
+ framework: null,
212
+ styling: null,
213
+ database: null,
214
+ gemini: false,
215
+ storybook: false,
216
+ threejs: false,
217
+ hasAgentsFolder: false,
218
+ };
219
+
220
+ // Check for existing agent configs
221
+ Object.entries(AGENTS).forEach(([key, config]) => {
222
+ const filePath = path.join(projectDir, config.file);
223
+ if (fs.existsSync(filePath)) {
224
+ detected.agents.push(key);
225
+ }
226
+ });
227
+
228
+ // Check for existing .agents folder
229
+ if (fs.existsSync(path.join(projectDir, '.agents'))) {
230
+ detected.hasAgentsFolder = true;
231
+ }
232
+
233
+ // Check package.json for framework/dependencies
234
+ const pkgPath = path.join(projectDir, 'package.json');
235
+ if (fs.existsSync(pkgPath)) {
236
+ try {
237
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
238
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
239
+
240
+ // Framework detection
241
+ if (deps['next']) detected.framework = 'next';
242
+ else if (deps['remix'] || deps['@remix-run/react']) detected.framework = 'remix';
243
+ else if (deps['astro']) detected.framework = 'astro';
244
+ else if (deps['react']) detected.framework = 'react';
245
+
246
+ // Styling detection
247
+ if (deps['tailwindcss']) detected.styling = 'tailwind';
248
+ else if (deps['@mui/material']) detected.styling = 'mui';
249
+ else if (deps['styled-components']) detected.styling = 'styled';
250
+
251
+ // Database detection
252
+ if (deps['@supabase/supabase-js']) detected.database = 'supabase';
253
+ else if (deps['firebase']) detected.database = 'firebase';
254
+ else if (deps['@prisma/client']) detected.database = 'prisma';
255
+
256
+ // Additional feature detection
257
+ if (deps['@google/generative-ai']) detected.gemini = true;
258
+ if (deps['storybook'] || deps['@storybook/react'] || deps['@storybook/nextjs']) detected.storybook = true;
259
+ if (deps['three'] || deps['@react-three/fiber']) detected.threejs = true;
260
+
261
+ } catch (e) {
262
+ // Ignore parse errors
263
+ }
264
+ }
265
+
266
+ // Check for .gemini folder (indicates Gemini usage)
267
+ if (fs.existsSync(path.join(projectDir, '.gemini'))) {
268
+ detected.gemini = true;
269
+ }
270
+
271
+ return detected;
272
+ }
273
+
274
+ function determineRules(config) {
275
+ const rules = [...CORE_RULES];
276
+
277
+ if (config.styling === 'tailwind') rules.push('tailwind-v4');
278
+ if (config.styling === 'mui') rules.push('mui');
279
+ if (config.database === 'supabase') rules.push('supabase');
280
+ if (config.gemini) rules.push('gemini');
281
+ if (config.threejs) rules.push('three-js-react');
282
+
283
+ // Always include web-performance
284
+ rules.push('web-performance');
285
+
286
+ return rules;
287
+ }
288
+
289
+ function determineSkills(config) {
290
+ const skills = [...CORE_SKILLS];
291
+
292
+ if (config.gemini) skills.push('integrate-gemini');
293
+
294
+ // Always include vercel-react-best-practices (just the SKILL.md, not full rules subfolder)
295
+ skills.push('vercel-react-best-practices');
296
+
297
+ return skills;
298
+ }
299
+
300
+ function determineInstructions(config) {
301
+ const instructions = [...CORE_INSTRUCTIONS];
302
+
303
+ if (config.styling === 'mui') instructions.push('mui');
304
+ if (config.storybook) instructions.push('storybook');
305
+
306
+ return instructions;
307
+ }
308
+
309
+ function generateProjectConfig(config) {
310
+ const rulesInclude = determineRules(config);
311
+
312
+ return {
313
+ $schema: 'https://raw.githubusercontent.com/ericthayer/agents-config/main/schemas/agents-project.schema.json',
314
+ version: '1.0.0',
315
+ project: {
316
+ name: config.projectName || path.basename(process.cwd()),
317
+ framework: config.framework || 'react',
318
+ styling: config.styling || 'vanilla',
319
+ database: config.database || 'none',
320
+ },
321
+ agents: config.agents || ['copilot', 'claude'],
322
+ features: {
323
+ gemini: config.gemini || false,
324
+ storybook: config.storybook || false,
325
+ threejs: config.threejs || false,
326
+ },
327
+ rules: {
328
+ include: rulesInclude,
329
+ exclude: [],
330
+ },
331
+ overrides: {},
332
+ };
333
+ }
334
+
335
+ function copyFileSync(src, dest) {
336
+ const dir = path.dirname(dest);
337
+ if (!fs.existsSync(dir)) {
338
+ fs.mkdirSync(dir, { recursive: true });
339
+ }
340
+ fs.copyFileSync(src, dest);
341
+ }
342
+
343
+ function copyFolderSync(src, dest) {
344
+ if (!fs.existsSync(src)) return;
345
+
346
+ const dir = path.dirname(dest);
347
+ if (!fs.existsSync(dir)) {
348
+ fs.mkdirSync(dir, { recursive: true });
349
+ }
350
+
351
+ if (!fs.existsSync(dest)) {
352
+ fs.mkdirSync(dest, { recursive: true });
353
+ }
354
+
355
+ const entries = fs.readdirSync(src, { withFileTypes: true });
356
+ for (const entry of entries) {
357
+ const srcPath = path.join(src, entry.name);
358
+ const destPath = path.join(dest, entry.name);
359
+
360
+ if (entry.isDirectory()) {
361
+ copyFolderSync(srcPath, destPath);
362
+ } else {
363
+ fs.copyFileSync(srcPath, destPath);
364
+ }
365
+ }
366
+ }
367
+
368
+ function copyAgentsFolder(projectDir, config, filesToCreate) {
369
+ const agentsDir = path.join(projectDir, '.agents');
370
+ const rules = determineRules(config);
371
+ const skills = determineSkills(config);
372
+ const instructions = determineInstructions(config);
373
+
374
+ // AGENTS.md
375
+ const agentsMdSrc = path.join(PACKAGE_ROOT, 'AGENTS.md');
376
+ const agentsMdDest = path.join(agentsDir, 'AGENTS.md');
377
+ if (fs.existsSync(agentsMdSrc)) {
378
+ filesToCreate.push({
379
+ src: agentsMdSrc,
380
+ dest: agentsMdDest,
381
+ relativePath: '.agents/AGENTS.md',
382
+ type: 'file',
383
+ });
384
+ }
385
+
386
+ // Rules
387
+ for (const rule of rules) {
388
+ const src = path.join(PACKAGE_ROOT, 'rules', `${rule}.md`);
389
+ const dest = path.join(agentsDir, 'rules', `${rule}.md`);
390
+ if (fs.existsSync(src)) {
391
+ filesToCreate.push({
392
+ src,
393
+ dest,
394
+ relativePath: `.agents/rules/${rule}.md`,
395
+ type: 'file',
396
+ });
397
+ }
398
+ }
399
+
400
+ // Skills
401
+ for (const skill of skills) {
402
+ const srcDir = path.join(PACKAGE_ROOT, 'skills', skill);
403
+ const destDir = path.join(agentsDir, 'skills', skill);
404
+
405
+ if (fs.existsSync(srcDir)) {
406
+ // For vercel-react-best-practices, only copy SKILL.md and AGENTS.md (not the huge rules subfolder)
407
+ if (skill === 'vercel-react-best-practices') {
408
+ const skillMdSrc = path.join(srcDir, 'SKILL.md');
409
+ const skillMdDest = path.join(destDir, 'SKILL.md');
410
+ if (fs.existsSync(skillMdSrc)) {
411
+ filesToCreate.push({
412
+ src: skillMdSrc,
413
+ dest: skillMdDest,
414
+ relativePath: `.agents/skills/${skill}/SKILL.md`,
415
+ type: 'file',
416
+ });
417
+ }
418
+ const agentsMdSrc2 = path.join(srcDir, 'AGENTS.md');
419
+ const agentsMdDest2 = path.join(destDir, 'AGENTS.md');
420
+ if (fs.existsSync(agentsMdSrc2)) {
421
+ filesToCreate.push({
422
+ src: agentsMdSrc2,
423
+ dest: agentsMdDest2,
424
+ relativePath: `.agents/skills/${skill}/AGENTS.md`,
425
+ type: 'file',
426
+ });
427
+ }
428
+ } else {
429
+ // Copy entire skill folder
430
+ filesToCreate.push({
431
+ src: srcDir,
432
+ dest: destDir,
433
+ relativePath: `.agents/skills/${skill}/`,
434
+ type: 'folder',
435
+ });
436
+ }
437
+ }
438
+ }
439
+
440
+ // Instructions
441
+ for (const instruction of instructions) {
442
+ const src = path.join(PACKAGE_ROOT, 'instructions', `${instruction}.instructions.md`);
443
+ const dest = path.join(agentsDir, 'instructions', `${instruction}.instructions.md`);
444
+ if (fs.existsSync(src)) {
445
+ filesToCreate.push({
446
+ src,
447
+ dest,
448
+ relativePath: `.agents/instructions/${instruction}.instructions.md`,
449
+ type: 'file',
450
+ });
451
+ }
452
+ }
453
+
454
+ return filesToCreate;
455
+ }
456
+
457
+ function loadTemplate(templateName) {
458
+ const templatePath = path.join(PACKAGE_ROOT, 'adapters', templateName);
459
+ if (fs.existsSync(templatePath)) {
460
+ return fs.readFileSync(templatePath, 'utf8');
461
+ }
462
+ return null;
463
+ }
464
+
465
+ function processTemplate(template, config) {
466
+ if (!template) return '';
467
+
468
+ const projectConfig = generateProjectConfig(config);
469
+
470
+ return template
471
+ .replace(/\{\{PROJECT_NAME\}\}/g, projectConfig.project.name)
472
+ .replace(/\{\{FRAMEWORK\}\}/g, FRAMEWORKS[projectConfig.project.framework] || projectConfig.project.framework)
473
+ .replace(/\{\{STYLING\}\}/g, STYLING[projectConfig.project.styling] || projectConfig.project.styling)
474
+ .replace(/\{\{DATABASE\}\}/g, DATABASES[projectConfig.project.database] || projectConfig.project.database)
475
+ .replace(/\{\{RULES_LIST\}\}/g, projectConfig.rules.include.map(r => `- .agents/rules/${r}.md`).join('\n'))
476
+ .replace(/\{\{PACKAGE_PATH\}\}/g, '.agents');
477
+ }
478
+
479
+ function writeFile(filePath, content) {
480
+ const dir = path.dirname(filePath);
481
+ if (!fs.existsSync(dir)) {
482
+ fs.mkdirSync(dir, { recursive: true });
483
+ }
484
+ fs.writeFileSync(filePath, content, 'utf8');
485
+ }
486
+
487
+ async function main() {
488
+ log.header('🤖 agents-init - AI Agent Configuration');
489
+
490
+ const projectDir = process.cwd();
491
+ const rl = createPrompt();
492
+
493
+ try {
494
+ // Detect existing configuration
495
+ log.info('Scanning project...');
496
+ const detected = detectExistingConfig(projectDir);
497
+
498
+ if (detected.framework) {
499
+ log.info(`Detected framework: ${FRAMEWORKS[detected.framework]}`);
500
+ }
501
+ if (detected.styling) {
502
+ log.info(`Detected styling: ${STYLING[detected.styling]}`);
503
+ }
504
+ if (detected.database) {
505
+ log.info(`Detected database: ${DATABASES[detected.database]}`);
506
+ }
507
+ if (detected.gemini) {
508
+ log.info('Detected Gemini integration');
509
+ }
510
+ if (detected.storybook) {
511
+ log.info('Detected Storybook');
512
+ }
513
+ if (detected.threejs) {
514
+ log.info('Detected Three.js');
515
+ }
516
+ if (detected.hasAgentsFolder) {
517
+ log.warn('Existing .agents folder found');
518
+ if (!isForce) {
519
+ log.warn('Use --force to overwrite existing files');
520
+ }
521
+ }
522
+ if (detected.agents.length > 0) {
523
+ log.warn(`Existing agent configs found: ${detected.agents.join(', ')}`);
524
+ }
525
+
526
+ // Interactive prompts
527
+ const selectedAgents = await multiSelect(rl, 'Which AI agents do you use?',
528
+ Object.fromEntries(Object.entries(AGENTS).map(([k, v]) => [k, v.name]))
529
+ );
530
+
531
+ const framework = detected.framework || await singleSelect(rl, 'Which framework?', FRAMEWORKS);
532
+ const styling = detected.styling || await singleSelect(rl, 'Which styling approach?', STYLING);
533
+ const database = detected.database || await singleSelect(rl, 'Which database/backend?', DATABASES);
534
+
535
+ // Ask about Gemini if not detected
536
+ let gemini = detected.gemini;
537
+ if (!gemini && selectedAgents.includes('gemini')) {
538
+ gemini = true;
539
+ }
540
+ if (!gemini) {
541
+ gemini = await confirm(rl, '\nDoes this project use Gemini AI features?');
542
+ }
543
+
544
+ const config = {
545
+ projectName: path.basename(projectDir),
546
+ agents: selectedAgents,
547
+ framework,
548
+ styling,
549
+ database,
550
+ gemini,
551
+ storybook: detected.storybook,
552
+ threejs: detected.threejs,
553
+ };
554
+
555
+ // Collect all files to create
556
+ const filesToCreate = [];
557
+
558
+ // Add .agents folder contents
559
+ copyAgentsFolder(projectDir, config, filesToCreate);
560
+
561
+ // Add agent adapter files
562
+ for (const agentKey of selectedAgents) {
563
+ const agent = AGENTS[agentKey];
564
+ const filePath = path.join(projectDir, agent.file);
565
+ const exists = fs.existsSync(filePath);
566
+
567
+ if (exists && !isForce) {
568
+ // Skip existing files unless --force
569
+ continue;
570
+ }
571
+
572
+ const template = loadTemplate(agent.template);
573
+ const content = processTemplate(template, config);
574
+
575
+ filesToCreate.push({
576
+ dest: filePath,
577
+ relativePath: agent.file,
578
+ content,
579
+ type: 'adapter',
580
+ exists,
581
+ });
582
+ }
583
+
584
+ // Add project config file
585
+ const projectConfigPath = path.join(projectDir, '.agents-project.json');
586
+ const projectConfigExists = fs.existsSync(projectConfigPath);
587
+ if (!projectConfigExists || isForce) {
588
+ filesToCreate.push({
589
+ dest: projectConfigPath,
590
+ relativePath: '.agents-project.json',
591
+ content: JSON.stringify(generateProjectConfig(config), null, 2),
592
+ type: 'config',
593
+ exists: projectConfigExists,
594
+ });
595
+ }
596
+
597
+ // Show summary
598
+ log.header('📁 Files to be created:');
599
+
600
+ console.log(`\n${colors.bright}.agents/ folder:${colors.reset}`);
601
+ filesToCreate.filter(f => f.relativePath.startsWith('.agents/')).forEach(f => {
602
+ log.file(f.relativePath);
603
+ });
604
+
605
+ console.log(`\n${colors.bright}Adapter files:${colors.reset}`);
606
+ filesToCreate.filter(f => f.type === 'adapter').forEach(f => {
607
+ const prefix = f.exists ? '(overwrite) ' : '';
608
+ log.file(`${prefix}${f.relativePath}`);
609
+ });
610
+
611
+ console.log(`\n${colors.bright}Config:${colors.reset}`);
612
+ filesToCreate.filter(f => f.type === 'config').forEach(f => {
613
+ log.file(f.relativePath);
614
+ });
615
+
616
+ // Warn about skipped files
617
+ const skippedAgents = selectedAgents.filter(key => {
618
+ const agent = AGENTS[key];
619
+ const filePath = path.join(projectDir, agent.file);
620
+ return fs.existsSync(filePath) && !isForce;
621
+ });
622
+
623
+ if (skippedAgents.length > 0) {
624
+ console.log(`\n${colors.yellow}Skipped (use --force to overwrite):${colors.reset}`);
625
+ skippedAgents.forEach(key => {
626
+ log.file(AGENTS[key].file);
627
+ });
628
+ }
629
+
630
+ if (isDryRun) {
631
+ log.header('🔍 Dry run complete - no files written');
632
+ rl.close();
633
+ return;
634
+ }
635
+
636
+ const shouldProceed = await confirm(rl, '\nCreate these files?');
637
+
638
+ if (!shouldProceed) {
639
+ log.info('Aborted');
640
+ rl.close();
641
+ return;
642
+ }
643
+
644
+ log.header('✍️ Creating files...');
645
+
646
+ for (const file of filesToCreate) {
647
+ try {
648
+ if (file.type === 'file') {
649
+ copyFileSync(file.src, file.dest);
650
+ } else if (file.type === 'folder') {
651
+ copyFolderSync(file.src, file.dest);
652
+ } else {
653
+ // adapter or config - write content
654
+ writeFile(file.dest, file.content);
655
+ }
656
+ log.success(`Created ${file.relativePath}`);
657
+ } catch (error) {
658
+ log.error(`Failed to create ${file.relativePath}: ${error.message}`);
659
+ }
660
+ }
661
+
662
+ log.header('✅ Setup complete!');
663
+ console.log(`
664
+ ${colors.cyan}What was created:${colors.reset}
665
+
666
+ ${colors.bright}.agents/${colors.reset}
667
+ Your project's local copy of rules, skills, and instructions.
668
+ AI agents can read these files for context.
669
+
670
+ ${colors.bright}Adapter files${colors.reset}
671
+ Thin config files that reference .agents/ for each AI tool.
672
+
673
+ ${colors.bright}.agents-project.json${colors.reset}
674
+ Project-specific configuration and overrides.
675
+
676
+ ${colors.cyan}Next steps:${colors.reset}
677
+
678
+ 1. Review the files in .agents/ and customize as needed
679
+ 2. Commit the .agents/ folder and adapter files to git
680
+ 3. Your AI agents will now follow consistent guidelines
681
+
682
+ ${colors.cyan}Useful commands:${colors.reset}
683
+
684
+ ${colors.bright}npx agents-init --force${colors.reset} Regenerate all config files
685
+ ${colors.bright}npx agents-init --dry-run${colors.reset} Preview changes without writing
686
+
687
+ ${colors.cyan}Documentation:${colors.reset}
688
+ https://github.com/ericthayer/agents-config
689
+ `);
690
+
691
+ } finally {
692
+ rl.close();
693
+ }
694
+ }
695
+
696
+ main().catch((err) => {
697
+ log.error(err.message);
698
+ process.exit(1);
699
+ });