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.
- package/AGENTS.md +490 -0
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/adapters/claude.template.md +77 -0
- package/adapters/codex.template.md +72 -0
- package/adapters/copilot.template.md +68 -0
- package/adapters/cursor.template.md +69 -0
- package/adapters/gemini.template.md +73 -0
- package/adapters/windsurf.template.md +81 -0
- package/bin/agents-init.js +699 -0
- package/bin/postinstall.js +28 -0
- package/instructions/development-standards.instructions.md +47 -0
- package/instructions/github-issue.instructions.md +324 -0
- package/instructions/github-release-notes.instructions.md +888 -0
- package/instructions/mui.instructions.md +50 -0
- package/instructions/storybook.instructions.md +55 -0
- package/instructions/web-interface-guidelines.instructions.md +331 -0
- package/package.json +56 -0
- package/prompts/create-pr.prompt.md +78 -0
- package/prompts/scaffold-component.prompt.md +57 -0
- package/rules/accessibility.md +36 -0
- package/rules/component-architecture.md +34 -0
- package/rules/gemini.md +547 -0
- package/rules/mui.md +491 -0
- package/rules/react-19-compiler.md +26 -0
- package/rules/spec-driven-development.md +36 -0
- package/rules/supabase.md +40 -0
- package/rules/tailwind-v4.md +29 -0
- package/rules/three-js-react.md +76 -0
- package/rules/web-performance.md +29 -0
- package/schemas/agents-project.schema.json +78 -0
- package/skills/accessibility-audit/SKILL.md +39 -0
- package/skills/integrate-gemini/SKILL.md +124 -0
- package/skills/scaffold-component/SKILL.md +77 -0
- package/skills/vercel-react-best-practices/AGENTS.md +2719 -0
- package/skills/vercel-react-best-practices/SKILL.md +125 -0
- package/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
- package/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/skills/workflows/sdd-workflow.md +49 -0
- 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
|
+
});
|