antikit 1.16.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
@@ -238,6 +238,55 @@ claudekit 3 25.0% █████
238
238
  local 1 8.3% ██
239
239
  ```
240
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
+
241
290
  ---
242
291
 
243
292
  ### 🔄 Tool Maintenance
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antikit",
3
- "version": "1.16.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",
@@ -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
+ }
package/src/index.js CHANGED
@@ -12,6 +12,8 @@ import { validateSkill } from './commands/validate.js';
12
12
  import { updateCli } from './commands/update.js';
13
13
  import { upgradeSkills } from './commands/upgrade.js';
14
14
  import { showStats } from './commands/stats.js';
15
+ import { backupSkills } from './commands/backup.js';
16
+ import { restoreSkills } from './commands/restore.js';
15
17
  import { listSources, addNewSource, removeExistingSource, setDefault } from './commands/source.js';
16
18
  import { listConfig, setGitHubToken, removeGitHubToken } from './commands/config.js';
17
19
  import { checkForUpdates } from './utils/updateNotifier.js';
@@ -95,6 +97,20 @@ program
95
97
  .description('Show statistics about installed skills')
96
98
  .action(showStats);
97
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
+
98
114
  program
99
115
  .command('completion')
100
116
  .description('Setup autocomplete for your shell (zsh/bash)')