proagents 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/init.js +561 -3
- package/package.json +1 -1
- package/proagents/AI_INSTRUCTIONS.md +16 -0
- package/proagents/PROAGENTS.md +3 -0
package/lib/commands/init.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, cpSync, writeFileSync, readFileSync, readdirSync, rmSync } from 'fs';
|
|
2
|
-
import { join, dirname } from 'path';
|
|
2
|
+
import { join, dirname, basename } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
+
import { createInterface } from 'readline';
|
|
4
5
|
import chalk from 'chalk';
|
|
5
6
|
import yaml from 'js-yaml';
|
|
6
7
|
import { selectPlatforms, copyPlatformFiles, savePlatformConfig, loadPlatformConfig } from './ai.js';
|
|
@@ -90,6 +91,559 @@ const FRAMEWORK_FILES = [
|
|
|
90
91
|
'AI_INSTRUCTIONS.md', // Universal instructions kept for reference
|
|
91
92
|
];
|
|
92
93
|
|
|
94
|
+
// Project type definitions for detection
|
|
95
|
+
const PROJECT_TYPES = [
|
|
96
|
+
{
|
|
97
|
+
id: 'nextjs',
|
|
98
|
+
name: 'Next.js (Full-stack)',
|
|
99
|
+
detect: {
|
|
100
|
+
files: ['next.config.js', 'next.config.mjs', 'next.config.ts'],
|
|
101
|
+
deps: ['next']
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'react',
|
|
106
|
+
name: 'React (Frontend)',
|
|
107
|
+
detect: {
|
|
108
|
+
deps: ['react', 'react-dom'],
|
|
109
|
+
notDeps: ['next', 'react-native']
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'vue',
|
|
114
|
+
name: 'Vue.js (Frontend)',
|
|
115
|
+
detect: {
|
|
116
|
+
files: ['vue.config.js', 'vite.config.ts', 'vite.config.js'],
|
|
117
|
+
deps: ['vue']
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'angular',
|
|
122
|
+
name: 'Angular (Frontend)',
|
|
123
|
+
detect: {
|
|
124
|
+
files: ['angular.json'],
|
|
125
|
+
deps: ['@angular/core']
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'react-native',
|
|
130
|
+
name: 'React Native (Mobile)',
|
|
131
|
+
detect: {
|
|
132
|
+
deps: ['react-native']
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: 'express',
|
|
137
|
+
name: 'Express.js (Backend)',
|
|
138
|
+
detect: {
|
|
139
|
+
deps: ['express']
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: 'nestjs',
|
|
144
|
+
name: 'NestJS (Backend)',
|
|
145
|
+
detect: {
|
|
146
|
+
files: ['nest-cli.json'],
|
|
147
|
+
deps: ['@nestjs/core']
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: 'fastify',
|
|
152
|
+
name: 'Fastify (Backend)',
|
|
153
|
+
detect: {
|
|
154
|
+
deps: ['fastify']
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: 'nodejs',
|
|
159
|
+
name: 'Node.js (Backend)',
|
|
160
|
+
detect: {
|
|
161
|
+
files: ['package.json'],
|
|
162
|
+
notDeps: ['react', 'vue', '@angular/core', 'next']
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'python',
|
|
167
|
+
name: 'Python',
|
|
168
|
+
detect: {
|
|
169
|
+
files: ['requirements.txt', 'pyproject.toml', 'setup.py', 'Pipfile']
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'django',
|
|
174
|
+
name: 'Django (Python)',
|
|
175
|
+
detect: {
|
|
176
|
+
files: ['manage.py'],
|
|
177
|
+
patterns: ['django']
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: 'flask',
|
|
182
|
+
name: 'Flask (Python)',
|
|
183
|
+
detect: {
|
|
184
|
+
patterns: ['flask']
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 'other',
|
|
189
|
+
name: 'Other / Custom',
|
|
190
|
+
detect: {}
|
|
191
|
+
}
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
// Tech stack configuration options
|
|
195
|
+
const TECH_STACK_OPTIONS = {
|
|
196
|
+
api_style: {
|
|
197
|
+
label: 'API Style',
|
|
198
|
+
options: [
|
|
199
|
+
{ id: 'rest', name: 'REST' },
|
|
200
|
+
{ id: 'graphql', name: 'GraphQL' },
|
|
201
|
+
{ id: 'grpc', name: 'gRPC' },
|
|
202
|
+
{ id: 'trpc', name: 'tRPC' }
|
|
203
|
+
],
|
|
204
|
+
detect: {
|
|
205
|
+
'graphql': ['@apollo/client', 'graphql', 'apollo-server', 'urql'],
|
|
206
|
+
'grpc': ['@grpc/grpc-js', 'grpc'],
|
|
207
|
+
'trpc': ['@trpc/client', '@trpc/server']
|
|
208
|
+
},
|
|
209
|
+
default: 'rest'
|
|
210
|
+
},
|
|
211
|
+
state_management: {
|
|
212
|
+
label: 'State Management',
|
|
213
|
+
options: [
|
|
214
|
+
{ id: 'zustand', name: 'Zustand' },
|
|
215
|
+
{ id: 'redux', name: 'Redux' },
|
|
216
|
+
{ id: 'jotai', name: 'Jotai' },
|
|
217
|
+
{ id: 'recoil', name: 'Recoil' },
|
|
218
|
+
{ id: 'mobx', name: 'MobX' },
|
|
219
|
+
{ id: 'context', name: 'React Context' },
|
|
220
|
+
{ id: 'none', name: 'None / Other' }
|
|
221
|
+
],
|
|
222
|
+
detect: {
|
|
223
|
+
'zustand': ['zustand'],
|
|
224
|
+
'redux': ['redux', '@reduxjs/toolkit', 'react-redux'],
|
|
225
|
+
'jotai': ['jotai'],
|
|
226
|
+
'recoil': ['recoil'],
|
|
227
|
+
'mobx': ['mobx', 'mobx-react']
|
|
228
|
+
},
|
|
229
|
+
default: 'zustand'
|
|
230
|
+
},
|
|
231
|
+
styling: {
|
|
232
|
+
label: 'Styling',
|
|
233
|
+
options: [
|
|
234
|
+
{ id: 'tailwind', name: 'Tailwind CSS' },
|
|
235
|
+
{ id: 'css-modules', name: 'CSS Modules' },
|
|
236
|
+
{ id: 'styled-components', name: 'Styled Components' },
|
|
237
|
+
{ id: 'emotion', name: 'Emotion' },
|
|
238
|
+
{ id: 'sass', name: 'Sass/SCSS' },
|
|
239
|
+
{ id: 'css', name: 'Plain CSS' }
|
|
240
|
+
],
|
|
241
|
+
detect: {
|
|
242
|
+
'tailwind': ['tailwindcss'],
|
|
243
|
+
'styled-components': ['styled-components'],
|
|
244
|
+
'emotion': ['@emotion/react', '@emotion/styled'],
|
|
245
|
+
'sass': ['sass', 'node-sass']
|
|
246
|
+
},
|
|
247
|
+
default: 'tailwind'
|
|
248
|
+
},
|
|
249
|
+
database: {
|
|
250
|
+
label: 'Database',
|
|
251
|
+
options: [
|
|
252
|
+
{ id: 'postgresql', name: 'PostgreSQL' },
|
|
253
|
+
{ id: 'mysql', name: 'MySQL' },
|
|
254
|
+
{ id: 'mongodb', name: 'MongoDB' },
|
|
255
|
+
{ id: 'sqlite', name: 'SQLite' },
|
|
256
|
+
{ id: 'supabase', name: 'Supabase' },
|
|
257
|
+
{ id: 'firebase', name: 'Firebase' },
|
|
258
|
+
{ id: 'none', name: 'None / Other' }
|
|
259
|
+
],
|
|
260
|
+
detect: {
|
|
261
|
+
'postgresql': ['pg', '@prisma/client', 'postgres'],
|
|
262
|
+
'mysql': ['mysql', 'mysql2'],
|
|
263
|
+
'mongodb': ['mongoose', 'mongodb'],
|
|
264
|
+
'sqlite': ['better-sqlite3', 'sqlite3'],
|
|
265
|
+
'supabase': ['@supabase/supabase-js'],
|
|
266
|
+
'firebase': ['firebase', 'firebase-admin']
|
|
267
|
+
},
|
|
268
|
+
default: 'postgresql'
|
|
269
|
+
},
|
|
270
|
+
orm: {
|
|
271
|
+
label: 'ORM',
|
|
272
|
+
options: [
|
|
273
|
+
{ id: 'prisma', name: 'Prisma' },
|
|
274
|
+
{ id: 'drizzle', name: 'Drizzle' },
|
|
275
|
+
{ id: 'typeorm', name: 'TypeORM' },
|
|
276
|
+
{ id: 'sequelize', name: 'Sequelize' },
|
|
277
|
+
{ id: 'mongoose', name: 'Mongoose' },
|
|
278
|
+
{ id: 'none', name: 'None / Raw SQL' }
|
|
279
|
+
],
|
|
280
|
+
detect: {
|
|
281
|
+
'prisma': ['@prisma/client', 'prisma'],
|
|
282
|
+
'drizzle': ['drizzle-orm'],
|
|
283
|
+
'typeorm': ['typeorm'],
|
|
284
|
+
'sequelize': ['sequelize'],
|
|
285
|
+
'mongoose': ['mongoose']
|
|
286
|
+
},
|
|
287
|
+
default: 'prisma'
|
|
288
|
+
},
|
|
289
|
+
auth_method: {
|
|
290
|
+
label: 'Authentication',
|
|
291
|
+
options: [
|
|
292
|
+
{ id: 'jwt', name: 'JWT' },
|
|
293
|
+
{ id: 'session', name: 'Session-based' },
|
|
294
|
+
{ id: 'oauth', name: 'OAuth' },
|
|
295
|
+
{ id: 'nextauth', name: 'NextAuth.js' },
|
|
296
|
+
{ id: 'clerk', name: 'Clerk' },
|
|
297
|
+
{ id: 'auth0', name: 'Auth0' },
|
|
298
|
+
{ id: 'supabase', name: 'Supabase Auth' },
|
|
299
|
+
{ id: 'none', name: 'None / Custom' }
|
|
300
|
+
],
|
|
301
|
+
detect: {
|
|
302
|
+
'nextauth': ['next-auth'],
|
|
303
|
+
'clerk': ['@clerk/nextjs', '@clerk/clerk-react'],
|
|
304
|
+
'auth0': ['@auth0/auth0-react', 'auth0'],
|
|
305
|
+
'supabase': ['@supabase/auth-helpers-nextjs']
|
|
306
|
+
},
|
|
307
|
+
default: 'jwt'
|
|
308
|
+
},
|
|
309
|
+
test_framework: {
|
|
310
|
+
label: 'Test Framework',
|
|
311
|
+
options: [
|
|
312
|
+
{ id: 'vitest', name: 'Vitest' },
|
|
313
|
+
{ id: 'jest', name: 'Jest' },
|
|
314
|
+
{ id: 'mocha', name: 'Mocha' },
|
|
315
|
+
{ id: 'playwright', name: 'Playwright' },
|
|
316
|
+
{ id: 'cypress', name: 'Cypress' },
|
|
317
|
+
{ id: 'pytest', name: 'Pytest (Python)' },
|
|
318
|
+
{ id: 'none', name: 'None' }
|
|
319
|
+
],
|
|
320
|
+
detect: {
|
|
321
|
+
'vitest': ['vitest'],
|
|
322
|
+
'jest': ['jest'],
|
|
323
|
+
'mocha': ['mocha'],
|
|
324
|
+
'playwright': ['@playwright/test', 'playwright'],
|
|
325
|
+
'cypress': ['cypress'],
|
|
326
|
+
'pytest': ['pytest']
|
|
327
|
+
},
|
|
328
|
+
default: 'vitest'
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Detect tech stack from dependencies
|
|
334
|
+
*/
|
|
335
|
+
function detectTechStack(targetDir) {
|
|
336
|
+
const { allDeps = [] } = getPackageDeps(targetDir);
|
|
337
|
+
const pythonDeps = getPythonDeps(targetDir);
|
|
338
|
+
const allProjectDeps = [...allDeps, ...pythonDeps.map(d => d.toLowerCase())];
|
|
339
|
+
|
|
340
|
+
const detected = {};
|
|
341
|
+
|
|
342
|
+
for (const [key, config] of Object.entries(TECH_STACK_OPTIONS)) {
|
|
343
|
+
detected[key] = null;
|
|
344
|
+
|
|
345
|
+
if (config.detect) {
|
|
346
|
+
for (const [optionId, detectDeps] of Object.entries(config.detect)) {
|
|
347
|
+
const hasMatch = detectDeps.some(dep => allProjectDeps.includes(dep));
|
|
348
|
+
if (hasMatch) {
|
|
349
|
+
detected[key] = optionId;
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Use default if not detected
|
|
356
|
+
if (!detected[key]) {
|
|
357
|
+
detected[key] = config.default;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return detected;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Detect project name from package.json or folder name
|
|
366
|
+
*/
|
|
367
|
+
function detectProjectName(targetDir) {
|
|
368
|
+
const packageJsonPath = join(targetDir, 'package.json');
|
|
369
|
+
|
|
370
|
+
if (existsSync(packageJsonPath)) {
|
|
371
|
+
try {
|
|
372
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
373
|
+
if (packageJson.name) {
|
|
374
|
+
return packageJson.name;
|
|
375
|
+
}
|
|
376
|
+
} catch {
|
|
377
|
+
// Fall through to folder name
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Fallback to folder name
|
|
382
|
+
return basename(targetDir);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Read package.json dependencies
|
|
387
|
+
*/
|
|
388
|
+
function getPackageDeps(targetDir) {
|
|
389
|
+
const packageJsonPath = join(targetDir, 'package.json');
|
|
390
|
+
|
|
391
|
+
if (!existsSync(packageJsonPath)) {
|
|
392
|
+
return { deps: [], devDeps: [] };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
397
|
+
const deps = Object.keys(packageJson.dependencies || {});
|
|
398
|
+
const devDeps = Object.keys(packageJson.devDependencies || {});
|
|
399
|
+
return { deps, devDeps, allDeps: [...deps, ...devDeps] };
|
|
400
|
+
} catch {
|
|
401
|
+
return { deps: [], devDeps: [], allDeps: [] };
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Read requirements.txt for Python projects
|
|
407
|
+
*/
|
|
408
|
+
function getPythonDeps(targetDir) {
|
|
409
|
+
const requirementsPath = join(targetDir, 'requirements.txt');
|
|
410
|
+
|
|
411
|
+
if (!existsSync(requirementsPath)) {
|
|
412
|
+
return [];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
const content = readFileSync(requirementsPath, 'utf-8');
|
|
417
|
+
return content.split('\n')
|
|
418
|
+
.map(line => line.trim().split('==')[0].split('>=')[0].split('<=')[0])
|
|
419
|
+
.filter(Boolean);
|
|
420
|
+
} catch {
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Detect project type based on files and dependencies
|
|
427
|
+
*/
|
|
428
|
+
function detectProjectType(targetDir) {
|
|
429
|
+
const { allDeps = [] } = getPackageDeps(targetDir);
|
|
430
|
+
const pythonDeps = getPythonDeps(targetDir);
|
|
431
|
+
const allProjectDeps = [...allDeps, ...pythonDeps.map(d => d.toLowerCase())];
|
|
432
|
+
|
|
433
|
+
const detectedTypes = [];
|
|
434
|
+
|
|
435
|
+
for (const projectType of PROJECT_TYPES) {
|
|
436
|
+
if (projectType.id === 'other') continue;
|
|
437
|
+
|
|
438
|
+
const { detect } = projectType;
|
|
439
|
+
let matches = true;
|
|
440
|
+
let score = 0;
|
|
441
|
+
|
|
442
|
+
// Check for required files
|
|
443
|
+
if (detect.files) {
|
|
444
|
+
const hasFile = detect.files.some(file => existsSync(join(targetDir, file)));
|
|
445
|
+
if (hasFile) {
|
|
446
|
+
score += 10;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Check for required dependencies
|
|
451
|
+
if (detect.deps) {
|
|
452
|
+
const hasDep = detect.deps.some(dep => allProjectDeps.includes(dep));
|
|
453
|
+
if (hasDep) {
|
|
454
|
+
score += 5;
|
|
455
|
+
} else if (detect.deps.length > 0 && !detect.files) {
|
|
456
|
+
matches = false;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Check for excluded dependencies
|
|
461
|
+
if (detect.notDeps && matches) {
|
|
462
|
+
const hasExcluded = detect.notDeps.some(dep => allProjectDeps.includes(dep));
|
|
463
|
+
if (hasExcluded) {
|
|
464
|
+
matches = false;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Check for patterns in requirements.txt
|
|
469
|
+
if (detect.patterns && pythonDeps.length > 0) {
|
|
470
|
+
const hasPattern = detect.patterns.some(pattern =>
|
|
471
|
+
pythonDeps.some(dep => dep.toLowerCase().includes(pattern))
|
|
472
|
+
);
|
|
473
|
+
if (hasPattern) {
|
|
474
|
+
score += 5;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (matches && score > 0) {
|
|
479
|
+
detectedTypes.push({ ...projectType, score });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Sort by score descending
|
|
484
|
+
detectedTypes.sort((a, b) => b.score - a.score);
|
|
485
|
+
|
|
486
|
+
return detectedTypes;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Interactive prompt for project configuration
|
|
491
|
+
*/
|
|
492
|
+
async function promptProjectConfig(targetDir) {
|
|
493
|
+
const rl = createInterface({
|
|
494
|
+
input: process.stdin,
|
|
495
|
+
output: process.stdout
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
|
|
499
|
+
|
|
500
|
+
// Detect project name
|
|
501
|
+
const detectedName = detectProjectName(targetDir);
|
|
502
|
+
|
|
503
|
+
// Detect project types
|
|
504
|
+
const detectedTypes = detectProjectType(targetDir);
|
|
505
|
+
const topDetectedType = detectedTypes.length > 0 ? detectedTypes[0] : null;
|
|
506
|
+
|
|
507
|
+
// Detect tech stack
|
|
508
|
+
const detectedTechStack = detectTechStack(targetDir);
|
|
509
|
+
|
|
510
|
+
console.log(chalk.bold('\nProject Configuration'));
|
|
511
|
+
console.log(chalk.gray('─'.repeat(40) + '\n'));
|
|
512
|
+
|
|
513
|
+
// Project Name
|
|
514
|
+
console.log(chalk.cyan('Project Name'));
|
|
515
|
+
if (detectedName) {
|
|
516
|
+
console.log(chalk.gray(` Detected: ${detectedName}`));
|
|
517
|
+
}
|
|
518
|
+
const nameInput = await question(chalk.yellow(` Enter name (press Enter for "${detectedName}"): `));
|
|
519
|
+
const projectName = nameInput.trim() || detectedName;
|
|
520
|
+
|
|
521
|
+
console.log('');
|
|
522
|
+
|
|
523
|
+
// Project Type
|
|
524
|
+
console.log(chalk.cyan('Project Type'));
|
|
525
|
+
if (topDetectedType) {
|
|
526
|
+
console.log(chalk.green(` Detected: ${topDetectedType.name}`));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
console.log(chalk.gray('\n Available types:'));
|
|
530
|
+
PROJECT_TYPES.forEach((type, index) => {
|
|
531
|
+
const isDetected = topDetectedType && type.id === topDetectedType.id;
|
|
532
|
+
const marker = isDetected ? chalk.green(' ✓ (detected)') : '';
|
|
533
|
+
console.log(chalk.white(` ${index + 1}. ${type.name}`) + marker);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
const defaultTypeIndex = topDetectedType
|
|
537
|
+
? PROJECT_TYPES.findIndex(t => t.id === topDetectedType.id) + 1
|
|
538
|
+
: PROJECT_TYPES.length;
|
|
539
|
+
|
|
540
|
+
const typeInput = await question(chalk.yellow(`\n Enter number (press Enter for ${defaultTypeIndex}): `));
|
|
541
|
+
const typeIndex = parseInt(typeInput.trim()) || defaultTypeIndex;
|
|
542
|
+
const projectType = PROJECT_TYPES[Math.min(Math.max(typeIndex - 1, 0), PROJECT_TYPES.length - 1)];
|
|
543
|
+
|
|
544
|
+
console.log('');
|
|
545
|
+
console.log(chalk.green(`✓ Project: ${projectName} (${projectType.name})`));
|
|
546
|
+
|
|
547
|
+
// Tech Stack Configuration
|
|
548
|
+
console.log(chalk.bold('\nTech Stack Configuration'));
|
|
549
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
550
|
+
console.log(chalk.gray('Press Enter to accept detected/default values\n'));
|
|
551
|
+
|
|
552
|
+
const techStack = {};
|
|
553
|
+
|
|
554
|
+
for (const [key, config] of Object.entries(TECH_STACK_OPTIONS)) {
|
|
555
|
+
const detected = detectedTechStack[key];
|
|
556
|
+
const detectedOption = config.options.find(o => o.id === detected);
|
|
557
|
+
const detectedName = detectedOption ? detectedOption.name : config.default;
|
|
558
|
+
|
|
559
|
+
console.log(chalk.cyan(config.label));
|
|
560
|
+
config.options.forEach((option, index) => {
|
|
561
|
+
const isDetected = option.id === detected;
|
|
562
|
+
const marker = isDetected ? chalk.green(' ✓') : '';
|
|
563
|
+
console.log(chalk.white(` ${index + 1}. ${option.name}`) + marker);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const defaultIndex = config.options.findIndex(o => o.id === detected) + 1 || 1;
|
|
567
|
+
const input = await question(chalk.yellow(` Select (Enter for ${defaultIndex}): `));
|
|
568
|
+
const selectedIndex = parseInt(input.trim()) || defaultIndex;
|
|
569
|
+
const selectedOption = config.options[Math.min(Math.max(selectedIndex - 1, 0), config.options.length - 1)];
|
|
570
|
+
techStack[key] = selectedOption.id;
|
|
571
|
+
console.log('');
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
rl.close();
|
|
575
|
+
|
|
576
|
+
// Summary
|
|
577
|
+
console.log(chalk.bold('\nConfiguration Summary'));
|
|
578
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
579
|
+
console.log(chalk.white(` Project: ${projectName} (${projectType.name})`));
|
|
580
|
+
for (const [key, value] of Object.entries(techStack)) {
|
|
581
|
+
const config = TECH_STACK_OPTIONS[key];
|
|
582
|
+
const option = config.options.find(o => o.id === value);
|
|
583
|
+
console.log(chalk.white(` ${config.label}: ${option ? option.name : value}`));
|
|
584
|
+
}
|
|
585
|
+
console.log('');
|
|
586
|
+
|
|
587
|
+
return {
|
|
588
|
+
name: projectName,
|
|
589
|
+
type: projectType.id,
|
|
590
|
+
typeName: projectType.name,
|
|
591
|
+
techStack
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Save project config to proagents.config.yaml
|
|
597
|
+
*/
|
|
598
|
+
function saveProjectConfig(projectConfig, configPath) {
|
|
599
|
+
let config = {};
|
|
600
|
+
|
|
601
|
+
if (existsSync(configPath)) {
|
|
602
|
+
try {
|
|
603
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
604
|
+
config = yaml.load(content) || {};
|
|
605
|
+
} catch {
|
|
606
|
+
config = {};
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Save project info
|
|
611
|
+
config.project = {
|
|
612
|
+
name: projectConfig.name,
|
|
613
|
+
type: projectConfig.type
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
// Save tech stack under automation.decisions
|
|
617
|
+
if (projectConfig.techStack) {
|
|
618
|
+
if (!config.automation) {
|
|
619
|
+
config.automation = {};
|
|
620
|
+
}
|
|
621
|
+
if (!config.automation.decisions) {
|
|
622
|
+
config.automation.decisions = {};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Architecture decisions
|
|
626
|
+
config.automation.decisions.architecture = {
|
|
627
|
+
api_style: projectConfig.techStack.api_style,
|
|
628
|
+
state_management: projectConfig.techStack.state_management,
|
|
629
|
+
styling: projectConfig.techStack.styling,
|
|
630
|
+
database: projectConfig.techStack.database,
|
|
631
|
+
orm: projectConfig.techStack.orm,
|
|
632
|
+
auth_method: projectConfig.techStack.auth_method
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
// Testing decisions
|
|
636
|
+
config.automation.decisions.testing = {
|
|
637
|
+
framework: projectConfig.techStack.test_framework,
|
|
638
|
+
coverage_target: 80,
|
|
639
|
+
location: 'colocated'
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const yamlContent = yaml.dump(config, { indent: 2, lineWidth: 120 });
|
|
644
|
+
writeFileSync(configPath, yamlContent);
|
|
645
|
+
}
|
|
646
|
+
|
|
93
647
|
/**
|
|
94
648
|
* Initialize ProAgents in the current project
|
|
95
649
|
*/
|
|
@@ -223,6 +777,9 @@ No releases yet. Use \`pa:release\` to generate release notes.
|
|
|
223
777
|
console.log(chalk.green('✓ Created RELEASE_NOTES.md'));
|
|
224
778
|
}
|
|
225
779
|
|
|
780
|
+
// Interactive project configuration
|
|
781
|
+
const projectConfig = await promptProjectConfig(targetDir);
|
|
782
|
+
|
|
226
783
|
// Add ProAgents section to README.md (AI tools auto-read this)
|
|
227
784
|
const readmePath = join(targetDir, 'README.md');
|
|
228
785
|
const proagentsSection = `
|
|
@@ -251,7 +808,7 @@ For detailed commands, see \`./proagents/PROAGENTS.md\`
|
|
|
251
808
|
console.log(chalk.green('✓ Added ProAgents commands to README.md'));
|
|
252
809
|
}
|
|
253
810
|
} else {
|
|
254
|
-
writeFileSync(readmePath, proagentsSection + `#
|
|
811
|
+
writeFileSync(readmePath, proagentsSection + `# ${projectConfig.name}\n\nProject description.\n`);
|
|
255
812
|
console.log(chalk.green('✓ Created README.md with ProAgents commands'));
|
|
256
813
|
}
|
|
257
814
|
|
|
@@ -271,8 +828,9 @@ For detailed commands, see \`./proagents/PROAGENTS.md\`
|
|
|
271
828
|
console.log(chalk.green(`✓ Merged with existing: ${aiResults.merged.join(', ')}`));
|
|
272
829
|
}
|
|
273
830
|
|
|
274
|
-
// Save
|
|
831
|
+
// Save project and platform config
|
|
275
832
|
const configPath = join(proagentsDir, 'proagents.config.yaml');
|
|
833
|
+
saveProjectConfig(projectConfig, configPath);
|
|
276
834
|
savePlatformConfig(selectedPlatforms, configPath);
|
|
277
835
|
|
|
278
836
|
// Success message
|
package/package.json
CHANGED
|
@@ -58,6 +58,22 @@ When the user types commands starting with `pa:`, recognize and execute them:
|
|
|
58
58
|
| `pa:ai-add` | Add more AI platforms |
|
|
59
59
|
| `pa:ai-remove` | Remove AI platforms from config |
|
|
60
60
|
|
|
61
|
+
**How to execute AI Platform commands:**
|
|
62
|
+
|
|
63
|
+
For `pa:ai-list`:
|
|
64
|
+
- Read `./proagents/proagents.config.yaml` and show the `platforms` array
|
|
65
|
+
- Show which AI instruction files exist in project root (CLAUDE.md, GEMINI.md, etc.)
|
|
66
|
+
|
|
67
|
+
For `pa:ai-add`:
|
|
68
|
+
- Tell user to run: `npx proagents ai add`
|
|
69
|
+
- This will show an interactive prompt to select additional AI platforms
|
|
70
|
+
- The command will create the appropriate AI instruction files (.cursorrules, GEMINI.md, etc.)
|
|
71
|
+
|
|
72
|
+
For `pa:ai-remove`:
|
|
73
|
+
- Tell user to run: `npx proagents ai remove`
|
|
74
|
+
- This will show which platforms can be removed
|
|
75
|
+
- The command will remove the AI instruction files and update config
|
|
76
|
+
|
|
61
77
|
### Configuration
|
|
62
78
|
| Command | Action |
|
|
63
79
|
|---------|--------|
|
package/proagents/PROAGENTS.md
CHANGED
|
@@ -15,6 +15,9 @@ Execute these commands when user types them (prefix: `pa:`):
|
|
|
15
15
|
| `pa:doc` | Generate documentation |
|
|
16
16
|
| `pa:release` | Generate release notes → `./RELEASE_NOTES.md` |
|
|
17
17
|
| `pa:changelog` | Update `./CHANGELOG.md` |
|
|
18
|
+
| `pa:ai-list` | Show configured AI platforms from config |
|
|
19
|
+
| `pa:ai-add` | Tell user to run `npx proagents ai add` |
|
|
20
|
+
| `pa:ai-remove` | Tell user to run `npx proagents ai remove` |
|
|
18
21
|
|
|
19
22
|
## Feature Workflow
|
|
20
23
|
|