proagents 1.6.7 → 1.6.9

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,4 +1,4 @@
1
- import { existsSync, cpSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
1
+ import { existsSync, cpSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs';
2
2
  import { join, dirname } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { createInterface } from 'readline';
@@ -49,8 +49,9 @@ export function getPlatformById(id) {
49
49
 
50
50
  /**
51
51
  * Interactive platform selection using readline
52
+ * @param {string[]} previouslySelected - Previously selected platforms (from interrupted setup)
52
53
  */
53
- export async function selectPlatforms() {
54
+ export async function selectPlatforms(previouslySelected = []) {
54
55
  const rl = createInterface({
55
56
  input: process.stdin,
56
57
  output: process.stdout
@@ -63,12 +64,16 @@ export async function selectPlatforms() {
63
64
 
64
65
  let index = 1;
65
66
  const indexMap = {};
67
+ const preSelectedIndices = [];
66
68
 
67
69
  // IDE-based platforms
68
70
  console.log(chalk.cyan.bold(` ${AI_PLATFORMS.ide.label}:`));
69
71
  for (const platform of AI_PLATFORMS.ide.platforms) {
70
- console.log(chalk.white(` ${index}. ${platform.name}`) + chalk.gray(` - ${platform.desc}`));
72
+ const wasSelected = previouslySelected && previouslySelected.includes(platform.id);
73
+ const marker = wasSelected ? chalk.green(' ✓ (previously selected)') : '';
74
+ console.log(chalk.white(` ${index}. ${platform.name}`) + chalk.gray(` - ${platform.desc}`) + marker);
71
75
  indexMap[index] = platform.id;
76
+ if (wasSelected) preSelectedIndices.push(index);
72
77
  index++;
73
78
  }
74
79
 
@@ -77,16 +82,29 @@ export async function selectPlatforms() {
77
82
  // Web-based platforms
78
83
  console.log(chalk.cyan.bold(` ${AI_PLATFORMS.web.label}:`));
79
84
  for (const platform of AI_PLATFORMS.web.platforms) {
80
- console.log(chalk.white(` ${index}. ${platform.name}`) + chalk.gray(` - ${platform.desc}`));
85
+ const wasSelected = previouslySelected && previouslySelected.includes(platform.id);
86
+ const marker = wasSelected ? chalk.green(' ✓ (previously selected)') : '';
87
+ console.log(chalk.white(` ${index}. ${platform.name}`) + chalk.gray(` - ${platform.desc}`) + marker);
81
88
  indexMap[index] = platform.id;
89
+ if (wasSelected) preSelectedIndices.push(index);
82
90
  index++;
83
91
  }
84
92
 
85
93
  console.log('');
86
94
 
87
- const answer = await question(chalk.yellow('Your selection (e.g., 1,2,3 or "all"): '));
95
+ // Show default based on previous selection
96
+ const defaultHint = preSelectedIndices.length > 0
97
+ ? `Enter for previous: ${preSelectedIndices.join(',')}`
98
+ : 'e.g., 1,2,3 or "all"';
99
+
100
+ const answer = await question(chalk.yellow(`Your selection (${defaultHint}): `));
88
101
  rl.close();
89
102
 
103
+ // If user just pressed enter and we have previous selections, use them
104
+ if (answer.trim() === '' && preSelectedIndices.length > 0) {
105
+ return previouslySelected;
106
+ }
107
+
90
108
  if (answer.toLowerCase() === 'all') {
91
109
  return getAllPlatforms().map(p => p.id);
92
110
  }
@@ -131,6 +149,46 @@ function extractProagentsSection(content) {
131
149
  return null;
132
150
  }
133
151
 
152
+ /**
153
+ * Remove only ProAgents section from a file, keep user's original content
154
+ * Returns: 'deleted' (file removed), 'cleaned' (section removed), 'skipped' (no ProAgents section)
155
+ */
156
+ function removeProagentsSectionFromFile(filePath) {
157
+ try {
158
+ const content = readFileSync(filePath, 'utf-8');
159
+
160
+ const startIndex = content.indexOf(PROAGENTS_START);
161
+ const endIndex = content.indexOf(PROAGENTS_END);
162
+
163
+ if (startIndex !== -1 && endIndex !== -1) {
164
+ // Has ProAgents section - remove it, keep the rest
165
+ const before = content.substring(0, startIndex).trim();
166
+ const after = content.substring(endIndex + PROAGENTS_END.length).trim();
167
+ const remaining = (before + '\n\n' + after).trim();
168
+
169
+ if (remaining.length === 0) {
170
+ // File only had ProAgents content - delete it
171
+ rmSync(filePath, { force: true });
172
+ return 'deleted';
173
+ } else {
174
+ // File has other content - keep it, remove only ProAgents section
175
+ writeFileSync(filePath, remaining + '\n');
176
+ return 'cleaned';
177
+ }
178
+ } else {
179
+ // No ProAgents markers - file was created by ProAgents (not merged)
180
+ // Check if it's a ProAgents-generated file by looking for ProAgents reference
181
+ if (content.includes('proagents') || content.includes('ProAgents') || content.includes('.proagents/')) {
182
+ rmSync(filePath, { force: true });
183
+ return 'deleted';
184
+ }
185
+ return 'skipped';
186
+ }
187
+ } catch (error) {
188
+ return 'skipped';
189
+ }
190
+ }
191
+
134
192
  /**
135
193
  * Merge ProAgents instructions with existing file content
136
194
  * - If file doesn't exist: create with ProAgents content
@@ -247,6 +305,51 @@ export function loadPlatformConfig(configPath) {
247
305
  }
248
306
  }
249
307
 
308
+ /**
309
+ * Check if a file contains ProAgents content
310
+ */
311
+ function hasProagentsContent(filePath) {
312
+ try {
313
+ const content = readFileSync(filePath, 'utf-8');
314
+ // Check for ProAgents markers or references
315
+ return content.includes(PROAGENTS_START) ||
316
+ content.includes('.proagents/') ||
317
+ content.includes('ProAgents Commands');
318
+ } catch {
319
+ return false;
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Detect installed platforms by checking for actual files with ProAgents content
325
+ * Only counts as installed if the file has ProAgents-related content
326
+ */
327
+ export function detectInstalledPlatforms(targetDir) {
328
+ const installed = [];
329
+
330
+ for (const platform of getAllPlatforms()) {
331
+ const filePath = join(targetDir, platform.file);
332
+ if (existsSync(filePath) && hasProagentsContent(filePath)) {
333
+ installed.push(platform.id);
334
+ }
335
+ }
336
+
337
+ return installed;
338
+ }
339
+
340
+ /**
341
+ * Get installed platforms - combines config and file detection
342
+ * Returns platforms that are either in config OR have files present
343
+ */
344
+ export function getInstalledPlatforms(targetDir, configPath) {
345
+ const fromConfig = loadPlatformConfig(configPath);
346
+ const fromFiles = detectInstalledPlatforms(targetDir);
347
+
348
+ // Merge both sources, remove duplicates
349
+ const combined = [...new Set([...fromConfig, ...fromFiles])];
350
+ return combined;
351
+ }
352
+
250
353
  /**
251
354
  * Show available platforms that can be added
252
355
  */
@@ -294,7 +397,7 @@ export async function aiAddCommand() {
294
397
  const targetDir = process.cwd();
295
398
  const proagentsDir = join(targetDir, '.proagents');
296
399
  const sourceDir = join(__dirname, '..', '..', '.proagents');
297
- const configPath = join(proagentsDir, 'proagents.config.yaml');
400
+ const configPath = join(targetDir, 'proagents.config.yaml');
298
401
 
299
402
  // Check if proagents is initialized
300
403
  if (!existsSync(proagentsDir)) {
@@ -302,8 +405,8 @@ export async function aiAddCommand() {
302
405
  return;
303
406
  }
304
407
 
305
- // Load current platforms
306
- const currentIds = loadPlatformConfig(configPath);
408
+ // Detect from both config AND actual files
409
+ const currentIds = getInstalledPlatforms(targetDir, configPath);
307
410
 
308
411
  console.log(chalk.bold.blue('\nProAgents - Add AI Platform'));
309
412
  console.log(chalk.blue('===========================\n'));
@@ -361,7 +464,7 @@ export async function aiAddCommand() {
361
464
  }
362
465
 
363
466
  console.log(chalk.gray('\nAI instruction files added to project root.'));
364
- console.log(chalk.gray('Config updated in .proagents/proagents.config.yaml\n'));
467
+ console.log(chalk.gray('Config updated in proagents.config.yaml\n'));
365
468
  }
366
469
 
367
470
  /**
@@ -370,9 +473,10 @@ export async function aiAddCommand() {
370
473
  export function aiListCommand() {
371
474
  const targetDir = process.cwd();
372
475
  const proagentsDir = join(targetDir, '.proagents');
373
- const configPath = join(proagentsDir, 'proagents.config.yaml');
476
+ const configPath = join(targetDir, 'proagents.config.yaml');
374
477
 
375
- const currentIds = loadPlatformConfig(configPath);
478
+ // Detect from both config AND actual files
479
+ const currentIds = getInstalledPlatforms(targetDir, configPath);
376
480
 
377
481
  console.log(chalk.bold.blue('\nProAgents - AI Platforms'));
378
482
  console.log(chalk.blue('========================\n'));
@@ -387,17 +491,18 @@ export function aiListCommand() {
387
491
  export async function aiRemoveCommand() {
388
492
  const targetDir = process.cwd();
389
493
  const proagentsDir = join(targetDir, '.proagents');
390
- const configPath = join(proagentsDir, 'proagents.config.yaml');
494
+ const configPath = join(targetDir, 'proagents.config.yaml');
391
495
 
392
496
  if (!existsSync(proagentsDir)) {
393
497
  console.log(chalk.red('\n✗ ProAgents not initialized.\n'));
394
498
  return;
395
499
  }
396
500
 
397
- const currentIds = loadPlatformConfig(configPath);
501
+ // Detect from both config AND actual files
502
+ const currentIds = getInstalledPlatforms(targetDir, configPath);
398
503
 
399
504
  if (currentIds.length === 0) {
400
- console.log(chalk.yellow('\nNo AI platforms configured.\n'));
505
+ console.log(chalk.yellow('\nNo AI platforms installed.\n'));
401
506
  return;
402
507
  }
403
508
 
@@ -443,12 +548,36 @@ export async function aiRemoveCommand() {
443
548
  return;
444
549
  }
445
550
 
446
- // Remove from config (don't delete files - user might have customized them)
551
+ // Remove from config
447
552
  const newIds = currentIds.filter(id => !toRemove.includes(id));
448
553
  savePlatformConfig(newIds, configPath);
449
554
 
450
- const removedNames = toRemove.map(id => getPlatformById(id)?.name).filter(Boolean);
451
- console.log(chalk.green(`\n✓ Removed from config: ${removedNames.join(', ')}`));
452
- console.log(chalk.gray('Note: AI instruction files in project root were not deleted.'));
453
- console.log(chalk.gray('You can manually delete them if needed.\n'));
555
+ // Remove ProAgents sections from AI files (smart removal - keeps user content)
556
+ const results = { deleted: [], cleaned: [], skipped: [] };
557
+
558
+ for (const id of toRemove) {
559
+ const platform = getPlatformById(id);
560
+ if (!platform) continue;
561
+
562
+ const filePath = join(targetDir, platform.file);
563
+ if (existsSync(filePath)) {
564
+ const result = removeProagentsSectionFromFile(filePath);
565
+ if (result === 'deleted') {
566
+ results.deleted.push(platform.name);
567
+ } else if (result === 'cleaned') {
568
+ results.cleaned.push(platform.name);
569
+ }
570
+ }
571
+ }
572
+
573
+ // Show results
574
+ console.log('');
575
+ if (results.deleted.length > 0) {
576
+ console.log(chalk.green(`✓ Removed: ${results.deleted.join(', ')}`));
577
+ }
578
+ if (results.cleaned.length > 0) {
579
+ console.log(chalk.green(`✓ Cleaned ProAgents section from: ${results.cleaned.join(', ')} (kept your custom config)`));
580
+ }
581
+
582
+ console.log(chalk.gray('\nConfig updated in proagents.config.yaml\n'));
454
583
  }
@@ -28,12 +28,12 @@ export function configListCommand() {
28
28
  // 1. Main Config
29
29
  console.log(chalk.cyan.bold('1. Main Configuration'));
30
30
  console.log(chalk.gray(' ─────────────────────────────────────────'));
31
- const configPath = join(proagentsDir, 'proagents.config.yaml');
31
+ const configPath = join(targetDir, 'proagents.config.yaml');
32
32
  if (existsSync(configPath)) {
33
- console.log(chalk.green(' ✓ ') + chalk.white('.proagents/proagents.config.yaml'));
33
+ console.log(chalk.green(' ✓ ') + chalk.white('proagents.config.yaml'));
34
34
  console.log(chalk.gray(' Checkpoints, git settings, parallel features, etc.\n'));
35
35
  } else {
36
- console.log(chalk.yellow(' ○ ') + chalk.white('.proagents/proagents.config.yaml') + chalk.gray(' (not created)\n'));
36
+ console.log(chalk.yellow(' ○ ') + chalk.white('proagents.config.yaml') + chalk.gray(' (not created)\n'));
37
37
  }
38
38
 
39
39
  // 2. AI Platforms
@@ -184,7 +184,7 @@ export function configListCommand() {
184
184
  */
185
185
  export function configShowCommand() {
186
186
  const targetDir = process.cwd();
187
- const configPath = join(targetDir, '.proagents', 'proagents.config.yaml');
187
+ const configPath = join(targetDir, 'proagents.config.yaml');
188
188
 
189
189
  console.log('\n' + chalk.bold.blue('ProAgents Current Configuration'));
190
190
  console.log(chalk.blue('================================\n'));
@@ -198,7 +198,7 @@ export function configShowCommand() {
198
198
  const content = readFileSync(configPath, 'utf-8');
199
199
  const config = yaml.load(content);
200
200
 
201
- console.log(chalk.cyan('File: ') + chalk.white('.proagents/proagents.config.yaml\n'));
201
+ console.log(chalk.cyan('File: ') + chalk.white('proagents.config.yaml\n'));
202
202
  console.log(chalk.gray('─────────────────────────────────────────────\n'));
203
203
  console.log(content);
204
204
  } catch (error) {
@@ -212,7 +212,7 @@ export function configShowCommand() {
212
212
  */
213
213
  export function configEditCommand() {
214
214
  const targetDir = process.cwd();
215
- const configPath = join(targetDir, '.proagents', 'proagents.config.yaml');
215
+ const configPath = join(targetDir, 'proagents.config.yaml');
216
216
 
217
217
  if (!existsSync(configPath)) {
218
218
  console.log(chalk.yellow('\nConfig file not found. Run "proagents init" first.\n'));
@@ -220,7 +220,7 @@ export function configEditCommand() {
220
220
  }
221
221
 
222
222
  console.log(chalk.cyan('\nTo edit configuration:\n'));
223
- console.log(chalk.white(' Open: ') + chalk.green('.proagents/proagents.config.yaml'));
223
+ console.log(chalk.white(' Open: ') + chalk.green('proagents.config.yaml'));
224
224
  console.log(chalk.white(' Docs: ') + chalk.green('.proagents/config/README.md\n'));
225
225
  }
226
226
 
@@ -261,7 +261,7 @@ function parseValue(value) {
261
261
  */
262
262
  export function configSetCommand(key, value) {
263
263
  const targetDir = process.cwd();
264
- const configPath = join(targetDir, '.proagents', 'proagents.config.yaml');
264
+ const configPath = join(targetDir, 'proagents.config.yaml');
265
265
 
266
266
  console.log('');
267
267
 
@@ -297,7 +297,7 @@ export function configSetCommand(key, value) {
297
297
  */
298
298
  export function configGetCommand(key) {
299
299
  const targetDir = process.cwd();
300
- const configPath = join(targetDir, '.proagents', 'proagents.config.yaml');
300
+ const configPath = join(targetDir, 'proagents.config.yaml');
301
301
 
302
302
  console.log('');
303
303
 
@@ -324,13 +324,25 @@ export function configGetCommand(key) {
324
324
  }
325
325
  }
326
326
 
327
+ /**
328
+ * Save config setup progress (for resume after interrupt)
329
+ */
330
+ function saveSetupProgress(configPath, config, step) {
331
+ config._config_setup = {
332
+ last_step: step,
333
+ timestamp: new Date().toISOString()
334
+ };
335
+ const yamlContent = yaml.dump(config, { indent: 2, lineWidth: 120 });
336
+ writeFileSync(configPath, yamlContent);
337
+ }
338
+
327
339
  /**
328
340
  * Command: proagents config setup
329
341
  * Interactive wizard for main configuration
330
342
  */
331
343
  export async function configSetupCommand() {
332
344
  const targetDir = process.cwd();
333
- const configPath = join(targetDir, '.proagents', 'proagents.config.yaml');
345
+ const configPath = join(targetDir, 'proagents.config.yaml');
334
346
 
335
347
  console.log('\n' + chalk.bold.blue('ProAgents Configuration Wizard'));
336
348
  console.log(chalk.blue('===============================\n'));
@@ -366,15 +378,22 @@ export async function configSetupCommand() {
366
378
  config = yaml.load(readFileSync(configPath, 'utf-8')) || {};
367
379
  }
368
380
 
381
+ // Check for interrupted setup
382
+ if (config._config_setup) {
383
+ console.log(chalk.cyan('📋 Resuming with your previous choices...\n'));
384
+ }
385
+
369
386
  console.log(chalk.cyan('Project Settings\n'));
370
387
 
371
388
  // Project settings
372
389
  config.project = config.project || {};
373
390
  config.project.name = await question('Project name', config.project.name || 'My Project');
391
+ saveSetupProgress(configPath, config, 'project_name');
374
392
 
375
393
  const projectTypes = ['web-frontend', 'fullstack', 'mobile', 'backend'];
376
394
  console.log(chalk.gray(' Types: ' + projectTypes.join(', ')));
377
395
  config.project.type = await question('Project type', config.project.type || 'fullstack');
396
+ saveSetupProgress(configPath, config, 'project_type');
378
397
 
379
398
  console.log('\n' + chalk.cyan('Checkpoints (pause for approval)\n'));
380
399
 
@@ -386,12 +405,14 @@ export async function configSetupCommand() {
386
405
  config.checkpoints.after_implementation = await yesNo('Pause after implementation?', config.checkpoints.after_implementation ?? false);
387
406
  config.checkpoints.after_testing = await yesNo('Pause after testing?', config.checkpoints.after_testing ?? false);
388
407
  config.checkpoints.before_deployment = await yesNo('Pause before deployment?', config.checkpoints.before_deployment ?? true);
408
+ saveSetupProgress(configPath, config, 'checkpoints');
389
409
 
390
410
  console.log('\n' + chalk.cyan('Git Settings\n'));
391
411
 
392
412
  // Git settings
393
413
  config.git = config.git || {};
394
414
  config.git.enabled = await yesNo('Enable git integration?', config.git.enabled ?? true);
415
+ saveSetupProgress(configPath, config, 'git_enabled');
395
416
 
396
417
  if (config.git.enabled) {
397
418
  config.git.branch_prefix = await question('Branch prefix', config.git.branch_prefix || 'feature/');
@@ -400,6 +421,7 @@ export async function configSetupCommand() {
400
421
  console.log(chalk.gray(' Conventions: ' + conventions.join(', ')));
401
422
  config.git.commit_convention = await question('Commit convention', config.git.commit_convention || 'conventional');
402
423
  config.git.require_pr = await yesNo('Require pull requests?', config.git.require_pr ?? true);
424
+ saveSetupProgress(configPath, config, 'git');
403
425
  }
404
426
 
405
427
  console.log('\n' + chalk.cyan('Parallel Features\n'));
@@ -407,20 +429,25 @@ export async function configSetupCommand() {
407
429
  // Parallel features
408
430
  config.parallel_features = config.parallel_features || {};
409
431
  config.parallel_features.enabled = await yesNo('Enable parallel features?', config.parallel_features.enabled ?? true);
432
+ saveSetupProgress(configPath, config, 'parallel_enabled');
410
433
 
411
434
  if (config.parallel_features.enabled) {
412
435
  const maxStr = await question('Max concurrent features', String(config.parallel_features.max_concurrent || 3));
413
436
  config.parallel_features.max_concurrent = parseInt(maxStr) || 3;
437
+ saveSetupProgress(configPath, config, 'parallel');
414
438
  }
415
439
 
416
440
  rl.close();
417
441
 
418
- // Save config
442
+ // Remove setup progress marker (setup complete)
443
+ delete config._config_setup;
444
+
445
+ // Save final config
419
446
  const header = `# ProAgents Configuration\n# Generated by setup wizard\n# Last updated: ${new Date().toISOString().split('T')[0]}\n\n`;
420
447
  const yamlContent = yaml.dump(config, { indent: 2, lineWidth: 120 });
421
448
  writeFileSync(configPath, header + yamlContent);
422
449
 
423
- console.log(chalk.green('\n✓ Configuration saved to proagents/proagents.config.yaml\n'));
450
+ console.log(chalk.green('\n✓ Configuration saved to proagents.config.yaml\n'));
424
451
 
425
452
  } catch (error) {
426
453
  rl.close();
@@ -595,6 +595,77 @@ function detectProjectType(targetDir) {
595
595
  return detectedTypes;
596
596
  }
597
597
 
598
+ /**
599
+ * Check if setup was interrupted (incomplete)
600
+ * Returns true if .proagents exists but setup wasn't completed
601
+ */
602
+ function checkIncompleteSetup(targetDir) {
603
+ const configPath = join(targetDir, 'proagents.config.yaml');
604
+ const readmePath = join(targetDir, 'README.md');
605
+ const gitignorePath = join(targetDir, '.gitignore');
606
+
607
+ // Check 1: No config file in root = incomplete
608
+ if (!existsSync(configPath)) {
609
+ return true;
610
+ }
611
+
612
+ // Check 2: No AI instruction files at all = incomplete
613
+ const aiFiles = ['CLAUDE.md', '.cursorrules', '.windsurfrules', 'CHATGPT.md', 'GEMINI.md'];
614
+ const hasAnyAiFile = aiFiles.some(f => existsSync(join(targetDir, f)));
615
+ if (!hasAnyAiFile) {
616
+ return true;
617
+ }
618
+
619
+ // Check 3: README exists but no ProAgents section = incomplete
620
+ if (existsSync(readmePath)) {
621
+ const readmeContent = readFileSync(readmePath, 'utf-8');
622
+ if (!readmeContent.includes('PROAGENTS:START')) {
623
+ return true;
624
+ }
625
+ }
626
+
627
+ // Check 4: .gitignore exists but no proagents entry = incomplete
628
+ if (existsSync(gitignorePath)) {
629
+ const gitignoreContent = readFileSync(gitignorePath, 'utf-8');
630
+ if (!gitignoreContent.includes('.proagents')) {
631
+ return true;
632
+ }
633
+ }
634
+
635
+ return false;
636
+ }
637
+
638
+ /**
639
+ * Prompt user for what to do with incomplete setup
640
+ */
641
+ async function promptIncompleteSetupChoice() {
642
+ const rl = createInterface({
643
+ input: process.stdin,
644
+ output: process.stdout
645
+ });
646
+
647
+ const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
648
+
649
+ console.log(chalk.cyan('What would you like to do?'));
650
+ console.log(chalk.white(' 1. Continue setup') + chalk.green(' (recommended)'));
651
+ console.log(chalk.gray(' Complete the remaining setup steps'));
652
+ console.log('');
653
+ console.log(chalk.white(' 2. Restart fresh'));
654
+ console.log(chalk.gray(' Remove everything and start over'));
655
+ console.log('');
656
+ console.log(chalk.white(' 3. Update only'));
657
+ console.log(chalk.gray(' Just update framework files, skip setup'));
658
+ console.log('');
659
+
660
+ const choice = await question(chalk.yellow(' Choose (1, 2, or 3, default=1): '));
661
+ rl.close();
662
+
663
+ const trimmed = choice.trim();
664
+ if (trimmed === '2') return 'restart';
665
+ if (trimmed === '3') return 'update';
666
+ return 'continue';
667
+ }
668
+
598
669
  /**
599
670
  * Prompt user for .gitignore preference
600
671
  */
@@ -626,8 +697,10 @@ async function promptGitignoreChoice() {
626
697
 
627
698
  /**
628
699
  * Interactive prompt for project configuration
700
+ * @param {string} targetDir - The target directory
701
+ * @param {object} existingConfig - Existing partial config from interrupted setup
629
702
  */
630
- async function promptProjectConfig(targetDir) {
703
+ async function promptProjectConfig(targetDir, existingConfig = {}) {
631
704
  const rl = createInterface({
632
705
  input: process.stdin,
633
706
  output: process.stdout
@@ -635,12 +708,19 @@ async function promptProjectConfig(targetDir) {
635
708
 
636
709
  const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
637
710
 
711
+ // Get saved values from interrupted setup
712
+ const savedProject = existingConfig.project || {};
713
+ const savedName = savedProject.name;
714
+ const savedType = savedProject.type;
715
+
638
716
  // Detect project name
639
- const detectedName = detectProjectName(targetDir);
717
+ const detectedName = savedName || detectProjectName(targetDir);
640
718
 
641
719
  // Detect project types
642
720
  const detectedTypes = detectProjectType(targetDir);
643
- const topDetectedType = detectedTypes.length > 0 ? detectedTypes[0] : null;
721
+ const topDetectedType = savedType
722
+ ? PROJECT_TYPES.find(t => t.id === savedType)
723
+ : (detectedTypes.length > 0 ? detectedTypes[0] : null);
644
724
 
645
725
  // Detect tech stack
646
726
  const detectedTechStack = detectTechStack(targetDir);
@@ -648,9 +728,16 @@ async function promptProjectConfig(targetDir) {
648
728
  console.log(chalk.bold('\nProject Configuration'));
649
729
  console.log(chalk.gray('─'.repeat(40) + '\n'));
650
730
 
731
+ // Show resume notice if we have saved values
732
+ if (savedName || savedType) {
733
+ console.log(chalk.cyan('📋 Resuming with your previous choices...\n'));
734
+ }
735
+
651
736
  // Project Name
652
737
  console.log(chalk.cyan('Project Name'));
653
- if (detectedName) {
738
+ if (savedName) {
739
+ console.log(chalk.green(` Previously saved: ${savedName}`));
740
+ } else if (detectedName) {
654
741
  console.log(chalk.gray(` Detected: ${detectedName}`));
655
742
  }
656
743
  const nameInput = await question(chalk.yellow(` Enter name (press Enter for "${detectedName}"): `));
@@ -765,6 +852,44 @@ function applyTemplate(templateId, targetDir) {
765
852
  };
766
853
  }
767
854
 
855
+ /**
856
+ * Load existing partial config (for resume after interrupted setup)
857
+ */
858
+ function loadPartialConfig(configPath) {
859
+ if (!existsSync(configPath)) return {};
860
+
861
+ try {
862
+ const content = readFileSync(configPath, 'utf-8');
863
+ return yaml.load(content) || {};
864
+ } catch {
865
+ return {};
866
+ }
867
+ }
868
+
869
+ /**
870
+ * Save partial config progress (called after each setup step)
871
+ */
872
+ function savePartialConfig(configPath, updates) {
873
+ let config = loadPartialConfig(configPath);
874
+
875
+ // Deep merge updates into config
876
+ for (const [key, value] of Object.entries(updates)) {
877
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
878
+ config[key] = { ...(config[key] || {}), ...value };
879
+ } else {
880
+ config[key] = value;
881
+ }
882
+ }
883
+
884
+ // Add setup progress marker
885
+ config._setup = config._setup || {};
886
+ config._setup.last_step = updates._step || config._setup.last_step;
887
+ config._setup.timestamp = new Date().toISOString();
888
+
889
+ const yamlContent = yaml.dump(config, { indent: 2, lineWidth: 120 });
890
+ writeFileSync(configPath, yamlContent);
891
+ }
892
+
768
893
  /**
769
894
  * Save project config to proagents.config.yaml
770
895
  */
@@ -829,6 +954,10 @@ export async function initCommand(options = {}) {
829
954
  const targetDir = process.cwd();
830
955
  const proagentsDir = join(targetDir, '.proagents');
831
956
  const sourceDir = join(__dirname, '..', '..', '.proagents');
957
+ const configPath = join(targetDir, 'proagents.config.yaml');
958
+
959
+ // Load any existing partial config (from interrupted setup)
960
+ const existingConfig = loadPartialConfig(configPath);
832
961
 
833
962
  console.log('\n' + chalk.bold.blue('ProAgents Initialization'));
834
963
  console.log(chalk.blue('========================\n'));
@@ -836,24 +965,56 @@ export async function initCommand(options = {}) {
836
965
  // Check if already initialized
837
966
  const alreadyInitialized = existsSync(proagentsDir);
838
967
 
968
+ // Check for incomplete setup (user killed terminal mid-setup)
969
+ const isIncompleteSetup = alreadyInitialized && checkIncompleteSetup(targetDir);
970
+
839
971
  if (alreadyInitialized && !options.force) {
840
- // Smart update mode - preserve user files, update framework
841
- console.log(chalk.cyan('ℹ️ ProAgents detected. Running smart update...'));
842
- console.log(chalk.gray(' (Preserving your customizations)\n'));
972
+ if (isIncompleteSetup) {
973
+ // Incomplete setup detected - ask user what to do
974
+ console.log(chalk.yellow('⚠️ Incomplete setup detected!'));
975
+ console.log(chalk.gray(' Previous setup was interrupted before completion.\n'));
976
+
977
+ const choice = await promptIncompleteSetupChoice();
978
+
979
+ if (choice === 'continue') {
980
+ console.log(chalk.cyan('\nContinuing setup...\n'));
981
+ // Fall through to fresh install (but keep .proagents folder)
982
+ } else if (choice === 'restart') {
983
+ console.log(chalk.cyan('\nRestarting fresh setup...\n'));
984
+ rmSync(proagentsDir, { recursive: true, force: true });
985
+ // Fall through to fresh install
986
+ } else {
987
+ // Update only
988
+ console.log(chalk.cyan('\nRunning smart update...\n'));
989
+ try {
990
+ await smartUpdate(sourceDir, proagentsDir);
991
+ console.log(chalk.green('\n✓ ProAgents updated successfully!\n'));
992
+ return;
993
+ } catch (error) {
994
+ console.error(chalk.red('\n✗ Error updating ProAgents:'));
995
+ console.error(chalk.red(error.message));
996
+ process.exit(1);
997
+ }
998
+ }
999
+ } else {
1000
+ // Complete setup exists - run smart update
1001
+ console.log(chalk.cyan('ℹ️ ProAgents detected. Running smart update...'));
1002
+ console.log(chalk.gray(' (Preserving your customizations)\n'));
843
1003
 
844
- try {
845
- await smartUpdate(sourceDir, proagentsDir);
846
- console.log(chalk.green('\n✓ ProAgents updated successfully!\n'));
847
- console.log(chalk.gray('Preserved:'));
848
- console.log(chalk.gray(' • active-features/ (your work in progress)'));
849
- console.log(chalk.gray(' • proagents.config.yaml (your values + new options merged)'));
850
- console.log(chalk.gray(' • .learning/ (learned patterns)'));
851
- console.log(chalk.gray(' • cache/ (analysis cache)\n'));
852
- return;
853
- } catch (error) {
854
- console.error(chalk.red('\n✗ Error updating ProAgents:'));
855
- console.error(chalk.red(error.message));
856
- process.exit(1);
1004
+ try {
1005
+ await smartUpdate(sourceDir, proagentsDir);
1006
+ console.log(chalk.green('\n✓ ProAgents updated successfully!\n'));
1007
+ console.log(chalk.gray('Preserved:'));
1008
+ console.log(chalk.gray(' • active-features/ (your work in progress)'));
1009
+ console.log(chalk.gray(' • proagents.config.yaml (your values + new options merged)'));
1010
+ console.log(chalk.gray(' • .learning/ (learned patterns)'));
1011
+ console.log(chalk.gray(' • cache/ (analysis cache)\n'));
1012
+ return;
1013
+ } catch (error) {
1014
+ console.error(chalk.red('\n✗ Error updating ProAgents:'));
1015
+ console.error(chalk.red(error.message));
1016
+ process.exit(1);
1017
+ }
857
1018
  }
858
1019
  }
859
1020
 
@@ -949,6 +1110,12 @@ Generated by [ProAgents](https://github.com/prakashpro3/proAgents)
949
1110
  } else {
950
1111
  console.log(chalk.green('✓ Added .proagents/ local data to .gitignore'));
951
1112
  }
1113
+
1114
+ // Save progress after gitignore choice
1115
+ savePartialConfig(configPath, {
1116
+ gitignore_mode: gitignoreChoice,
1117
+ _step: 'gitignore'
1118
+ });
952
1119
  }
953
1120
 
954
1121
  // Create placeholder CHANGELOG.md if not exists
@@ -1016,10 +1183,19 @@ No releases yet. Use \`pa:release\` to generate release notes.
1016
1183
  }
1017
1184
  console.log(chalk.green(`\n✓ Using template: ${PROJECT_TEMPLATES[options.template].name}`));
1018
1185
  } else {
1019
- // Interactive prompts
1020
- projectConfig = await promptProjectConfig(targetDir);
1186
+ // Interactive prompts - pass existing config for pre-filling
1187
+ projectConfig = await promptProjectConfig(targetDir, existingConfig);
1021
1188
  }
1022
1189
 
1190
+ // Save progress after project config
1191
+ savePartialConfig(configPath, {
1192
+ project: {
1193
+ name: projectConfig.name,
1194
+ type: projectConfig.type
1195
+ },
1196
+ _step: 'project_config'
1197
+ });
1198
+
1023
1199
  // Add ProAgents section to README.md (AI tools auto-read this)
1024
1200
  const readmePath = join(targetDir, 'README.md');
1025
1201
  const proagentsSection = `
@@ -1058,9 +1234,16 @@ For detailed commands, see \`./.proagents/PROAGENTS.md\`
1058
1234
  selectedPlatforms = projectConfig.platforms;
1059
1235
  console.log(chalk.gray(` Using template platforms: ${selectedPlatforms.join(', ')}`));
1060
1236
  } else {
1061
- selectedPlatforms = await selectPlatforms();
1237
+ // Pass existing config for pre-selection
1238
+ selectedPlatforms = await selectPlatforms(existingConfig.ai_platforms);
1062
1239
  }
1063
1240
 
1241
+ // Save progress after platform selection
1242
+ savePartialConfig(configPath, {
1243
+ ai_platforms: selectedPlatforms,
1244
+ _step: 'platforms'
1245
+ });
1246
+
1064
1247
  // Copy AI instruction files for selected platforms (merges with existing files)
1065
1248
  const aiResults = copyPlatformFiles(selectedPlatforms, sourceDir, targetDir);
1066
1249
 
@@ -1074,11 +1257,16 @@ For detailed commands, see \`./.proagents/PROAGENTS.md\`
1074
1257
  console.log(chalk.green(`✓ Merged with existing: ${aiResults.merged.join(', ')}`));
1075
1258
  }
1076
1259
 
1077
- // Save project and platform config to ROOT config (not proagents/ folder)
1078
- const configPath = join(targetDir, 'proagents.config.yaml');
1260
+ // Save final project and platform config (removes _setup marker)
1079
1261
  saveProjectConfig(projectConfig, configPath);
1080
1262
  savePlatformConfig(selectedPlatforms, configPath);
1081
1263
 
1264
+ // Clean up setup progress marker
1265
+ const finalConfig = loadPartialConfig(configPath);
1266
+ delete finalConfig._setup;
1267
+ const yamlContent = yaml.dump(finalConfig, { indent: 2, lineWidth: 120 });
1268
+ writeFileSync(configPath, yamlContent);
1269
+
1082
1270
  // Success message
1083
1271
  console.log(chalk.green('\n✓ ProAgents initialized successfully!\n'));
1084
1272
 
@@ -19,6 +19,50 @@ const AI_FILES = [
19
19
  'AI_INSTRUCTIONS.md',
20
20
  ];
21
21
 
22
+ // ProAgents markers used in merged files
23
+ const PROAGENTS_START = '<!-- PROAGENTS:START -->';
24
+ const PROAGENTS_END = '<!-- PROAGENTS:END -->';
25
+
26
+ /**
27
+ * Remove only ProAgents section from a file, keep user's original content
28
+ * Returns: 'deleted' (file removed), 'cleaned' (section removed), 'skipped' (no ProAgents section)
29
+ */
30
+ function removeProagentsSectionFromFile(filePath) {
31
+ try {
32
+ const content = readFileSync(filePath, 'utf-8');
33
+
34
+ const startIndex = content.indexOf(PROAGENTS_START);
35
+ const endIndex = content.indexOf(PROAGENTS_END);
36
+
37
+ if (startIndex !== -1 && endIndex !== -1) {
38
+ // Has ProAgents section - remove it, keep the rest
39
+ const before = content.substring(0, startIndex).trim();
40
+ const after = content.substring(endIndex + PROAGENTS_END.length).trim();
41
+ const remaining = (before + '\n\n' + after).trim();
42
+
43
+ if (remaining.length === 0) {
44
+ // File only had ProAgents content - delete it
45
+ rmSync(filePath, { force: true });
46
+ return 'deleted';
47
+ } else {
48
+ // File has other content - keep it, remove only ProAgents section
49
+ writeFileSync(filePath, remaining + '\n');
50
+ return 'cleaned';
51
+ }
52
+ } else {
53
+ // No ProAgents markers - file was created by ProAgents (not merged)
54
+ // Check if it's a ProAgents-generated file by looking for ProAgents reference
55
+ if (content.includes('proagents') || content.includes('ProAgents') || content.includes('.proagents/')) {
56
+ rmSync(filePath, { force: true });
57
+ return 'deleted';
58
+ }
59
+ return 'skipped';
60
+ }
61
+ } catch (error) {
62
+ return 'skipped';
63
+ }
64
+ }
65
+
22
66
  /**
23
67
  * Command: proagents uninstall
24
68
  */
@@ -46,7 +90,7 @@ export async function uninstallCommand(options = {}) {
46
90
 
47
91
  console.log(chalk.yellow('This will remove:'));
48
92
  console.log(chalk.gray(' • ./.proagents/ folder'));
49
- console.log(chalk.gray(' • AI instruction files (CLAUDE.md, .cursorrules, etc.)'));
93
+ console.log(chalk.gray(' • ProAgents sections from AI files (keeps your original config)'));
50
94
  console.log(chalk.gray(' • ProAgents section from README.md\n'));
51
95
 
52
96
  const answer = await question(chalk.yellow('Are you sure? (yes/no): '));
@@ -66,26 +110,39 @@ export async function uninstallCommand(options = {}) {
66
110
  console.log(chalk.green('✓ Removed ./.proagents/ folder'));
67
111
  }
68
112
 
69
- // 2. Remove AI instruction files from project root
113
+ // 2. Remove AI instruction files from project root (smart removal)
70
114
  let aiFilesRemoved = 0;
115
+ let aiFilesCleaned = 0;
116
+
71
117
  for (const file of AI_FILES) {
72
118
  const filePath = join(targetDir, file);
73
119
  if (existsSync(filePath)) {
74
- rmSync(filePath, { force: true });
75
- aiFilesRemoved++;
120
+ const result = removeProagentsSectionFromFile(filePath);
121
+ if (result === 'deleted') {
122
+ aiFilesRemoved++;
123
+ } else if (result === 'cleaned') {
124
+ aiFilesCleaned++;
125
+ }
76
126
  }
77
127
  }
78
128
 
79
129
  // Remove .github/copilot-instructions.md
80
130
  const copilotPath = join(targetDir, '.github', 'copilot-instructions.md');
81
131
  if (existsSync(copilotPath)) {
82
- rmSync(copilotPath, { force: true });
83
- aiFilesRemoved++;
132
+ const result = removeProagentsSectionFromFile(copilotPath);
133
+ if (result === 'deleted') {
134
+ aiFilesRemoved++;
135
+ } else if (result === 'cleaned') {
136
+ aiFilesCleaned++;
137
+ }
84
138
  }
85
139
 
86
140
  if (aiFilesRemoved > 0) {
87
141
  console.log(chalk.green(`✓ Removed ${aiFilesRemoved} AI instruction file(s)`));
88
142
  }
143
+ if (aiFilesCleaned > 0) {
144
+ console.log(chalk.green(`✓ Cleaned ProAgents section from ${aiFilesCleaned} AI file(s) (kept original config)`));
145
+ }
89
146
 
90
147
  // 3. Remove ProAgents section from README.md
91
148
  const readmePath = join(targetDir, 'README.md');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proagents",
3
- "version": "1.6.7",
3
+ "version": "1.6.9",
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",