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 +49 -0
- package/package.json +1 -1
- package/src/commands/backup.js +118 -0
- package/src/commands/restore.js +196 -0
- package/src/index.js +16 -0
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
|
@@ -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)')
|