proagents 1.6.13 → 1.6.16

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.
@@ -24,13 +24,19 @@ export const AI_PLATFORMS = {
24
24
  web: {
25
25
  label: 'Web-based AI Platforms',
26
26
  platforms: [
27
- { id: 'chatgpt', name: 'ChatGPT / Codex', file: 'CHATGPT.md', desc: 'OpenAI ChatGPT' },
27
+ { id: 'chatgpt', name: 'ChatGPT', file: 'CHATGPT.md', desc: 'OpenAI ChatGPT' },
28
28
  { id: 'gemini', name: 'Gemini', file: 'GEMINI.md', desc: 'Google Gemini' },
29
29
  { id: 'replit', name: 'Replit AI', file: 'REPLIT.md', desc: 'Replit Ghostwriter' },
30
30
  { id: 'bolt', name: 'Bolt.new', file: 'BOLT.md', desc: 'StackBlitz Bolt' },
31
31
  { id: 'lovable', name: 'Lovable', file: 'LOVABLE.md', desc: 'Lovable (GPT Engineer)' },
32
32
  { id: 'groq', name: 'Groq', file: 'GROQ.md', desc: 'Groq fast inference' },
33
33
  ]
34
+ },
35
+ cli: {
36
+ label: 'CLI-based AI Agents',
37
+ platforms: [
38
+ { id: 'codex', name: 'Codex CLI', file: 'AGENTS.md', desc: 'OpenAI Codex CLI' },
39
+ ]
34
40
  }
35
41
  };
36
42
 
@@ -39,6 +45,7 @@ export function getAllPlatforms() {
39
45
  return [
40
46
  ...AI_PLATFORMS.ide.platforms,
41
47
  ...AI_PLATFORMS.web.platforms,
48
+ ...AI_PLATFORMS.cli.platforms,
42
49
  ];
43
50
  }
44
51
 
@@ -47,6 +54,39 @@ export function getPlatformById(id) {
47
54
  return getAllPlatforms().find(p => p.id === id);
48
55
  }
49
56
 
