antikit 1.5.0 → 1.7.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.5.0",
3
+ "version": "1.7.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",
@@ -137,9 +137,21 @@ export async function installSkill(skillName, options = {}) {
137
137
 
138
138
  // Save skill metadata for future upgrades
139
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
+
140
151
  const metadata = {
141
152
  name: skillName,
142
153
  source: { owner, repo },
154
+ version,
143
155
  installedAt: Date.now()
144
156
  };
145
157
  writeFileSync(join(destPath, '.antikit-skill.json'), JSON.stringify(metadata, null, 2));
@@ -1,9 +1,22 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
- import { checkbox, confirm } from '@inquirer/prompts';
3
+ import { checkbox, confirm, Separator } 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
  }
@@ -84,18 +119,53 @@ function displaySkillsList(skills) {
84
119
  }
85
120
 
86
121
  async function interactiveInstall(skills) {
87
- // 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
- }));
122
+ // Sort skills by Source then Name
123
+ skills.sort((a, b) => {
124
+ if (a.source !== b.source) return a.source.localeCompare(b.source);
125
+ return a.name.localeCompare(b.name);
126
+ });
127
+
128
+ const choices = [];
129
+ let currentSource = null;
130
+
131
+ for (const skill of skills) {
132
+ // Add Separator for new source
133
+ if (skill.source !== currentSource) {
134
+ currentSource = skill.source;
135
+ choices.push(new Separator(` \n ──────── Source: ${chalk.bold.magenta(currentSource)} ────────`));
136
+ }
137
+
138
+ let label = '';
139
+ let disabled = false;
140
+
141
+ if (skill.installed) {
142
+ if (skill.updateAvailable) {
143
+ label = `${chalk.yellow('↑')} ${chalk.cyan(skill.name)} ${chalk.yellow(`(Update: v${skill.localVersion} → v${skill.remoteVersion})`)}`;
144
+ } else {
145
+ label = `${chalk.green('āœ“')} ${chalk.cyan(skill.name)} ${chalk.dim('(Installed)')}`;
146
+ disabled = true;
147
+ }
148
+ } else {
149
+ label = `${chalk.dim(' ')} ${chalk.cyan(skill.name)}`;
150
+ }
151
+
152
+ if (skill.description) {
153
+ label += ` ${chalk.dim('- ' + skill.description.slice(0, 40) + '...')}`;
154
+ }
155
+
156
+ choices.push({
157
+ name: label,
158
+ value: skill,
159
+ disabled
160
+ });
161
+ }
93
162
 
94
163
  // Show checkbox selection
95
164
  const selected = await checkbox({
96
- message: 'Select skills to install (Space to select, Enter to confirm):',
165
+ message: 'Select skills to install/update:',
97
166
  choices,
98
- pageSize: 15
167
+ pageSize: 20, // Increase page size for better view
168
+ loop: false
99
169
  });
100
170
 
101
171
  if (selected.length === 0) {
@@ -105,21 +175,23 @@ async function interactiveInstall(skills) {
105
175
 
106
176
  // Confirm installation
107
177
  const shouldInstall = await confirm({
108
- message: `Install ${selected.length} skill(s)?`,
178
+ message: `Install/Update ${selected.length} skill(s)?`,
109
179
  default: true
110
180
  });
111
181
 
112
182
  if (!shouldInstall) {
113
- console.log(chalk.yellow('Installation cancelled.'));
183
+ console.log(chalk.yellow('Operation cancelled.'));
114
184
  return;
115
185
  }
116
186
 
117
187
  // Install selected skills
118
188
  console.log();
119
189
  for (const skill of selected) {
120
- await installSkill(skill.name, { force: false, owner: skill.owner, repo: skill.repo });
190
+ // Force install if updating
191
+ const force = skill.installed && skill.updateAvailable;
192
+ await installSkill(skill.name, { force, owner: skill.owner, repo: skill.repo });
121
193
  }
122
194
 
123
195
  console.log();
124
- console.log(chalk.green.bold(`āœ“ Installed ${selected.length} skill(s)`));
196
+ console.log(chalk.green.bold(`āœ“ Processed ${selected.length} skill(s)`));
125
197
  }
@@ -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
  /**