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 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.15.0",
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)')