antikit 1.4.0 → 1.6.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
@@ -1,6 +1,6 @@
1
1
  # antikit
2
2
 
3
- CLI tool to manage AI agent skills from the [antiskills](https://github.com/vunamhung/antiskills) repository.
3
+ CLI tool to manage AI agent skills from multiple repositories. Easily discover, install, and update skills for your AI agents.
4
4
 
5
5
  ## Installation
6
6
 
@@ -10,8 +10,9 @@ npm install -g antikit
10
10
 
11
11
  ## Usage
12
12
 
13
- ### List available skills from remote
13
+ ### šŸ“¦ Manage Skills
14
14
 
15
+ #### List available skills
15
16
  ```bash
16
17
  antikit list
17
18
  # or
@@ -19,17 +20,16 @@ antikit ls
19
20
 
20
21
  # Search skills by name
21
22
  antikit list -s <query>
22
- ```
23
23
 
24
- ### List installed local skills
24
+ # Interactive mode (select and install)
25
+ antikit list -i
25
26
 
26
- ```bash
27
- antikit local
28
- # or
29
- antikit l
27
+ # Filter by source
28
+ antikit list --source official
30
29
  ```
31
30
 
32
- ### Install a skill
31
+ #### Install a skill
32
+ Automatically installs dependencies defined in `SKILL.md`.
33
33
 
34
34
  ```bash
35
35
  antikit install <skill-name>
@@ -40,25 +40,94 @@ antikit i <skill-name>
40
40
  antikit install <skill-name> --force
41
41
  ```
42
42
 
43
- ### Remove a skill
43
+ #### Upgrade installed skills
44
+ Update your local skills to the latest version from their sources.
45
+
46
+ ```bash
47
+ # Upgrade all installed skills
48
+ antikit upgrade
49
+
50
+ # Upgrade a specific skill
51
+ antikit upgrade <skill-name>
52
+
53
+ # Upgrade without confirmation (good for scripts)
54
+ antikit upgrade --yes
55
+ ```
56
+
57
+ #### List installed local skills
58
+ ```bash
59
+ antikit local
60
+ # or
61
+ antikit l
62
+ ```
44
63
 
64
+ #### Remove a skill
45
65
  ```bash
46
66
  antikit remove <skill-name>
47
67
  # or
48
68
  antikit rm <skill-name>
49
69
  ```
50
70
 
51
- ## Requirements
71
+ ---
52
72
 
53
- - Node.js >= 18.0.0
54
- - Git (for installing skills)
55
- - A project with `.agent/skills` directory
73
+ ### šŸ“” Manage Sources
74
+ You can fetch skills from multiple GitHub repositories.
75
+
76
+ ```bash
77
+ # List configured sources
78
+ antikit source list
56
79
 
57
- ## How it works
80
+ # Add a new source (GitHub owner/repo)
81
+ antikit source add vunamhung/another-repo
58
82
 
59
- 1. Skills are fetched from the `vunamhung/antiskills` GitHub repository
60
- 2. Each skill is a folder containing a `SKILL.md` file with YAML frontmatter
61
- 3. Skills are installed to the nearest `.agent/skills` directory in your project
83
+ # Add with a custom name
84
+ antikit source add vunamhung/private-skills --name private
85
+
86
+ # Set a default source
87
+ antikit source default private
88
+
89
+ # Remove a source
90
+ antikit source remove private
91
+ ```
92
+
93
+ ---
94
+
95
+ ### šŸ”„ Self Update
96
+ Update the `antikit` CLI tool itself to the latest version.
97
+
98
+ ```bash
99
+ antikit update
100
+ ```
101
+ *Note: You will also be notified automatically if a new version is available when running any command.*
102
+
103
+ ---
104
+
105
+ ## Skill Development
106
+
107
+ ### Skill Structure
108
+ A skill is a directory containing a `SKILL.md` file.
109
+
110
+ ### Defining Dependencies
111
+ You can specify dependencies in the `SKILL.md` frontmatter. `antikit` will automatically install them.
112
+
113
+ ```yaml
114
+ ---
115
+ name: my-skill
116
+ description: A powerful skill that needs helpers
117
+ dependencies:
118
+ - sql-helper
119
+ - python-runner
120
+ ---
121
+
122
+ # My Skill Content
123
+ ...
124
+ ```
125
+
126
+ ## Requirements
127
+
128
+ - Node.js >= 18.0.0
129
+ - Git (for cloning skills)
130
+ - A project with `.agent/skills` directory (created automatically)
62
131
 
63
132
  ## License
64
133
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antikit",
3
- "version": "1.4.0",
3
+ "version": "1.6.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",
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { execSync } from 'child_process';
4
- import { existsSync, mkdirSync, cpSync, rmSync } from 'fs';
4
+ import { existsSync, mkdirSync, cpSync, rmSync, writeFileSync } from 'fs';
5
5
  import { join } from 'path';
6
6
  import { homedir } from 'os';
7
7
  import { getOrCreateSkillsDir, skillExists } from '../utils/local.js';
@@ -69,15 +69,96 @@ export async function installSkill(skillName, options = {}) {
69
69
  if (!existsSync(sourcePath)) {
70
70
  rmSync(tempDir, { recursive: true, force: true });
71
71
  spinner.fail(`Skill "${skillName}" not found in ${owner}/${repo}`);
72
+ if (options.noExit) throw new Error('Skill not found');
72
73
  process.exit(1);
73
74
  }
74
75
 
76
+ // --- Check dependencies ---
77
+ try {
78
+ const mdPath = join(sourcePath, 'SKILL.md');
79
+ if (existsSync(mdPath)) {
80
+ // Import locally to avoid cluttering top imports if possible, or just use fs
81
+ const { readFileSync } = await import('fs');
82
+ const content = readFileSync(mdPath, 'utf-8');
83
+
84
+ // Parse frontmatter dependencies
85
+ // Supports inline: dependencies: [a, b]
86
+ // Supports list:
87
+ // dependencies:
88
+ // - a
89
+ // - b
90
+ let deps = [];
91
+
92
+ // Try inline
93
+ const inlineMatch = content.match(/^dependencies:\s*\[(.*?)\]/m);
94
+ if (inlineMatch) {
95
+ deps = inlineMatch[1].split(',').map(s => s.trim().replace(/['"]/g, '')).filter(Boolean);
96
+ } else {
97
+ // Try list
98
+ const listMatch = content.match(/^dependencies:\s*\n((?:\s*-\s*.+\n?)+)/m);
99
+ if (listMatch) {
100
+ deps = listMatch[1].split('\n')
101
+ .map(l => l.replace(/^\s*-\s*/, '').trim())
102
+ .filter(Boolean);
103
+ }
104
+ }
105
+
106
+ if (deps.length > 0) {
107
+ spinner.stop();
108
+ console.log(chalk.blue(`\nSkill "${skillName}" requires: ${deps.join(', ')}`));
109
+
110
+ for (const dep of deps) {
111
+ // Prevent infinite recursion if self-referencing
112
+ if (dep === skillName) continue;
113
+
114
+ if (!skillExists(dep)) {
115
+ console.log(chalk.dim(`Installing dependency: ${dep}...`));
116
+ // Recursive install
117
+ await installSkill(dep, {
118
+ ...options,
119
+ force: false,
120
+ noExit: true // Don't exit on dependency failure, just log
121
+ });
122
+ }
123
+ }
124
+ spinner.start(`Resuming installation of "${skillName}"...`);
125
+ }
126
+ }
127
+ } catch (e) {
128
+ // Dep check failed, but continue installing main skill
129
+ // console.error(e);
130
+ }
131
+
75
132
  if (options.force && existsSync(destPath)) {
76
133
  rmSync(destPath, { recursive: true, force: true });
77
134
  }
78
135
 
79
136
  cpSync(sourcePath, destPath, { recursive: true });
80
137
 
138
+ // Save skill metadata for future upgrades
139
+ try {
140
+ let version = '0.0.0';
141
+ const mdPath = join(destPath, 'SKILL.md');
142
+ if (existsSync(mdPath)) {
143
+ // We likely already imported readFileSync if inside try block,
144
+ // but for safety use dynamic import or assume imported
145
+ const { readFileSync } = await import('fs');
146
+ const content = readFileSync(mdPath, 'utf-8');
147
+ const vMatch = content.match(/^version:\s*(.+)/m);
148
+ if (vMatch) version = vMatch[1].trim();
149
+ }
150
+
151
+ const metadata = {
152
+ name: skillName,
153
+ source: { owner, repo },
154
+ version,
155
+ installedAt: Date.now()
156
+ };
157
+ writeFileSync(join(destPath, '.antikit-skill.json'), JSON.stringify(metadata, null, 2));
158
+ } catch (e) {
159
+ // Ignore metadata write error, not critical
160
+ }
161
+
81
162
  // Cleanup temp
82
163
  rmSync(tempDir, { recursive: true, force: true });
83
164
 
@@ -87,7 +168,9 @@ export async function installSkill(skillName, options = {}) {
87
168
  } catch (error) {
88
169
  spinner.fail(`Failed to install "${skillName}"`);
89
170
  console.error(chalk.red(error.message));
171
+ if (options.noExit) {
172
+ throw error;
173
+ }
90
174
  process.exit(1);
91
175
  }
92
176
  }
93
-
@@ -2,8 +2,21 @@ import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { checkbox, confirm } from '@inquirer/prompts';
4
4
  import { fetchRemoteSkills, fetchSkillInfo } from '../utils/github.js';
5
- import { skillExists } from '../utils/local.js';
5
+ import { skillExists, getOrCreateSkillsDir } from '../utils/local.js';
6
6
  import { installSkill } from './install.js';
7
+ import { existsSync, readFileSync } from 'fs';
8
+ import { join } from 'path';
9
+
10
+ function compareVersions(v1, v2) {
11
+ if (!v1 || !v2) return 0;
12
+ const p1 = v1.split('.').map(Number);
13
+ const p2 = v2.split('.').map(Number);
14
+ for (let i = 0; i < 3; i++) {
15
+ if ((p1[i] || 0) > (p2[i] || 0)) return 1;
16
+ if ((p1[i] || 0) < (p2[i] || 0)) return -1;
17
+ }
18
+ return 0;
19
+ }
7
20
 
8
21
  export async function listRemoteSkills(options) {
9
22
  const sourceName = options.source || null;
@@ -26,13 +39,32 @@ export async function listRemoteSkills(options) {
26
39
  return;
27
40
  }
28
41
 
29
- // Fetch descriptions
42
+ const skillsDir = getOrCreateSkillsDir();
43
+
44
+ // Fetch descriptions & versions
30
45
  const infoSpinner = ora('Fetching skill info...').start();
31
46
  const skillsWithInfo = await Promise.all(
32
47
  skills.map(async (skill) => {
33
- const description = await fetchSkillInfo(skill.name, skill.owner, skill.repo);
48
+ const info = await fetchSkillInfo(skill.name, skill.owner, skill.repo);
49
+ const description = info ? info.description : null;
50
+ const remoteVersion = info ? info.version : '0.0.0';
51
+
34
52
  const installed = skillExists(skill.name);
35
- return { ...skill, description, installed };
53
+ let updateAvailable = false;
54
+ let localVersion = '0.0.0';
55
+
56
+ if (installed) {
57
+ try {
58
+ const metaPath = join(skillsDir, skill.name, '.antikit-skill.json');
59
+ if (existsSync(metaPath)) {
60
+ const meta = JSON.parse(readFileSync(metaPath, 'utf8'));
61
+ localVersion = meta.version || '0.0.0';
62
+ updateAvailable = compareVersions(remoteVersion, localVersion) > 0;
63
+ }
64
+ } catch { }
65
+ }
66
+
67
+ return { ...skill, description, installed, updateAvailable, localVersion, remoteVersion };
36
68
  })
37
69
  );
38
70
  infoSpinner.stop();
@@ -68,11 +100,14 @@ function displaySkillsList(skills) {
68
100
  for (const [sourceName, sourceSkills] of Object.entries(bySource)) {
69
101
  console.log(chalk.magenta.bold(`\nšŸ“¦ ${sourceName}`));
70
102
  for (const skill of sourceSkills) {
71
- const status = skill.installed
72
- ? chalk.green(' āœ“')
73
- : chalk.dim(' ');
103
+ let status = chalk.dim(' ');
104
+ if (skill.installed) {
105
+ status = skill.updateAvailable
106
+ ? chalk.yellow(' ↑')
107
+ : chalk.green(' āœ“');
108
+ }
74
109
 
75
- console.log(`${status} ${chalk.cyan.bold(skill.name)}`);
110
+ console.log(`${status} ${chalk.cyan.bold(skill.name)} ${skill.installed ? chalk.dim(`(v${skill.localVersion}${skill.updateAvailable ? ` → v${skill.remoteVersion}` : ''})`) : ''}`);
76
111
  if (skill.description) {
77
112
  console.log(` ${chalk.dim(skill.description)}`);
78
113
  }
@@ -85,15 +120,35 @@ function displaySkillsList(skills) {
85
120
 
86
121
  async function interactiveInstall(skills) {
87
122
  // Prepare choices for checkbox
88
- const choices = skills.map(skill => ({
89
- name: `${skill.installed ? chalk.green('āœ“') : ' '} ${chalk.cyan(skill.name)} ${chalk.dim(`[${skill.source}]`)} ${skill.description ? chalk.dim('- ' + skill.description.slice(0, 50) + '...') : ''}`,
90
- value: skill,
91
- disabled: skill.installed ? '(installed)' : false
92
- }));
123
+ const choices = skills.map(skill => {
124
+ let label = '';
125
+ let disabled = false;
126
+
127
+ if (skill.installed) {
128
+ if (skill.updateAvailable) {
129
+ label = `${chalk.yellow('↑')} ${chalk.cyan(skill.name)} ${chalk.yellow(`(Update: v${skill.localVersion} → v${skill.remoteVersion})`)}`;
130
+ } else {
131
+ label = `${chalk.green('āœ“')} ${chalk.cyan(skill.name)} ${chalk.dim('(Installed)')}`;
132
+ disabled = true; // Disable if installed and no update
133
+ }
134
+ } else {
135
+ label = `${chalk.dim(' ')} ${chalk.cyan(skill.name)}`;
136
+ }
137
+
138
+ if (skill.description) {
139
+ label += ` ${chalk.dim('- ' + skill.description.slice(0, 40) + '...')}`;
140
+ }
141
+
142
+ return {
143
+ name: label,
144
+ value: skill,
145
+ disabled
146
+ };
147
+ });
93
148
 
94
149
  // Show checkbox selection
95
150
  const selected = await checkbox({
96
- message: 'Select skills to install (Space to select, Enter to confirm):',
151
+ message: 'Select skills to install/update (Space to select, Enter to confirm):',
97
152
  choices,
98
153
  pageSize: 15
99
154
  });
@@ -105,21 +160,23 @@ async function interactiveInstall(skills) {
105
160
 
106
161
  // Confirm installation
107
162
  const shouldInstall = await confirm({
108
- message: `Install ${selected.length} skill(s)?`,
163
+ message: `Install/Update ${selected.length} skill(s)?`,
109
164
  default: true
110
165
  });
111
166
 
112
167
  if (!shouldInstall) {
113
- console.log(chalk.yellow('Installation cancelled.'));
168
+ console.log(chalk.yellow('Operation cancelled.'));
114
169
  return;
115
170
  }
116
171
 
117
172
  // Install selected skills
118
173
  console.log();
119
174
  for (const skill of selected) {
120
- await installSkill(skill.name, { force: false, owner: skill.owner, repo: skill.repo });
175
+ // Force install if updating
176
+ const force = skill.installed && skill.updateAvailable;
177
+ await installSkill(skill.name, { force, owner: skill.owner, repo: skill.repo });
121
178
  }
122
179
 
123
180
  console.log();
124
- console.log(chalk.green.bold(`āœ“ Installed ${selected.length} skill(s)`));
181
+ console.log(chalk.green.bold(`āœ“ Processed ${selected.length} skill(s)`));
125
182
  }
@@ -0,0 +1,90 @@
1
+ import chalk from 'chalk';
2
+ import { readdirSync, existsSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { getOrCreateSkillsDir } from '../utils/local.js';
5
+ import { installSkill } from './install.js';
6
+ import { confirm } from '@inquirer/prompts';
7
+
8
+ export async function upgradeSkills(skillName, options = {}) {
9
+ const skillsDir = getOrCreateSkillsDir();
10
+
11
+ // 1. Upgrade specific skill
12
+ if (skillName) {
13
+ try {
14
+ await upgradeSingleSkill(skillsDir, skillName);
15
+ } catch {
16
+ process.exit(1);
17
+ }
18
+ return;
19
+ }
20
+
21
+ // 2. Upgrade all skills
22
+ const skills = readdirSync(skillsDir).filter(f =>
23
+ existsSync(join(skillsDir, f)) &&
24
+ !f.startsWith('.')
25
+ );
26
+
27
+ if (skills.length === 0) {
28
+ console.log(chalk.yellow('No skills installed.'));
29
+ return;
30
+ }
31
+
32
+ console.log(chalk.blue(`Found ${skills.length} installed skills.`));
33
+
34
+ let shouldProceed = options.yes;
35
+ if (!shouldProceed) {
36
+ shouldProceed = await confirm({ message: 'Upgrade all skills?', default: true });
37
+ }
38
+
39
+ if (!shouldProceed) return;
40
+
41
+ let successCount = 0;
42
+ let failCount = 0;
43
+
44
+ for (const skill of skills) {
45
+ try {
46
+ await upgradeSingleSkill(skillsDir, skill);
47
+ successCount++;
48
+ } catch {
49
+ failCount++;
50
+ }
51
+ }
52
+
53
+ console.log('\n────────────────────────────────────────');
54
+ if (failCount === 0) {
55
+ console.log(chalk.green(`āœ“ All ${successCount} skills upgraded successfully`));
56
+ } else {
57
+ console.log(chalk.yellow(`⚠ Upgraded ${successCount} skills, ${failCount} failed`));
58
+ }
59
+ }
60
+
61
+ async function upgradeSingleSkill(skillsDir, skillName) {
62
+ const skillPath = join(skillsDir, skillName);
63
+ const metaPath = join(skillPath, '.antikit-skill.json');
64
+
65
+ if (!existsSync(metaPath)) {
66
+ console.log(chalk.yellow(`⚠ Skipping "${skillName}": Missing metadata (install again to fix)`));
67
+ throw new Error('Missing metadata');
68
+ }
69
+
70
+ try {
71
+ const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
72
+ if (!meta.source || !meta.source.owner || !meta.source.repo) {
73
+ console.log(chalk.yellow(`⚠ Skipping "${skillName}": Invalid metadata`));
74
+ throw new Error('Invalid metadata');
75
+ }
76
+
77
+ console.log(chalk.bold.cyan(`\nUpgrading ${skillName}...`));
78
+
79
+ await installSkill(skillName, {
80
+ force: true,
81
+ owner: meta.source.owner,
82
+ repo: meta.source.repo,
83
+ noExit: true // Don't kill process on error
84
+ });
85
+
86
+ } catch (error) {
87
+ // Error already logged by installSkill or above
88
+ throw error;
89
+ }
90
+ }
package/src/index.js CHANGED
@@ -8,9 +8,11 @@ import { listLocalSkills } from './commands/local.js';
8
8
  import { installSkill } from './commands/install.js';
9
9
  import { removeSkill } from './commands/remove.js';
10
10
  import { updateCli } from './commands/update.js';
11
+ import { upgradeSkills } from './commands/upgrade.js';
11
12
  import { listSources, addNewSource, removeExistingSource, setDefault } from './commands/source.js';
12
13
  import { checkForUpdates } from './utils/updateNotifier.js';
13
14
 
15
+
14
16
  const require = createRequire(import.meta.url);
15
17
  const pkg = require('../package.json');
16
18
 
@@ -59,6 +61,13 @@ program
59
61
  .description('Update antikit to the latest version')
60
62
  .action(updateCli);
61
63
 
64
+ program
65
+ .command('upgrade [skill]')
66
+ .alias('ug')
67
+ .description('Upgrade installed skills')
68
+ .option('-y, --yes', 'Skip confirmation')
69
+ .action(upgradeSkills);
70
+
62
71
  // Source management commands
63
72
  const sourceCmd = program
64
73
  .command('source')
@@ -95,14 +95,20 @@ export async function fetchSkillInfo(skillName, owner, repo) {
95
95
  const data = await response.json();
96
96
  const content = Buffer.from(data.content, 'base64').toString('utf-8');
97
97
 
98
- // Extract description from YAML frontmatter
98
+ // Extract info from YAML frontmatter
99
99
  const match = content.match(/^---\n([\s\S]*?)\n---/);
100
100
  if (match) {
101
- const descMatch = match[1].match(/description:\s*(.+)/);
102
- return descMatch ? descMatch[1].trim() : null;
101
+ const frontmatter = match[1];
102
+ const descMatch = frontmatter.match(/description:\s*(.+)/);
103
+ const versionMatch = frontmatter.match(/version:\s*(.+)/);
104
+
105
+ return {
106
+ description: descMatch ? descMatch[1].trim() : null,
107
+ version: versionMatch ? versionMatch[1].trim() : '0.0.0'
108
+ };
103
109
  }
104
110
 
105
- return null;
111
+ return { description: null, version: '0.0.0' };
106
112
  }
107
113
 
108
114
  /**