antikit 1.15.0 → 1.17.0
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/README.md +85 -0
- package/package.json +2 -1
- package/src/commands/backup.js +118 -0
- package/src/commands/restore.js +196 -0
- package/src/commands/stats.js +196 -0
- package/src/index.js +22 -0
package/README.md
CHANGED
|
@@ -204,6 +204,91 @@ antikit config set-token <new_token>
|
|
|
204
204
|
antikit config remove-token
|
|
205
205
|
```
|
|
206
206
|
|
|
207
|
+
### 📊 Statistics & Analytics
|
|
208
|
+
|
|
209
|
+
View insights about your installed skills.
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# Show comprehensive statistics
|
|
213
|
+
antikit stats
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**What you'll see:**
|
|
217
|
+
- 📈 **Overview** - Total skills, sources, metadata coverage
|
|
218
|
+
- 📦 **Source Distribution** - Skills grouped by source with percentages
|
|
219
|
+
- 🔢 **Version Stats** - Version tracking information
|
|
220
|
+
- 🌟 **Top Skills** - Recently installed or most used skills
|
|
221
|
+
|
|
222
|
+
**Example Output:**
|
|
223
|
+
```
|
|
224
|
+
📊 Antikit Statistics
|
|
225
|
+
|
|
226
|
+
Metric Value
|
|
227
|
+
────────────────────────────────────
|
|
228
|
+
Total Skills Installed 12
|
|
229
|
+
Total Sources Configured 3
|
|
230
|
+
Skills with Metadata 10
|
|
231
|
+
|
|
232
|
+
📦 Skills by Source:
|
|
233
|
+
|
|
234
|
+
Source Skills Percentage
|
|
235
|
+
───────────────────────────────────────────
|
|
236
|
+
antiskills 8 66.7% ████████████████
|
|
237
|
+
claudekit 3 25.0% █████
|
|
238
|
+
local 1 8.3% ██
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### 💾 Backup & Restore
|
|
245
|
+
|
|
246
|
+
Export and import your skills configuration for easy migration between machines or disaster recovery.
|
|
247
|
+
|
|
248
|
+
**Create Backup**
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
# Create backup with auto-generated filename
|
|
252
|
+
antikit backup
|
|
253
|
+
# Creates: antikit-backup-2026-01-15.json
|
|
254
|
+
|
|
255
|
+
# Specify custom output file
|
|
256
|
+
antikit backup my-setup.json
|
|
257
|
+
|
|
258
|
+
# Force backup even if no skills installed
|
|
259
|
+
antikit backup --force
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Restore from Backup**
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
# Restore with confirmation prompt
|
|
266
|
+
antikit restore antikit-backup-2026-01-15.json
|
|
267
|
+
|
|
268
|
+
# Auto-confirm (no prompts)
|
|
269
|
+
antikit restore my-setup.json --yes
|
|
270
|
+
|
|
271
|
+
# Force reinstall existing skills
|
|
272
|
+
antikit restore my-setup.json --force
|
|
273
|
+
|
|
274
|
+
# Skip restoring sources (only install skills)
|
|
275
|
+
antikit restore my-setup.json --skip-sources
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Backup File Contents:**
|
|
279
|
+
- 📦 List of installed skills with versions
|
|
280
|
+
- 🔗 Source configurations
|
|
281
|
+
- 📅 Backup timestamp and metadata
|
|
282
|
+
- 🔒 Config (tokens are masked for security)
|
|
283
|
+
|
|
284
|
+
**Use Cases:**
|
|
285
|
+
- 🖥️ **Team Onboarding** - Share skill setup with team members
|
|
286
|
+
- 🔄 **Machine Migration** - Move setup to new computer
|
|
287
|
+
- 💾 **Disaster Recovery** - Restore after accidental removal
|
|
288
|
+
- 📸 **Snapshots** - Backup before major changes
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
207
292
|
### 🔄 Tool Maintenance
|
|
208
293
|
|
|
209
294
|
**Update CLI**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "antikit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "CLI tool to manage AI agent skills from Anti Gravity skills repository",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"test:watch": "vitest",
|
|
13
13
|
"test:coverage": "vitest run --coverage",
|
|
14
14
|
"release": "commit-and-tag-version",
|
|
15
|
+
"release:push": "./scripts/release.sh",
|
|
15
16
|
"prepare": "husky",
|
|
16
17
|
"format": "prettier --write ."
|
|
17
18
|
},
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { confirm } from '@inquirer/prompts';
|
|
5
|
+
import { getLocalSkills, findLocalSkillsDir } from '../utils/local.js';
|
|
6
|
+
import { getSources, loadConfig } from '../utils/configManager.js';
|
|
7
|
+
import { METADATA_FILE } from '../utils/constants.js';
|
|
8
|
+
import { existsSync, readFileSync } from 'fs';
|
|
9
|
+
|
|
10
|
+
export async function backupSkills(outputPath, options = {}) {
|
|
11
|
+
const skillsDir = findLocalSkillsDir();
|
|
12
|
+
|
|
13
|
+
if (!skillsDir) {
|
|
14
|
+
console.log(chalk.yellow('No .agent/skills directory found in current path.'));
|
|
15
|
+
console.log(chalk.dim('Nothing to backup.'));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const skills = getLocalSkills();
|
|
20
|
+
|
|
21
|
+
if (skills.length === 0 && !options.force) {
|
|
22
|
+
console.log(chalk.yellow('No skills installed to backup.'));
|
|
23
|
+
const shouldContinue = await confirm({
|
|
24
|
+
message: 'Continue with empty backup?',
|
|
25
|
+
default: false
|
|
26
|
+
});
|
|
27
|
+
if (!shouldContinue) {
|
|
28
|
+
console.log(chalk.yellow('Backup cancelled.'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(chalk.bold.cyan('\n💾 Creating backup...\n'));
|
|
34
|
+
|
|
35
|
+
// Prepare backup data
|
|
36
|
+
const backupData = {
|
|
37
|
+
version: getPackageVersion(),
|
|
38
|
+
backupDate: new Date().toISOString(),
|
|
39
|
+
skillsDirectory: skillsDir,
|
|
40
|
+
totalSkills: skills.length,
|
|
41
|
+
skills: [],
|
|
42
|
+
sources: getSources(),
|
|
43
|
+
config: getConfigForBackup()
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Collect skill metadata
|
|
47
|
+
for (const skill of skills) {
|
|
48
|
+
const metaPath = join(skill.path, METADATA_FILE);
|
|
49
|
+
const skillData = {
|
|
50
|
+
name: skill.name,
|
|
51
|
+
description: skill.description || null
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (existsSync(metaPath)) {
|
|
55
|
+
try {
|
|
56
|
+
const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
|
|
57
|
+
skillData.version = meta.version || null;
|
|
58
|
+
skillData.source = meta.source || null;
|
|
59
|
+
skillData.sourceName = meta.sourceName || null;
|
|
60
|
+
skillData.installedDate = meta.installedAt || null;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.log(chalk.dim(`⚠️ Could not read metadata for ${skill.name}`));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
backupData.skills.push(skillData);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Determine output path
|
|
70
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0];
|
|
71
|
+
const defaultFilename = `antikit-backup-${timestamp}.json`;
|
|
72
|
+
const finalPath = outputPath || defaultFilename;
|
|
73
|
+
|
|
74
|
+
// Write backup file
|
|
75
|
+
try {
|
|
76
|
+
writeFileSync(finalPath, JSON.stringify(backupData, null, 2));
|
|
77
|
+
|
|
78
|
+
console.log(chalk.green('✓ Backup created successfully!\n'));
|
|
79
|
+
console.log(chalk.bold('Backup Summary:'));
|
|
80
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
81
|
+
console.log(`${chalk.cyan('File:')} ${finalPath}`);
|
|
82
|
+
console.log(`${chalk.cyan('Skills:')} ${backupData.totalSkills}`);
|
|
83
|
+
console.log(`${chalk.cyan('Sources:')} ${backupData.sources.length}`);
|
|
84
|
+
console.log(`${chalk.cyan('Date:')} ${new Date(backupData.backupDate).toLocaleString()}`);
|
|
85
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
86
|
+
console.log();
|
|
87
|
+
console.log(chalk.dim(`To restore: ${chalk.white(`antikit restore ${finalPath}`)}`));
|
|
88
|
+
console.log();
|
|
89
|
+
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(chalk.red(`✗ Failed to create backup: ${error.message}`));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getPackageVersion() {
|
|
97
|
+
try {
|
|
98
|
+
const pkgPath = join(process.cwd(), 'package.json');
|
|
99
|
+
if (existsSync(pkgPath)) {
|
|
100
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
101
|
+
return pkg.version || 'unknown';
|
|
102
|
+
}
|
|
103
|
+
} catch { }
|
|
104
|
+
return 'unknown';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getConfigForBackup() {
|
|
108
|
+
try {
|
|
109
|
+
const config = loadConfig();
|
|
110
|
+
// Mask sensitive data
|
|
111
|
+
return {
|
|
112
|
+
githubToken: config.githubToken ? '***MASKED***' : null,
|
|
113
|
+
sourcesCount: config.sources?.length || 0
|
|
114
|
+
};
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
|
+
import { confirm } from '@inquirer/prompts';
|
|
4
|
+
import Table from 'cli-table3';
|
|
5
|
+
import { installSkill } from './install.js';
|
|
6
|
+
import { addSource } from '../utils/configManager.js';
|
|
7
|
+
import { skillExists } from '../utils/local.js';
|
|
8
|
+
|
|
9
|
+
export async function restoreSkills(backupPath, options = {}) {
|
|
10
|
+
if (!backupPath) {
|
|
11
|
+
console.log(chalk.red('Please specify a backup file to restore.'));
|
|
12
|
+
console.log(chalk.dim('Usage: antikit restore <backup-file.json>'));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!existsSync(backupPath)) {
|
|
17
|
+
console.log(chalk.red(`Backup file not found: ${backupPath}`));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Load backup file
|
|
22
|
+
let backupData;
|
|
23
|
+
try {
|
|
24
|
+
const content = readFileSync(backupPath, 'utf-8');
|
|
25
|
+
backupData = JSON.parse(content);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.log(chalk.red(`Invalid backup file: ${error.message}`));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Validate backup structure
|
|
32
|
+
if (!backupData.skills || !Array.isArray(backupData.skills)) {
|
|
33
|
+
console.log(chalk.red('Invalid backup format: missing skills array'));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(chalk.bold.cyan('\n📦 Restore from Backup\n'));
|
|
38
|
+
console.log(chalk.dim('Backup Information:'));
|
|
39
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
40
|
+
console.log(`${chalk.cyan('Date:')} ${new Date(backupData.backupDate).toLocaleString()}`);
|
|
41
|
+
console.log(`${chalk.cyan('Skills:')} ${backupData.totalSkills || backupData.skills.length}`);
|
|
42
|
+
console.log(`${chalk.cyan('Sources:')} ${backupData.sources?.length || 0}`);
|
|
43
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
44
|
+
console.log();
|
|
45
|
+
|
|
46
|
+
// Show skills to be restored
|
|
47
|
+
displaySkillsPreview(backupData.skills);
|
|
48
|
+
|
|
49
|
+
// Confirm restoration
|
|
50
|
+
if (!options.yes) {
|
|
51
|
+
const shouldRestore = await confirm({
|
|
52
|
+
message: `Restore ${backupData.skills.length} skill(s)?`,
|
|
53
|
+
default: true
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!shouldRestore) {
|
|
57
|
+
console.log(chalk.yellow('Restore cancelled.'));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log();
|
|
63
|
+
|
|
64
|
+
// Restore sources first (if any)
|
|
65
|
+
if (backupData.sources && backupData.sources.length > 0 && !options.skipSources) {
|
|
66
|
+
await restoreSources(backupData.sources);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Restore skills
|
|
70
|
+
await restoreSkillsList(backupData.skills, options);
|
|
71
|
+
|
|
72
|
+
console.log();
|
|
73
|
+
console.log(chalk.green.bold('✓ Restore completed!'));
|
|
74
|
+
console.log();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function displaySkillsPreview(skills) {
|
|
78
|
+
if (skills.length === 0) {
|
|
79
|
+
console.log(chalk.yellow('No skills in backup.'));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log(chalk.bold('Skills to restore:\n'));
|
|
84
|
+
|
|
85
|
+
const table = new Table({
|
|
86
|
+
head: [chalk.cyan('Skill'), chalk.cyan('Version'), chalk.cyan('Source'), chalk.cyan('Status')],
|
|
87
|
+
colWidths: [25, 12, 25, 15],
|
|
88
|
+
wordWrap: true,
|
|
89
|
+
style: { head: [], border: [] }
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const previewCount = Math.min(10, skills.length);
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < previewCount; i++) {
|
|
95
|
+
const skill = skills[i];
|
|
96
|
+
const status = skillExists(skill.name) ? chalk.yellow('Update') : chalk.green('New');
|
|
97
|
+
|
|
98
|
+
table.push([
|
|
99
|
+
chalk.bold(skill.name),
|
|
100
|
+
skill.version || chalk.dim('N/A'),
|
|
101
|
+
skill.sourceName || chalk.dim('local'),
|
|
102
|
+
status
|
|
103
|
+
]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log(table.toString());
|
|
107
|
+
|
|
108
|
+
if (skills.length > previewCount) {
|
|
109
|
+
console.log(chalk.dim(`\n... and ${skills.length - previewCount} more skills`));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function restoreSources(sources) {
|
|
116
|
+
console.log(chalk.bold.cyan('📡 Restoring sources...\n'));
|
|
117
|
+
|
|
118
|
+
let addedCount = 0;
|
|
119
|
+
let skippedCount = 0;
|
|
120
|
+
|
|
121
|
+
for (const source of sources) {
|
|
122
|
+
// Skip default source (should already exist)
|
|
123
|
+
if (source.default) {
|
|
124
|
+
skippedCount++;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
addSource(
|
|
130
|
+
source.name,
|
|
131
|
+
source.owner,
|
|
132
|
+
source.repo,
|
|
133
|
+
source.branch || 'main',
|
|
134
|
+
source.path || null
|
|
135
|
+
);
|
|
136
|
+
console.log(chalk.green(`✓ Added source: ${source.name}`));
|
|
137
|
+
addedCount++;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
// Source might already exist
|
|
140
|
+
if (error.message.includes('already exists')) {
|
|
141
|
+
console.log(chalk.dim(`⊙ Source already exists: ${source.name}`));
|
|
142
|
+
skippedCount++;
|
|
143
|
+
} else {
|
|
144
|
+
console.log(chalk.yellow(`⚠ Failed to add source ${source.name}: ${error.message}`));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log(chalk.dim(`\nSources: ${addedCount} added, ${skippedCount} skipped\n`));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function restoreSkillsList(skills, options) {
|
|
153
|
+
console.log(chalk.bold.cyan('🔧 Installing skills...\n'));
|
|
154
|
+
|
|
155
|
+
let successCount = 0;
|
|
156
|
+
let skipCount = 0;
|
|
157
|
+
let failCount = 0;
|
|
158
|
+
|
|
159
|
+
for (const skill of skills) {
|
|
160
|
+
// Skip if already installed and not forcing
|
|
161
|
+
if (skillExists(skill.name) && !options.force) {
|
|
162
|
+
console.log(chalk.dim(`⊙ Skipping ${skill.name} (already installed)`));
|
|
163
|
+
skipCount++;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Prepare install options
|
|
168
|
+
const installOptions = {
|
|
169
|
+
force: options.force || false,
|
|
170
|
+
noExit: true
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Add source info if available
|
|
174
|
+
if (skill.source && skill.source.owner && skill.source.repo) {
|
|
175
|
+
installOptions.owner = skill.source.owner;
|
|
176
|
+
installOptions.repo = skill.source.repo;
|
|
177
|
+
installOptions.path = skill.source.path || null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
await installSkill(skill.name, installOptions);
|
|
182
|
+
successCount++;
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.log(chalk.red(`✗ Failed to install ${skill.name}: ${error.message}`));
|
|
185
|
+
failCount++;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Summary
|
|
190
|
+
console.log('\n' + chalk.dim('─'.repeat(50)));
|
|
191
|
+
console.log(chalk.bold('Restore Summary:'));
|
|
192
|
+
console.log(`${chalk.green('Installed:')} ${successCount}`);
|
|
193
|
+
if (skipCount > 0) console.log(`${chalk.dim('Skipped:')} ${skipCount}`);
|
|
194
|
+
if (failCount > 0) console.log(`${chalk.red('Failed:')} ${failCount}`);
|
|
195
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
196
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
import { existsSync, readFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { getLocalSkills, findLocalSkillsDir } from '../utils/local.js';
|
|
6
|
+
import { getSources } from '../utils/configManager.js';
|
|
7
|
+
import { METADATA_FILE } from '../utils/constants.js';
|
|
8
|
+
|
|
9
|
+
export async function showStats() {
|
|
10
|
+
const skillsDir = findLocalSkillsDir();
|
|
11
|
+
|
|
12
|
+
if (!skillsDir) {
|
|
13
|
+
console.log(chalk.yellow('No .agent/skills directory found in current path.'));
|
|
14
|
+
console.log(chalk.dim('Run this command from a project with .agent/skills folder.'));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const skills = getLocalSkills();
|
|
19
|
+
const sources = getSources();
|
|
20
|
+
|
|
21
|
+
console.log(chalk.bold.cyan('\n📊 Antikit Statistics\n'));
|
|
22
|
+
console.log(chalk.dim(`Skills directory: ${skillsDir}\n`));
|
|
23
|
+
|
|
24
|
+
// 1. Overview Stats
|
|
25
|
+
displayOverview(skills, sources);
|
|
26
|
+
|
|
27
|
+
if (skills.length > 0) {
|
|
28
|
+
// 2. Source Distribution
|
|
29
|
+
displaySourceDistribution(skills);
|
|
30
|
+
|
|
31
|
+
// 3. Version Stats
|
|
32
|
+
displayVersionStats(skills);
|
|
33
|
+
|
|
34
|
+
// 4. Top Skills (by size or complexity)
|
|
35
|
+
displayTopSkills(skills);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function displayOverview(skills, sources) {
|
|
42
|
+
const overview = new Table({
|
|
43
|
+
head: [chalk.cyan('Metric'), chalk.cyan('Value')],
|
|
44
|
+
colWidths: [30, 20],
|
|
45
|
+
style: { head: [], border: [] }
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
overview.push(
|
|
49
|
+
[chalk.bold('Total Skills Installed'), chalk.green.bold(skills.length.toString())],
|
|
50
|
+
[chalk.bold('Total Sources Configured'), chalk.blue(sources.length.toString())],
|
|
51
|
+
[
|
|
52
|
+
chalk.bold('Skills with Metadata'),
|
|
53
|
+
chalk.yellow(
|
|
54
|
+
skills.filter(s => {
|
|
55
|
+
const metaPath = join(s.path, METADATA_FILE);
|
|
56
|
+
return existsSync(metaPath);
|
|
57
|
+
}).length.toString()
|
|
58
|
+
)
|
|
59
|
+
]
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
console.log(overview.toString());
|
|
63
|
+
console.log();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function displaySourceDistribution(skills) {
|
|
67
|
+
const sourceMap = new Map();
|
|
68
|
+
|
|
69
|
+
skills.forEach(skill => {
|
|
70
|
+
const metaPath = join(skill.path, METADATA_FILE);
|
|
71
|
+
let sourceName = 'local';
|
|
72
|
+
|
|
73
|
+
if (existsSync(metaPath)) {
|
|
74
|
+
try {
|
|
75
|
+
const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
|
|
76
|
+
if (meta.source && meta.source.owner && meta.source.repo) {
|
|
77
|
+
// Use source name if available, otherwise owner/repo
|
|
78
|
+
sourceName = meta.sourceName || `${meta.source.owner}/${meta.source.repo}`;
|
|
79
|
+
}
|
|
80
|
+
} catch { }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
sourceMap.set(sourceName, (sourceMap.get(sourceName) || 0) + 1);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Sort by count descending
|
|
87
|
+
const sortedSources = Array.from(sourceMap.entries()).sort((a, b) => b[1] - a[1]);
|
|
88
|
+
|
|
89
|
+
console.log(chalk.bold.cyan('📦 Skills by Source:\n'));
|
|
90
|
+
|
|
91
|
+
const sourceTable = new Table({
|
|
92
|
+
head: [chalk.cyan('Source'), chalk.cyan('Skills'), chalk.cyan('Percentage')],
|
|
93
|
+
colWidths: [35, 12, 15],
|
|
94
|
+
style: { head: [], border: [] }
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const total = skills.length;
|
|
98
|
+
sortedSources.forEach(([source, count]) => {
|
|
99
|
+
const percentage = ((count / total) * 100).toFixed(1);
|
|
100
|
+
const bar = '█'.repeat(Math.ceil((count / total) * 20));
|
|
101
|
+
|
|
102
|
+
sourceTable.push([
|
|
103
|
+
chalk.magenta(source),
|
|
104
|
+
chalk.green(count.toString()),
|
|
105
|
+
chalk.yellow(`${percentage}%`) + ' ' + chalk.dim(bar)
|
|
106
|
+
]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
console.log(sourceTable.toString());
|
|
110
|
+
console.log();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function displayVersionStats(skills) {
|
|
114
|
+
let withVersion = 0;
|
|
115
|
+
let withoutVersion = 0;
|
|
116
|
+
const versions = new Map();
|
|
117
|
+
|
|
118
|
+
skills.forEach(skill => {
|
|
119
|
+
const metaPath = join(skill.path, METADATA_FILE);
|
|
120
|
+
|
|
121
|
+
if (existsSync(metaPath)) {
|
|
122
|
+
try {
|
|
123
|
+
const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
|
|
124
|
+
if (meta.version) {
|
|
125
|
+
withVersion++;
|
|
126
|
+
// Track version patterns (major.minor.patch)
|
|
127
|
+
const major = meta.version.split('.')[0];
|
|
128
|
+
versions.set(major, (versions.get(major) || 0) + 1);
|
|
129
|
+
} else {
|
|
130
|
+
withoutVersion++;
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
withoutVersion++;
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
withoutVersion++;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
console.log(chalk.bold.cyan('🔢 Version Information:\n'));
|
|
141
|
+
|
|
142
|
+
const versionTable = new Table({
|
|
143
|
+
head: [chalk.cyan('Status'), chalk.cyan('Count')],
|
|
144
|
+
colWidths: [35, 15],
|
|
145
|
+
style: { head: [], border: [] }
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
versionTable.push(
|
|
149
|
+
['Skills with version info', chalk.green(withVersion.toString())],
|
|
150
|
+
['Skills without version info', chalk.dim(withoutVersion.toString())]
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
console.log(versionTable.toString());
|
|
154
|
+
console.log();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function displayTopSkills(skills) {
|
|
158
|
+
// Show skills sorted by name with their basic info
|
|
159
|
+
const topCount = Math.min(5, skills.length);
|
|
160
|
+
|
|
161
|
+
console.log(chalk.bold.cyan(`🌟 Installed Skills (showing ${topCount} of ${skills.length}):\n`));
|
|
162
|
+
|
|
163
|
+
const topTable = new Table({
|
|
164
|
+
head: [chalk.cyan('Skill Name'), chalk.cyan('Version'), chalk.cyan('Source')],
|
|
165
|
+
colWidths: [30, 15, 25],
|
|
166
|
+
wordWrap: true,
|
|
167
|
+
style: { head: [], border: [] }
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Sort alphabetically and take top N
|
|
171
|
+
const sortedSkills = [...skills].sort((a, b) => a.name.localeCompare(b.name)).slice(0, topCount);
|
|
172
|
+
|
|
173
|
+
sortedSkills.forEach(skill => {
|
|
174
|
+
const metaPath = join(skill.path, METADATA_FILE);
|
|
175
|
+
let version = chalk.dim('local');
|
|
176
|
+
let source = chalk.dim('local');
|
|
177
|
+
|
|
178
|
+
if (existsSync(metaPath)) {
|
|
179
|
+
try {
|
|
180
|
+
const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
|
|
181
|
+
if (meta.version) version = chalk.yellow(meta.version);
|
|
182
|
+
if (meta.source && meta.source.owner && meta.source.repo) {
|
|
183
|
+
source = chalk.magenta(meta.sourceName || `${meta.source.owner}/${meta.source.repo}`);
|
|
184
|
+
}
|
|
185
|
+
} catch { }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
topTable.push([chalk.bold.cyan(skill.name), version, source]);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
console.log(topTable.toString());
|
|
192
|
+
|
|
193
|
+
if (skills.length > topCount) {
|
|
194
|
+
console.log(chalk.dim(`\n... and ${skills.length - topCount} more. Use 'antikit local' to see all.`));
|
|
195
|
+
}
|
|
196
|
+
}
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,9 @@ import { showSkillInfo } from './commands/info.js';
|
|
|
11
11
|
import { validateSkill } from './commands/validate.js';
|
|
12
12
|
import { updateCli } from './commands/update.js';
|
|
13
13
|
import { upgradeSkills } from './commands/upgrade.js';
|
|
14
|
+
import { showStats } from './commands/stats.js';
|
|
15
|
+
import { backupSkills } from './commands/backup.js';
|
|
16
|
+
import { restoreSkills } from './commands/restore.js';
|
|
14
17
|
import { listSources, addNewSource, removeExistingSource, setDefault } from './commands/source.js';
|
|
15
18
|
import { listConfig, setGitHubToken, removeGitHubToken } from './commands/config.js';
|
|
16
19
|
import { checkForUpdates } from './utils/updateNotifier.js';
|
|
@@ -89,6 +92,25 @@ program
|
|
|
89
92
|
.option('-y, --yes', 'Skip confirmation')
|
|
90
93
|
.action(upgradeSkills);
|
|
91
94
|
|
|
95
|
+
program
|
|
96
|
+
.command('stats')
|
|
97
|
+
.description('Show statistics about installed skills')
|
|
98
|
+
.action(showStats);
|
|
99
|
+
|
|
100
|
+
program
|
|
101
|
+
.command('backup [output]')
|
|
102
|
+
.description('Backup installed skills configuration')
|
|
103
|
+
.option('-f, --force', 'Create backup even if no skills installed')
|
|
104
|
+
.action(backupSkills);
|
|
105
|
+
|
|
106
|
+
program
|
|
107
|
+
.command('restore <backup-file>')
|
|
108
|
+
.description('Restore skills from backup file')
|
|
109
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
110
|
+
.option('-f, --force', 'Force reinstall existing skills')
|
|
111
|
+
.option('--skip-sources', 'Skip restoring sources')
|
|
112
|
+
.action(restoreSkills);
|
|
113
|
+
|
|
92
114
|
program
|
|
93
115
|
.command('completion')
|
|
94
116
|
.description('Setup autocomplete for your shell (zsh/bash)')
|