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.
@@ -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 + `# Project Name\n\nProject description.\n`);
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 selected platforms to config
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proagents",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "AI-agnostic development workflow framework that automates the full software development lifecycle",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -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
  |---------|--------|
@@ -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