proagents 1.6.11 → 1.6.13

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.
@@ -0,0 +1,150 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { createInterface } from 'readline';
4
+ import chalk from 'chalk';
5
+
6
+ /**
7
+ * Restore ProAgents data from backup file
8
+ */
9
+ export async function restoreCommand(backupFile, options = {}) {
10
+ const targetDir = process.cwd();
11
+ const proagentsDir = join(targetDir, '.proagents');
12
+
13
+ console.log(chalk.bold('\nProAgents Restore'));
14
+ console.log(chalk.gray('=================\n'));
15
+
16
+ // Check if backup file exists
17
+ if (!backupFile) {
18
+ console.log(chalk.red('Error: Please provide a backup file path.'));
19
+ console.log(chalk.gray('\nUsage: proagents restore <backup-file.json>'));
20
+ console.log(chalk.gray('Example: proagents restore proagents-backup-2024-01-15.json\n'));
21
+ return;
22
+ }
23
+
24
+ const backupPath = backupFile.startsWith('/') ? backupFile : join(targetDir, backupFile);
25
+
26
+ if (!existsSync(backupPath)) {
27
+ console.log(chalk.red(`Error: Backup file not found: ${backupPath}`));
28
+ return;
29
+ }
30
+
31
+ // Read backup file
32
+ let backup;
33
+ try {
34
+ const content = readFileSync(backupPath, 'utf-8');
35
+ backup = JSON.parse(content);
36
+ } catch (error) {
37
+ console.log(chalk.red(`Error: Could not parse backup file: ${error.message}`));
38
+ return;
39
+ }
40
+
41
+ // Validate backup structure
42
+ if (!backup.exportedAt || !backup.version) {
43
+ console.log(chalk.red('Error: Invalid backup file format.'));
44
+ return;
45
+ }
46
+
47
+ console.log(chalk.cyan('Backup Information:'));
48
+ console.log(` Exported: ${chalk.white(backup.exportedAt)}`);
49
+ console.log(` Version: ${chalk.white(backup.version)}`);
50
+
51
+ const fileCount = Object.keys(backup.files || {}).length;
52
+ const folderCount = Object.keys(backup.folders || {}).length;
53
+ console.log(` Files: ${chalk.white(fileCount)}`);
54
+ console.log(` Folder items: ${chalk.white(folderCount)}`);
55
+ console.log('');
56
+
57
+ // Confirm restore unless --force
58
+ if (!options.force) {
59
+ const rl = createInterface({
60
+ input: process.stdin,
61
+ output: process.stdout
62
+ });
63
+
64
+ const answer = await new Promise(resolve => {
65
+ rl.question(chalk.yellow('Restore this backup? This will overwrite existing files. (y/N) '), resolve);
66
+ });
67
+ rl.close();
68
+
69
+ if (answer.toLowerCase() !== 'y') {
70
+ console.log(chalk.gray('\nRestore cancelled.\n'));
71
+ return;
72
+ }
73
+ }
74
+
75
+ console.log('');
76
+
77
+ // Ensure .proagents directory exists
78
+ if (!existsSync(proagentsDir)) {
79
+ mkdirSync(proagentsDir, { recursive: true });
80
+ console.log(chalk.cyan('Created .proagents/ directory'));
81
+ }
82
+
83
+ let restoredCount = 0;
84
+ let errorCount = 0;
85
+
86
+ // Restore individual files
87
+ if (backup.files) {
88
+ for (const [file, content] of Object.entries(backup.files)) {
89
+ const filePath = join(proagentsDir, file);
90
+ try {
91
+ // Ensure directory exists
92
+ const fileDir = dirname(filePath);
93
+ if (!existsSync(fileDir)) {
94
+ mkdirSync(fileDir, { recursive: true });
95
+ }
96
+ writeFileSync(filePath, content);
97
+ restoredCount++;
98
+ if (!options.quiet) {
99
+ console.log(chalk.green(` ✓ ${file}`));
100
+ }
101
+ } catch (error) {
102
+ errorCount++;
103
+ console.log(chalk.red(` ✗ ${file}: ${error.message}`));
104
+ }
105
+ }
106
+ }
107
+
108
+ // Restore folder items
109
+ if (backup.folders) {
110
+ for (const [file, content] of Object.entries(backup.folders)) {
111
+ const filePath = join(proagentsDir, file);
112
+ try {
113
+ // Ensure directory exists
114
+ const fileDir = dirname(filePath);
115
+ if (!existsSync(fileDir)) {
116
+ mkdirSync(fileDir, { recursive: true });
117
+ }
118
+ writeFileSync(filePath, content);
119
+ restoredCount++;
120
+ if (!options.quiet) {
121
+ console.log(chalk.green(` ✓ ${file}`));
122
+ }
123
+ } catch (error) {
124
+ errorCount++;
125
+ console.log(chalk.red(` ✗ ${file}: ${error.message}`));
126
+ }
127
+ }
128
+ }
129
+
130
+ // Summary
131
+ console.log(chalk.bold('\nRestore Complete'));
132
+ console.log(chalk.gray('─'.repeat(40)));
133
+ console.log(chalk.green(` ✓ Restored: ${restoredCount} files`));
134
+ if (errorCount > 0) {
135
+ console.log(chalk.red(` ✗ Errors: ${errorCount} files`));
136
+ }
137
+ console.log('');
138
+
139
+ // JSON output
140
+ if (options.json) {
141
+ console.log(JSON.stringify({
142
+ success: true,
143
+ restored: restoredCount,
144
+ errors: errorCount,
145
+ backupDate: backup.exportedAt
146
+ }, null, 2));
147
+ }
148
+
149
+ console.log(chalk.gray('Run `npx proagents doctor` to verify installation.\n'));
150
+ }
@@ -0,0 +1,320 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { execSync } from 'child_process';
4
+ import chalk from 'chalk';
5
+
6
+ /**
7
+ * Count files in directory recursively
8
+ */
9
+ function countFiles(dir, extension = null) {
10
+ let count = 0;
11
+ if (!existsSync(dir)) return 0;
12
+
13
+ try {
14
+ const entries = readdirSync(dir, { withFileTypes: true });
15
+ for (const entry of entries) {
16
+ const fullPath = join(dir, entry.name);
17
+ if (entry.isDirectory()) {
18
+ count += countFiles(fullPath, extension);
19
+ } else if (!extension || entry.name.endsWith(extension)) {
20
+ count++;
21
+ }
22
+ }
23
+ } catch {
24
+ // Directory not accessible
25
+ }
26
+ return count;
27
+ }
28
+
29
+ /**
30
+ * Get AI stats from worklog
31
+ */
32
+ function getAIStats(proagentsDir) {
33
+ const statsPath = join(proagentsDir, 'worklog', 'ai-stats.json');
34
+ if (existsSync(statsPath)) {
35
+ try {
36
+ return JSON.parse(readFileSync(statsPath, 'utf-8'));
37
+ } catch {
38
+ // Stats file unreadable
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+
44
+ /**
45
+ * Count features from active-features
46
+ */
47
+ function getFeatureStats(proagentsDir) {
48
+ const featuresDir = join(proagentsDir, 'active-features');
49
+ const stats = { active: 0, completed: 0, total: 0 };
50
+
51
+ if (!existsSync(featuresDir)) return stats;
52
+
53
+ try {
54
+ const indexPath = join(featuresDir, '_index.json');
55
+ if (existsSync(indexPath)) {
56
+ const index = JSON.parse(readFileSync(indexPath, 'utf-8'));
57
+ stats.active = (index.active_features || []).length;
58
+ stats.completed = (index.completed_features || []).length;
59
+ stats.total = stats.active + stats.completed;
60
+ }
61
+ } catch {
62
+ // Index not readable
63
+ }
64
+
65
+ return stats;
66
+ }
67
+
68
+ /**
69
+ * Get changelog stats
70
+ */
71
+ function getChangelogStats(proagentsDir) {
72
+ const changelogDir = join(proagentsDir, 'changelog');
73
+ const stats = { total: 0, features: 0, modules: 0 };
74
+
75
+ if (!existsSync(changelogDir)) return stats;
76
+
77
+ // Count feature changelogs
78
+ const featuresDir = join(changelogDir, 'features');
79
+ if (existsSync(featuresDir)) {
80
+ stats.features = countFiles(featuresDir, '.md');
81
+ }
82
+
83
+ // Count module changelogs
84
+ const modulesDir = join(changelogDir, 'modules');
85
+ if (existsSync(modulesDir)) {
86
+ stats.modules = countFiles(modulesDir, '.md');
87
+ }
88
+
89
+ // Count year folders
90
+ try {
91
+ const entries = readdirSync(changelogDir, { withFileTypes: true });
92
+ for (const entry of entries) {
93
+ if (entry.isDirectory() && /^20[2-9][0-9]$/.test(entry.name)) {
94
+ stats.total += countFiles(join(changelogDir, entry.name), '.md');
95
+ }
96
+ }
97
+ } catch {
98
+ // Directory not readable
99
+ }
100
+
101
+ return stats;
102
+ }
103
+
104
+ /**
105
+ * Get git stats
106
+ */
107
+ function getGitStats() {
108
+ const stats = { commits: 0, branches: 0, tags: 0, contributors: 0 };
109
+
110
+ try {
111
+ // Total commits
112
+ const commits = execSync('git rev-list --count HEAD 2>/dev/null', { encoding: 'utf-8' });
113
+ stats.commits = parseInt(commits.trim()) || 0;
114
+ } catch {
115
+ // Git not available or not a repo
116
+ }
117
+
118
+ try {
119
+ // Branches
120
+ const branches = execSync('git branch -a 2>/dev/null', { encoding: 'utf-8' });
121
+ stats.branches = branches.split('\n').filter(b => b.trim()).length;
122
+ } catch {
123
+ // Git not available
124
+ }
125
+
126
+ try {
127
+ // Tags
128
+ const tags = execSync('git tag 2>/dev/null', { encoding: 'utf-8' });
129
+ stats.tags = tags.split('\n').filter(t => t.trim()).length;
130
+ } catch {
131
+ // Git not available
132
+ }
133
+
134
+ try {
135
+ // Contributors
136
+ const contributors = execSync('git shortlog -sn --all 2>/dev/null', { encoding: 'utf-8' });
137
+ stats.contributors = contributors.split('\n').filter(c => c.trim()).length;
138
+ } catch {
139
+ // Git not available
140
+ }
141
+
142
+ return stats;
143
+ }
144
+
145
+ /**
146
+ * Get activity log stats
147
+ */
148
+ function getActivityStats(proagentsDir) {
149
+ const activityPath = join(proagentsDir, 'activity.log');
150
+ const stats = { total: 0, byAI: {}, byAction: {} };
151
+
152
+ if (!existsSync(activityPath)) return stats;
153
+
154
+ try {
155
+ const content = readFileSync(activityPath, 'utf-8');
156
+ const lines = content.split('\n').filter(l => l.trim());
157
+ stats.total = lines.length;
158
+
159
+ // Parse activity entries
160
+ for (const line of lines) {
161
+ // Extract AI platform (look for common patterns)
162
+ const aiMatch = line.match(/\[(claude|chatgpt|gemini|cursor|copilot|windsurf|bolt|kiro)\]/i);
163
+ if (aiMatch) {
164
+ const ai = aiMatch[1].toLowerCase();
165
+ stats.byAI[ai] = (stats.byAI[ai] || 0) + 1;
166
+ }
167
+
168
+ // Extract action type
169
+ const actionMatch = line.match(/\b(feature|fix|test|doc|deploy|review|refactor)\b/i);
170
+ if (actionMatch) {
171
+ const action = actionMatch[1].toLowerCase();
172
+ stats.byAction[action] = (stats.byAction[action] || 0) + 1;
173
+ }
174
+ }
175
+ } catch {
176
+ // Activity log not readable
177
+ }
178
+
179
+ return stats;
180
+ }
181
+
182
+ /**
183
+ * Get worklog session stats
184
+ */
185
+ function getSessionStats(proagentsDir) {
186
+ const worklogDir = join(proagentsDir, 'worklog');
187
+ const stats = { sessions: 0, lastSession: null };
188
+
189
+ if (!existsSync(worklogDir)) return stats;
190
+
191
+ try {
192
+ const entries = readdirSync(worklogDir);
193
+ const sessionFiles = entries.filter(e => e.match(/^\d{4}-\d{2}-\d{2}.*\.md$/));
194
+ stats.sessions = sessionFiles.length;
195
+
196
+ if (sessionFiles.length > 0) {
197
+ // Get most recent
198
+ sessionFiles.sort().reverse();
199
+ stats.lastSession = sessionFiles[0].replace('.md', '');
200
+ }
201
+ } catch {
202
+ // Worklog not readable
203
+ }
204
+
205
+ return stats;
206
+ }
207
+
208
+ /**
209
+ * Stats command - show project and AI usage statistics
210
+ */
211
+ export async function statsCommand(options = {}) {
212
+ const targetDir = process.cwd();
213
+ const proagentsDir = join(targetDir, '.proagents');
214
+
215
+ // Check if ProAgents is installed
216
+ if (!existsSync(proagentsDir)) {
217
+ console.log(chalk.yellow('\nProAgents is not installed in this project.'));
218
+ console.log(chalk.gray('Run `npx proagents init` to initialize.\n'));
219
+ return;
220
+ }
221
+
222
+ console.log(chalk.bold('\nProAgents Statistics'));
223
+ console.log(chalk.gray('====================\n'));
224
+
225
+ // Gather all stats
226
+ const aiStats = getAIStats(proagentsDir);
227
+ const featureStats = getFeatureStats(proagentsDir);
228
+ const changelogStats = getChangelogStats(proagentsDir);
229
+ const gitStats = getGitStats();
230
+ const activityStats = getActivityStats(proagentsDir);
231
+ const sessionStats = getSessionStats(proagentsDir);
232
+
233
+ // JSON output
234
+ if (options.json) {
235
+ const result = {
236
+ features: featureStats,
237
+ changelog: changelogStats,
238
+ git: gitStats,
239
+ activity: activityStats,
240
+ sessions: sessionStats,
241
+ aiStats: aiStats
242
+ };
243
+ console.log(JSON.stringify(result, null, 2));
244
+ return;
245
+ }
246
+
247
+ // Feature Stats
248
+ console.log(chalk.bold.cyan('Features'));
249
+ console.log(chalk.gray('─'.repeat(40)));
250
+ console.log(` Active: ${chalk.yellow(featureStats.active)}`);
251
+ console.log(` Completed: ${chalk.green(featureStats.completed)}`);
252
+ console.log(` Total: ${chalk.white(featureStats.total)}`);
253
+ console.log('');
254
+
255
+ // Changelog Stats
256
+ console.log(chalk.bold.cyan('Changelog'));
257
+ console.log(chalk.gray('─'.repeat(40)));
258
+ console.log(` Total entries: ${chalk.white(changelogStats.total)}`);
259
+ console.log(` Feature logs: ${chalk.white(changelogStats.features)}`);
260
+ console.log(` Module logs: ${chalk.white(changelogStats.modules)}`);
261
+ console.log('');
262
+
263
+ // Git Stats
264
+ console.log(chalk.bold.cyan('Git'));
265
+ console.log(chalk.gray('─'.repeat(40)));
266
+ console.log(` Commits: ${chalk.white(gitStats.commits)}`);
267
+ console.log(` Branches: ${chalk.white(gitStats.branches)}`);
268
+ console.log(` Tags: ${chalk.white(gitStats.tags)}`);
269
+ console.log(` Contributors: ${chalk.white(gitStats.contributors)}`);
270
+ console.log('');
271
+
272
+ // AI Activity Stats
273
+ console.log(chalk.bold.cyan('AI Activity'));
274
+ console.log(chalk.gray('─'.repeat(40)));
275
+ console.log(` Total actions: ${chalk.white(activityStats.total)}`);
276
+ console.log(` Sessions: ${chalk.white(sessionStats.sessions)}`);
277
+ if (sessionStats.lastSession) {
278
+ console.log(` Last session: ${chalk.gray(sessionStats.lastSession)}`);
279
+ }
280
+ console.log('');
281
+
282
+ // AI Platform breakdown
283
+ if (Object.keys(activityStats.byAI).length > 0) {
284
+ console.log(chalk.bold.cyan('AI Platforms'));
285
+ console.log(chalk.gray('─'.repeat(40)));
286
+ const sortedAI = Object.entries(activityStats.byAI).sort((a, b) => b[1] - a[1]);
287
+ for (const [ai, count] of sortedAI) {
288
+ const bar = '█'.repeat(Math.min(Math.ceil(count / 5), 20));
289
+ console.log(` ${ai.padEnd(12)} ${chalk.blue(bar)} ${count}`);
290
+ }
291
+ console.log('');
292
+ }
293
+
294
+ // Action breakdown
295
+ if (Object.keys(activityStats.byAction).length > 0) {
296
+ console.log(chalk.bold.cyan('Actions'));
297
+ console.log(chalk.gray('─'.repeat(40)));
298
+ const sortedActions = Object.entries(activityStats.byAction).sort((a, b) => b[1] - a[1]);
299
+ for (const [action, count] of sortedActions) {
300
+ const bar = '█'.repeat(Math.min(Math.ceil(count / 3), 20));
301
+ console.log(` ${action.padEnd(12)} ${chalk.green(bar)} ${count}`);
302
+ }
303
+ console.log('');
304
+ }
305
+
306
+ // AI Stats from worklog (if available)
307
+ if (aiStats && Object.keys(aiStats).length > 0) {
308
+ console.log(chalk.bold.cyan('AI Performance'));
309
+ console.log(chalk.gray('─'.repeat(40)));
310
+ for (const [ai, data] of Object.entries(aiStats)) {
311
+ if (typeof data === 'object' && data !== null) {
312
+ console.log(` ${chalk.white(ai)}:`);
313
+ if (data.sessions) console.log(` Sessions: ${data.sessions}`);
314
+ if (data.tasks) console.log(` Tasks: ${data.tasks}`);
315
+ if (data.reverts) console.log(` Reverts: ${data.reverts}`);
316
+ }
317
+ }
318
+ console.log('');
319
+ }
320
+ }
@@ -1,5 +1,5 @@
1
- import { existsSync, rmSync, readFileSync, writeFileSync } from 'fs';
2
- import { join } from 'path';
1
+ import { existsSync, rmSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
3
  import { createInterface } from 'readline';
4
4
  import chalk from 'chalk';
5
5
 
@@ -23,6 +23,84 @@ const AI_FILES = [
23
23
  const PROAGENTS_START = '<!-- PROAGENTS:START -->';
24
24
  const PROAGENTS_END = '<!-- PROAGENTS:END -->';
25
25
 
26
+ // User data folders to export
27
+ const USER_DATA_FOLDERS = ['changelog', 'worklog', 'active-features'];
28
+ const USER_DATA_FILES = ['proagents.config.yaml', 'activity.log', 'handoff.md'];
29
+
30
+ /**
31
+ * Recursively read all files in a directory
32
+ */
33
+ function readDirRecursive(dir, baseDir = dir) {
34
+ const files = {};
35
+
36
+ if (!existsSync(dir)) return files;
37
+
38
+ try {
39
+ const entries = readdirSync(dir, { withFileTypes: true });
40
+ for (const entry of entries) {
41
+ const fullPath = join(dir, entry.name);
42
+ const relativePath = fullPath.replace(baseDir + '/', '');
43
+
44
+ if (entry.isDirectory()) {
45
+ Object.assign(files, readDirRecursive(fullPath, baseDir));
46
+ } else {
47
+ try {
48
+ files[relativePath] = readFileSync(fullPath, 'utf-8');
49
+ } catch {
50
+ // Skip unreadable files (binary, permissions, etc.)
51
+ }
52
+ }
53
+ }
54
+ } catch {
55
+ // Directory read failed - return what we have
56
+ }
57
+
58
+ return files;
59
+ }
60
+
61
+ /**
62
+ * Export user data to a backup file
63
+ */
64
+ function exportUserData(proagentsDir, targetDir) {
65
+ const backup = {
66
+ exportedAt: new Date().toISOString(),
67
+ version: '1.0',
68
+ files: {},
69
+ folders: {}
70
+ };
71
+
72
+ // Export individual files
73
+ for (const file of USER_DATA_FILES) {
74
+ const filePath = join(proagentsDir, file);
75
+ if (existsSync(filePath)) {
76
+ try {
77
+ backup.files[file] = readFileSync(filePath, 'utf-8');
78
+ } catch {
79
+ // Skip unreadable files
80
+ }
81
+ }
82
+ }
83
+
84
+ // Export folders recursively
85
+ for (const folder of USER_DATA_FOLDERS) {
86
+ const folderPath = join(proagentsDir, folder);
87
+ if (existsSync(folderPath)) {
88
+ const folderFiles = readDirRecursive(folderPath, proagentsDir);
89
+ Object.assign(backup.folders, folderFiles);
90
+ }
91
+ }
92
+
93
+ // Generate backup filename with timestamp
94
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
95
+ const backupFileName = `proagents-backup-${timestamp}.json`;
96
+ const backupPath = join(targetDir, backupFileName);
97
+
98
+ // Write backup file
99
+ writeFileSync(backupPath, JSON.stringify(backup, null, 2));
100
+
101
+ return { backupPath, backupFileName, fileCount: Object.keys(backup.files).length + Object.keys(backup.folders).length };
102
+ }
103
+
26
104
  /**
27
105
  * Remove only ProAgents section from a file, keep user's original content
28
106
  * Returns: 'deleted' (file removed), 'cleaned' (section removed), 'skipped' (no ProAgents section)
@@ -89,11 +167,27 @@ export async function uninstallCommand(options = {}) {
89
167
  const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
90
168
 
91
169
  console.log(chalk.yellow('This will remove:'));
92
- console.log(chalk.gray(' • ./.proagents/ folder'));
170
+ console.log(chalk.gray(' • ./.proagents/ folder (including changelog, worklog, config)'));
93
171
  console.log(chalk.gray(' • ProAgents sections from AI files (keeps your original config)'));
94
172
  console.log(chalk.gray(' • ProAgents section from README.md\n'));
95
173
 
96
- const answer = await question(chalk.yellow('Are you sure? (yes/no): '));
174
+ // Offer to export user data first
175
+ console.log(chalk.cyan('💾 Would you like to export your data before uninstalling?'));
176
+ console.log(chalk.gray(' This saves changelog, worklog, config to a backup file.\n'));
177
+
178
+ const exportAnswer = await question(chalk.yellow('Export data first? (y/N): '));
179
+
180
+ if (exportAnswer.toLowerCase() === 'y' || exportAnswer.toLowerCase() === 'yes') {
181
+ try {
182
+ const result = exportUserData(proagentsDir, targetDir);
183
+ console.log(chalk.green(`\n✓ Exported ${result.fileCount} files to ${result.backupFileName}`));
184
+ console.log(chalk.gray(` Location: ${result.backupPath}\n`));
185
+ } catch (error) {
186
+ console.log(chalk.yellow(`\n⚠️ Could not export data: ${error.message}\n`));
187
+ }
188
+ }
189
+
190
+ const answer = await question(chalk.yellow('Proceed with uninstall? (yes/no): '));
97
191
  rl.close();
98
192
 
99
193
  if (answer.toLowerCase() !== 'yes' && answer.toLowerCase() !== 'y') {