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.
- package/.proagents/.cursorrules +16 -2
- package/.proagents/.windsurfrules +16 -2
- package/.proagents/AI_INSTRUCTIONS.md +1248 -53
- package/.proagents/ANTIGRAVITY.md +16 -2
- package/.proagents/BOLT.md +16 -2
- package/.proagents/CHATGPT.md +16 -2
- package/.proagents/CLAUDE.md +16 -2
- package/.proagents/GEMINI.md +16 -2
- package/.proagents/GROQ.md +16 -2
- package/.proagents/KIRO.md +16 -2
- package/.proagents/LOVABLE.md +16 -2
- package/.proagents/PROAGENTS.md +56 -21
- package/.proagents/REPLIT.md +16 -2
- package/.proagents/docs/command-details.md +985 -82
- package/.proagents/prompts/11-session-tracking.md +11 -5
- package/.proagents/worklog/_context.md +31 -1
- package/.proagents/worklog/ai-stats.json +19 -0
- package/README.md +92 -9
- package/bin/proagents.js +132 -1
- package/lib/commands/changelog.js +389 -0
- package/lib/commands/completion.js +413 -0
- package/lib/commands/config.js +248 -0
- package/lib/commands/doctor.js +222 -25
- package/lib/commands/help.js +22 -2
- package/lib/commands/init.js +2 -1
- package/lib/commands/open.js +188 -0
- package/lib/commands/release.js +1007 -0
- package/lib/commands/restore.js +150 -0
- package/lib/commands/stats.js +320 -0
- package/lib/commands/uninstall.js +98 -4
- package/lib/commands/upgrade.js +102 -10
- package/lib/commands/version.js +140 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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') {
|