57
+ /**
58
+ * Detect current IDE from environment variables
59
+ * Returns platform ID if detected and it's in our AI_PLATFORMS config, null otherwise
60
+ * Note: VS Code is NOT an AI platform, so we skip it even if detected
61
+ */
62
+ export function detectIDE() {
63
+ // Cursor IDE - check for Cursor-specific env vars
64
+ if (process.env.CURSOR_TRACE_ID || process.env.CURSOR_CHANNEL || process.env.CURSOR_SESSION_ID) {
65
+ return 'cursor';
66
+ }
67
+
68
+ // Windsurf IDE - Codeium's IDE
69
+ if (process.env.WINDSURF_SESSION_ID || process.env.CODEIUM_API_KEY ||
70
+ (process.env.TERM_PROGRAM && process.env.TERM_PROGRAM.toLowerCase().includes('windsurf'))) {
71
+ return 'windsurf';
72
+ }
73
+
74
+ // AWS Kiro IDE
75
+ if (process.env.KIRO_SESSION || process.env.KIRO_API_KEY ||
76
+ (process.env.TERM_PROGRAM && process.env.TERM_PROGRAM.toLowerCase().includes('kiro'))) {
77
+ return 'kiro';
78
+ }
79
+
80
+ // VS Code - NOT an AI platform, explicitly return null
81
+ // This prevents accidental detection as we don't want to create files for plain VS Code
82
+ if (process.env.TERM_PROGRAM === 'vscode' || process.env.VSCODE_GIT_IPC_HANDLE) {
83
+ return null;
84
+ }
85
+
86
+ // No AI-powered IDE detected
87
+ return null;
88
+ }
89
+
50
90
  /**
51
91
  * Interactive platform selection using readline
52
92
  * @param {string[]} previouslySelected - Previously selected platforms (from interrupted setup)
@@ -59,6 +99,21 @@ export async function selectPlatforms(previouslySelected = []) {
59
99
 
60
100
  const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
61
101
 
102
+ // Auto-detect IDE and pre-select it
103
+ const detectedIDE = detectIDE();
104
+ let autoSelected = [];
105
+
106
+ if (detectedIDE) {
107
+ const platform = getPlatformById(detectedIDE);
108
+ if (platform) {
109
+ console.log(chalk.green(`\n✓ Detected ${platform.name} IDE - auto-selected`));
110
+ autoSelected = [detectedIDE];
111
+ }
112
+ }
113
+
114
+ // Merge auto-detected with previously selected
115
+ const preselected = [...new Set([...autoSelected, ...(previouslySelected || [])])];
116
+
62
117
  console.log('\n' + chalk.bold('Which AI platform(s) do you use?'));
63
118
  console.log(chalk.gray('(Enter numbers separated by commas, or "all" for all platforms)\n'));
64
119
 
@@ -69,11 +124,18 @@ export async function selectPlatforms(previouslySelected = []) {
69
124
  // IDE-based platforms
70
125
  console.log(chalk.cyan.bold(` ${AI_PLATFORMS.ide.label}:`));
71
126
  for (const platform of AI_PLATFORMS.ide.platforms) {
72
- const wasSelected = previouslySelected && previouslySelected.includes(platform.id);
73
- const marker = wasSelected ? chalk.green(' ✓ (previously selected)') : '';
127
+ const isAutoDetected = autoSelected.includes(platform.id);
128
+ const wasPreviouslySelected = previouslySelected && previouslySelected.includes(platform.id);
129
+ const isPreselected = preselected.includes(platform.id);
130
+ let marker = '';
131
+ if (isAutoDetected) {
132
+ marker = chalk.green(' ✓ (auto-detected)');
133
+ } else if (wasPreviouslySelected) {
134
+ marker = chalk.green(' ✓ (previously selected)');
135
+ }
74
136
  console.log(chalk.white(` ${index}. ${platform.name}`) + chalk.gray(` - ${platform.desc}`) + marker);
75
137
  indexMap[index] = platform.id;
76
- if (wasSelected) preSelectedIndices.push(index);
138
+ if (isPreselected) preSelectedIndices.push(index);
77
139
  index++;
78
140
  }
79
141
 
@@ -82,27 +144,42 @@ export async function selectPlatforms(previouslySelected = []) {
82
144
  // Web-based platforms
83
145
  console.log(chalk.cyan.bold(` ${AI_PLATFORMS.web.label}:`));
84
146
  for (const platform of AI_PLATFORMS.web.platforms) {
85
- const wasSelected = previouslySelected && previouslySelected.includes(platform.id);
86
- const marker = wasSelected ? chalk.green(' ✓ (previously selected)') : '';
147
+ const wasPreviouslySelected = previouslySelected && previouslySelected.includes(platform.id);
148
+ const isPreselected = preselected.includes(platform.id);
149
+ const marker = wasPreviouslySelected ? chalk.green(' ✓ (previously selected)') : '';
87
150
  console.log(chalk.white(` ${index}. ${platform.name}`) + chalk.gray(` - ${platform.desc}`) + marker);
88
151
  indexMap[index] = platform.id;
89
- if (wasSelected) preSelectedIndices.push(index);
152
+ if (isPreselected) preSelectedIndices.push(index);
90
153
  index++;
91
154
  }
92
155
 
93
156
  console.log('');
94
157
 
95
- // Show default based on previous selection
158
+ // CLI-based platforms
159
+ console.log(chalk.cyan.bold(` ${AI_PLATFORMS.cli.label}:`));
160
+ for (const platform of AI_PLATFORMS.cli.platforms) {
161
+ const wasPreviouslySelected = previouslySelected && previouslySelected.includes(platform.id);
162
+ const isPreselected = preselected.includes(platform.id);
163
+ const marker = wasPreviouslySelected ? chalk.green(' ✓ (previously selected)') : '';
164
+ console.log(chalk.white(` ${index}. ${platform.name}`) + chalk.gray(` - ${platform.desc}`) + marker);
165
+ indexMap[index] = platform.id;
166
+ if (isPreselected) preSelectedIndices.push(index);
167
+ index++;
168
+ }
169
+
170
+ console.log('');
171
+
172
+ // Show default based on preselected (auto-detected + previously selected)
96
173
  const defaultHint = preSelectedIndices.length > 0
97
- ? `Enter for previous: ${preSelectedIndices.join(',')}`
174
+ ? `Enter for selected: ${preSelectedIndices.join(',')}`
98
175
  : 'e.g., 1,2,3 or "all"';
99
176
 
100
177
  const answer = await question(chalk.yellow(`Your selection (${defaultHint}): `));
101
178
  rl.close();
102
179
 
103
- // If user just pressed enter and we have previous selections, use them
180
+ // If user just pressed enter and we have preselected platforms, use them
104
181
  if (answer.trim() === '' && preSelectedIndices.length > 0) {
105
- return previouslySelected;
182
+ return preselected;
106
183
  }
107
184
 
108
185
  if (answer.toLowerCase() === 'all') {
@@ -387,6 +464,21 @@ export function showAvailablePlatforms(currentIds) {
387
464
  index++;
388
465
  }
389
466
 
467
+ console.log('');
468
+
469
+ // CLI-based platforms
470
+ console.log(chalk.cyan.bold(` ${AI_PLATFORMS.cli.label}:`));
471
+ for (const platform of AI_PLATFORMS.cli.platforms) {
472
+ const status = currentIds.includes(platform.id)
473
+ ? chalk.green(' ✓ (installed)')
474
+ : chalk.gray(' (not installed)');
475
+ console.log(chalk.white(` ${index}. ${platform.name}`) + status);
476
+ if (!currentIds.includes(platform.id)) {
477
+ available.push({ index, platform });
478
+ }
479
+ index++;
480
+ }
481
+
390
482
  return available;
391
483
  }
392
484
 
@@ -16,8 +16,131 @@ const PRESERVE_PATHS = [
16
16
  'cache', // Cached analysis
17
17
  'changelog', // Change history (user data)
18
18
  'worklog', // Work context (user data)
19
+ 'sessions', // Session data
20
+ 'backups', // User backups
19
21
  ];
20
22
 
23
+ // Files within PRESERVE_PATHS that should always be UPDATED (framework files)
24
+ // These patterns match files that should be overwritten with latest framework version
25
+ const UPDATE_WITHIN_PRESERVED = [
26
+ 'README.md', // Documentation
27
+ 'entry-template.md', // Changelog entry template
28
+ '*.template.md', // All template markdown files
29
+ '*.template.json', // All template JSON files
30
+ 'schemas', // Schema folders
31
+ ];
32
+
33
+ /**
34
+ * Backup user data before fresh install
35
+ * Returns object mapping path to temp backup location
36
+ */
37
+ function backupUserData(proagentsDir) {
38
+ const preserved = {};
39
+ for (const path of PRESERVE_PATHS) {
40
+ const fullPath = join(proagentsDir, path);
41
+ if (existsSync(fullPath)) {
42
+ const tempPath = join(proagentsDir, '..', `.proagents-backup-${path.replace(/\//g, '-')}`);
43
+ try {
44
+ cpSync(fullPath, tempPath, { recursive: true });
45
+ preserved[path] = tempPath;
46
+ } catch (err) {
47
+ // Silently continue if backup fails
48
+ }
49
+ }
50
+ }
51
+ return preserved;
52
+ }
53
+
54
+ /**
55
+ * Restore user data after fresh install
56
+ */
57
+ function restoreUserData(proagentsDir, preserved) {
58
+ for (const [path, tempPath] of Object.entries(preserved)) {
59
+ const fullPath = join(proagentsDir, path);
60
+ if (existsSync(tempPath)) {
61
+ try {
62
+ // Remove the framework's empty/default version
63
+ if (existsSync(fullPath)) {
64
+ rmSync(fullPath, { recursive: true, force: true });
65
+ }
66
+ // Restore user's data
67
+ cpSync(tempPath, fullPath, { recursive: true });
68
+ // Clean up temp backup
69
+ rmSync(tempPath, { recursive: true, force: true });
70
+ } catch (err) {
71
+ // Silently continue if restore fails
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Update framework files within preserved paths
79
+ * After restoring user data, this copies framework files like README.md, *.template.*, schemas/
80
+ */
81
+ function updateFrameworkInPreserved(sourceDir, proagentsDir) {
82
+ for (const preservedPath of PRESERVE_PATHS) {
83
+ const sourcePath = join(sourceDir, preservedPath);
84
+ const targetPath = join(proagentsDir, preservedPath);
85
+
86
+ if (!existsSync(sourcePath) || !existsSync(targetPath)) continue;
87
+
88
+ try {
89
+ // Update README.md if exists
90
+ const sourceReadme = join(sourcePath, 'README.md');
91
+ const targetReadme = join(targetPath, 'README.md');
92
+ if (existsSync(sourceReadme)) {
93
+ cpSync(sourceReadme, targetReadme, { force: true });
94
+ }
95
+
96
+ // Update entry-template.md (for changelog)
97
+ const sourceEntryTemplate = join(sourcePath, 'entry-template.md');
98
+ const targetEntryTemplate = join(targetPath, 'entry-template.md');
99
+ if (existsSync(sourceEntryTemplate)) {
100
+ cpSync(sourceEntryTemplate, targetEntryTemplate, { force: true });
101
+ }
102
+
103
+ // Update schemas folder if exists
104
+ const sourceSchemas = join(sourcePath, 'schemas');
105
+ const targetSchemas = join(targetPath, 'schemas');
106
+ if (existsSync(sourceSchemas)) {
107
+ if (existsSync(targetSchemas)) {
108
+ rmSync(targetSchemas, { recursive: true, force: true });
109
+ }
110
+ cpSync(sourceSchemas, targetSchemas, { recursive: true });
111
+ }
112
+
113
+ // Update all *.template.md and *.template.json files
114
+ if (existsSync(sourcePath)) {
115
+ const entries = readdirSync(sourcePath, { withFileTypes: true });
116
+ for (const entry of entries) {
117
+ if (entry.isFile() && (entry.name.endsWith('.template.md') || entry.name.endsWith('.template.json'))) {
118
+ cpSync(join(sourcePath, entry.name), join(targetPath, entry.name), { force: true });
119
+ }
120
+ // Also check subdirectories (like .learning/global/)
121
+ if (entry.isDirectory()) {
122
+ const subPath = join(sourcePath, entry.name);
123
+ const subTargetPath = join(targetPath, entry.name);
124
+ if (existsSync(subPath)) {
125
+ const subEntries = readdirSync(subPath, { withFileTypes: true });
126
+ for (const subEntry of subEntries) {
127
+ if (subEntry.isFile() && (subEntry.name.endsWith('.template.md') || subEntry.name.endsWith('.template.json'))) {
128
+ if (!existsSync(subTargetPath)) {
129
+ mkdirSync(subTargetPath, { recursive: true });
130
+ }
131
+ cpSync(join(subPath, subEntry.name), join(subTargetPath, subEntry.name), { force: true });
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
138
+ } catch (err) {
139
+ // Silently continue if update fails
140
+ }
141
+ }
142
+ }
143
+
21
144
  // Config file is handled specially - merged not preserved
22
145
  const CONFIG_FILE = 'proagents.config.yaml';
23
146
 
@@ -90,6 +213,7 @@ const FRAMEWORK_FILES = [
90
213
  'GETTING-STARTED-STORY.md',
91
214
  'slash-commands.json',
92
215
  'AI_INSTRUCTIONS.md', // Universal instructions kept for reference
216
+ 'AGENTS.md', // Universal AI instruction file (works with most AI platforms)
93
217
  ];
94
218
 
95
219
  // Project type definitions for detection
@@ -611,7 +735,7 @@ function checkIncompleteSetup(targetDir) {
611
735
  }
612
736
 
613
737
  // Check 2: No AI instruction files at all = incomplete
614
- const aiFiles = ['CLAUDE.md', '.cursorrules', '.windsurfrules', 'CHATGPT.md', 'GEMINI.md'];
738
+ const aiFiles = ['AGENTS.md', 'CLAUDE.md', '.cursorrules', '.windsurfrules', 'CHATGPT.md', 'GEMINI.md'];
615
739
  const hasAnyAiFile = aiFiles.some(f => existsSync(join(targetDir, f)));
616
740
  if (!hasAnyAiFile) {
617
741
  return true;
@@ -667,6 +791,27 @@ async function promptIncompleteSetupChoice() {
667
791
  return 'continue';
668
792
  }
669
793
 
794
+ /**
795
+ * Confirm destructive "Restart fresh" action
796
+ */
797
+ async function confirmRestartFresh() {
798
+ const rl = createInterface({
799
+ input: process.stdin,
800
+ output: process.stdout
801
+ });
802
+
803
+ const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
804
+
805
+ console.log(chalk.red.bold('\n⚠️ WARNING: This will DELETE all data in .proagents/'));
806
+ console.log(chalk.yellow(' Including: changelog, worklog, sessions, learning data, etc.'));
807
+ console.log(chalk.gray(' This action cannot be undone.\n'));
808
+
809
+ const answer = await question(chalk.red('Type "DELETE" to confirm, or press Enter to cancel: '));
810
+ rl.close();
811
+
812
+ return answer.trim().toUpperCase() === 'DELETE';
813
+ }
814
+
670
815
  /**
671
816
  * Prompt user for .gitignore preference
672
817
  */
@@ -981,6 +1126,12 @@ export async function initCommand(options = {}) {
981
1126
  console.log(chalk.cyan('\nContinuing setup...\n'));
982
1127
  // Fall through to fresh install (but keep .proagents folder)
983
1128
  } else if (choice === 'restart') {
1129
+ // Confirm destructive action
1130
+ const confirmed = await confirmRestartFresh();
1131
+ if (!confirmed) {
1132
+ console.log(chalk.yellow('\nCancelled. No changes made.\n'));
1133
+ return;
1134
+ }
984
1135
  console.log(chalk.cyan('\nRestarting fresh setup...\n'));
985
1136
  rmSync(proagentsDir, { recursive: true, force: true });
986
1137
  // Fall through to fresh install
@@ -1026,7 +1177,21 @@ export async function initCommand(options = {}) {
1026
1177
 
1027
1178
  // Fresh install or force overwrite
1028
1179
  console.log(chalk.gray('Copying framework files...'));
1180
+
1181
+ // Backup user data if .proagents exists (to preserve changelog, worklog, etc.)
1182
+ const userDataBackup = existsSync(proagentsDir) ? backupUserData(proagentsDir) : {};
1183
+
1029
1184
  cpSync(sourceDir, proagentsDir, { recursive: true, force: true });
1185
+
1186
+ // Restore user data after framework copy
1187
+ if (Object.keys(userDataBackup).length > 0) {
1188
+ restoreUserData(proagentsDir, userDataBackup);
1189
+ // Update framework files within preserved paths (README.md, templates, schemas)
1190
+ updateFrameworkInPreserved(sourceDir, proagentsDir);
1191
+ console.log(chalk.green('✓ Preserved user data (changelog, worklog, sessions, etc.)'));
1192
+ console.log(chalk.green('✓ Updated templates and documentation in preserved folders'));
1193
+ }
1194
+
1030
1195
  console.log(chalk.green('✓ Framework files copied to ./.proagents/'));
1031
1196
 
1032
1197
  // Create config if not skipped
@@ -1398,6 +1563,9 @@ async function smartUpdate(sourceDir, targetDir) {
1398
1563
  }
1399
1564
  }
1400
1565
 
1566
+ // Update framework files within preserved paths (templates, README, schemas)
1567
+ updateFrameworkInPreserved(sourceDir, targetDir);
1568
+
1401
1569
  console.log(chalk.gray('\nTip: Use "proagents ai add" to add more AI platforms'));
1402
1570
  }
1403
1571
 
@@ -9,7 +9,7 @@ const __dirname = dirname(__filename);
9
9
  /**
10
10
  * Get CLI version from package.json
11
11
  */
12
- function getCliVersion() {
12
+ export function getCliVersion() {
13
13
  try {
14
14
  const packagePath = join(__dirname, '..', '..', 'package.json');
15
15
  const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
@@ -41,7 +41,7 @@ function getProjectVersion(targetDir) {
41
41
  /**
42
42
  * Fetch latest version from npm
43
43
  */
44
- async function getLatestVersion() {
44
+ export async function getLatestVersion() {
45
45
  try {
46
46
  const controller = new AbortController();
47
47
  const timeout = setTimeout(() => controller.abort(), 5000);
@@ -66,7 +66,7 @@ async function getLatestVersion() {
66
66
  * Compare semantic versions
67
67
  * Returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2
68
68
  */
69
- function compareVersions(v1, v2) {
69
+ export function compareVersions(v1, v2) {
70
70
  const parts1 = v1.split('.').map(Number);
71
71
  const parts2 = v2.split('.').map(Number);
72
72
 
@@ -79,6 +79,27 @@ function compareVersions(v1, v2) {
79
79
  return 0;
80
80
  }
81
81
 
82
+ /**
83
+ * Check for updates and display notification if available
84
+ * Non-blocking - runs asynchronously
85
+ */
86
+ export async function checkForUpdates() {
87
+ try {
88
+ const cliVersion = getCliVersion();
89
+ const latestVersion = await getLatestVersion();
90
+
91
+ if (latestVersion && compareVersions(cliVersion, latestVersion) < 0) {
92
+ console.log('');
93
+ console.log(chalk.yellow(` ╭───────────────────────────────────────────────╮`));
94
+ console.log(chalk.yellow(` │ Update available: ${chalk.gray('v' + cliVersion)} → ${chalk.green('v' + latestVersion)} │`));
95
+ console.log(chalk.yellow(` │ Run: ${chalk.cyan('npx proagents init')} to update │`));
96
+ console.log(chalk.yellow(` ╰───────────────────────────────────────────────╯`));
97
+ }
98
+ } catch (error) {
99
+ // Silently ignore update check errors
100
+ }
101
+ }
102
+
82
103
  /**
83
104
  * Version command - show detailed version information
84
105
  */
@@ -118,7 +139,7 @@ export async function versionCommand(options = {}) {
118
139
  console.log(chalk.green('v' + latestVersion + ' (up to date)'));
119
140
  } else {
120
141
  console.log(chalk.yellow('v' + latestVersion + ' (update available)'));
121
- console.log(chalk.yellow(` ↳ Run 'npm update -g proagents' to update`));
142
+ console.log(chalk.yellow(` ↳ Run 'npx proagents init' to update`));
122
143
  }
123
144
  } else {
124
145
  console.log(chalk.gray('Could not fetch (offline or npm unavailable)'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proagents",
3
- "version": "1.6.13",
3
+ "version": "1.6.16",
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",