antikit 1.10.1 ā 1.12.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 +98 -91
- package/package.json +8 -6
- package/src/commands/info.js +48 -0
- package/src/commands/list.js +11 -2
- package/src/commands/validate.js +67 -0
- package/src/index.js +13 -0
- package/src/utils/github.js +65 -25
package/README.md
CHANGED
|
@@ -10,184 +10,191 @@ npm install -g antikit
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- **Multi-source Support**: Fetch skills from any GitHub repository.
|
|
14
|
-
- **Sub-directory Support**: Skills can reside in sub-folders (e.g. `.claude/skills`).
|
|
15
|
-
- **Interactive UI**: Browse, select, and update skills with a rich terminal
|
|
16
|
-
- **
|
|
17
|
-
- **Smart Upgrades**: Detects version changes and
|
|
18
|
-
- **Autocomplete**: Full Zsh/Bash
|
|
13
|
+
- **Multi-source Support**: Fetch skills from any GitHub repository (Public or Private).
|
|
14
|
+
- **Sub-directory Support**: Skills can reside in sub-folders (e.g. `.claude/skills`) within a repo.
|
|
15
|
+
- **Interactive UI**: Browse, select, multi-install, and update skills with a rich terminal interface.
|
|
16
|
+
- **Private Repo Access**: Seamless integration with private repositories via GitHub Token.
|
|
17
|
+
- **Smart Upgrades**: Detects version changes, manages dependencies, and streamlines updates.
|
|
18
|
+
- **Autocomplete**: Full Zsh/Bash/Fish tab-completion support.
|
|
19
|
+
- **Performance**: Optimized fetching using GitHub GraphQL API.
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## š Quick Start
|
|
21
24
|
|
|
22
|
-
###
|
|
25
|
+
### 1. Setup Autocomplete (Recommended)
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
This enables tab completion for commands and skill names.
|
|
25
28
|
|
|
26
29
|
```bash
|
|
27
30
|
antikit completion
|
|
28
|
-
# Follow instructions to add to ~/.zshrc or ~/.bashrc
|
|
31
|
+
# Follow the instructions to add the script to your ~/.zshrc or ~/.bashrc
|
|
29
32
|
```
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
### 2. Configure Authentication (Optional but Recommended)
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
antikit list
|
|
35
|
-
# or simply
|
|
36
|
-
antikit ls
|
|
37
|
-
```
|
|
36
|
+
To avoid API rate limits (60 requests/hr) and access **Private Repositories**, configure a GitHub Token.
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
1. **Create Token:** [Generate New Token](https://github.com/settings/tokens/new?description=antikit-cli&scopes=repo)
|
|
39
|
+
- Select scope `public_repo` (for public access) or `repo` (for private access).
|
|
40
|
+
2. **Set Token:**
|
|
41
|
+
```bash
|
|
42
|
+
antikit config set-token ghp_YOUR_TOKEN_HERE
|
|
43
|
+
```
|
|
40
44
|
|
|
41
45
|
---
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
## š Usage Guide
|
|
48
|
+
|
|
49
|
+
### š Discovery & Listing
|
|
44
50
|
|
|
45
|
-
|
|
51
|
+
**Interactive Mode (Default)**
|
|
52
|
+
Browse skills, see installed status, updates available, and descriptions. Use Space to select multiple skills and Enter to install.
|
|
46
53
|
|
|
47
54
|
```bash
|
|
48
|
-
|
|
49
|
-
antikit ls
|
|
55
|
+
antikit list
|
|
56
|
+
# Alias: antikit ls
|
|
57
|
+
```
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
antikit ls -s <query>
|
|
59
|
+
**Search & Filters**
|
|
53
60
|
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
```bash
|
|
62
|
+
# Search by skill name
|
|
63
|
+
antikit ls -s <keyword>
|
|
56
64
|
|
|
57
65
|
# Filter by source
|
|
58
66
|
antikit ls --source official
|
|
67
|
+
antikit ls --source claudekit
|
|
68
|
+
|
|
69
|
+
# Text Mode (Non-interactive, good for scripting)
|
|
70
|
+
antikit ls --text
|
|
59
71
|
```
|
|
60
72
|
|
|
61
|
-
|
|
73
|
+
### ā¬ļø Installation & Updates
|
|
62
74
|
|
|
63
|
-
|
|
75
|
+
**Install Skills**
|
|
76
|
+
Automatically resolves and installs dependencies defined in `SKILL.md`.
|
|
64
77
|
|
|
65
78
|
```bash
|
|
66
79
|
antikit install <skill-name>
|
|
67
|
-
#
|
|
68
|
-
antikit i <skill-name>
|
|
80
|
+
# Alias: antikit i <skill-name>
|
|
69
81
|
|
|
70
|
-
# Force
|
|
82
|
+
# Force re-install
|
|
71
83
|
antikit install <skill-name> --force
|
|
72
84
|
```
|
|
73
85
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Update your local skills to the latest version from their sources.
|
|
86
|
+
**Upgrade Skills**
|
|
87
|
+
Keep your skills up-to-date with one command.
|
|
77
88
|
|
|
78
89
|
```bash
|
|
79
|
-
# Upgrade all installed skills
|
|
90
|
+
# Upgrade all installed skills (Interactive confirmation)
|
|
80
91
|
antikit upgrade
|
|
81
|
-
#
|
|
82
|
-
antikit ug
|
|
92
|
+
# Alias: antikit ug
|
|
83
93
|
|
|
84
94
|
# Upgrade a specific skill
|
|
85
95
|
antikit upgrade <skill-name>
|
|
86
96
|
|
|
87
|
-
# Upgrade without confirmation (
|
|
97
|
+
# Upgrade all without confirmation (CI/Script mode)
|
|
88
98
|
antikit upgrade --yes
|
|
89
99
|
```
|
|
90
100
|
|
|
91
|
-
|
|
101
|
+
**Manage Local Skills**
|
|
92
102
|
|
|
93
103
|
```bash
|
|
104
|
+
# List locally installed skills
|
|
94
105
|
antikit local
|
|
95
|
-
#
|
|
96
|
-
antikit l
|
|
97
|
-
```
|
|
106
|
+
# Alias: antikit l
|
|
98
107
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
```bash
|
|
108
|
+
# Remove a skill
|
|
102
109
|
antikit remove <skill-name>
|
|
103
|
-
#
|
|
104
|
-
antikit rm <skill-name>
|
|
110
|
+
# Alias: antikit rm <skill-name>
|
|
105
111
|
```
|
|
106
112
|
|
|
107
|
-
|
|
113
|
+
### š” Source Management
|
|
108
114
|
|
|
109
|
-
|
|
115
|
+
You can fetch skills from multiple repositories, including monorepos with sub-directories.
|
|
110
116
|
|
|
111
|
-
|
|
117
|
+
**List Sources**
|
|
112
118
|
|
|
113
119
|
```bash
|
|
114
|
-
# List configured sources
|
|
115
120
|
antikit source list
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Add Sources**
|
|
116
124
|
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
```bash
|
|
126
|
+
# Add a Public/Private GitHub Repo
|
|
127
|
+
antikit source add owner/repo-name
|
|
119
128
|
|
|
120
|
-
# Add
|
|
129
|
+
# Add from a specific Sub-directory (e.g. monorepo)
|
|
121
130
|
antikit source add mrgoonie/claudekit-skills --path .claude/skills --name claudekit
|
|
122
131
|
|
|
123
|
-
# Add with a custom
|
|
124
|
-
antikit source add
|
|
132
|
+
# Add with a custom alias
|
|
133
|
+
antikit source add my-org/private-skills --name work
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Manage Sources**
|
|
125
137
|
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
```bash
|
|
139
|
+
# Set a default source for basic installs
|
|
140
|
+
antikit source default work
|
|
128
141
|
|
|
129
142
|
# Remove a source
|
|
130
|
-
antikit source remove
|
|
143
|
+
antikit source remove work
|
|
131
144
|
```
|
|
132
145
|
|
|
133
|
-
|
|
146
|
+
### āļø Configuration
|
|
147
|
+
|
|
148
|
+
Manage your local configuration and credentials.
|
|
134
149
|
|
|
135
|
-
|
|
150
|
+
```bash
|
|
151
|
+
# View current config (masked token)
|
|
152
|
+
antikit config list
|
|
136
153
|
|
|
137
|
-
Update
|
|
154
|
+
# Update GitHub Token
|
|
155
|
+
antikit config set-token <new_token>
|
|
156
|
+
|
|
157
|
+
# Remove Token
|
|
158
|
+
antikit config remove-token
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### š Tool Maintenance
|
|
162
|
+
|
|
163
|
+
**Update CLI**
|
|
164
|
+
Update `antikit` itself to the latest version.
|
|
138
165
|
|
|
139
166
|
```bash
|
|
140
167
|
antikit update
|
|
168
|
+
# Alias: antikit up
|
|
141
169
|
```
|
|
142
170
|
|
|
143
|
-
_Note: You will also be notified automatically if a new version is available when running any command._
|
|
144
|
-
|
|
145
171
|
---
|
|
146
172
|
|
|
147
|
-
## Skill Development
|
|
173
|
+
## š Skill Development
|
|
148
174
|
|
|
149
175
|
### Skill Structure
|
|
150
176
|
|
|
151
|
-
A skill is a directory containing a `SKILL.md`
|
|
177
|
+
A skill is simply a directory containing a `SKILL.md` (metadata & instructions) and any associated files.
|
|
152
178
|
|
|
153
179
|
### Defining Version & Dependencies
|
|
154
180
|
|
|
155
|
-
|
|
181
|
+
Add YAML frontmatter to your `SKILL.md` to enable versioning and automatic dependency installation.
|
|
156
182
|
|
|
157
183
|
```yaml
|
|
158
184
|
---
|
|
159
|
-
name: my-skill
|
|
160
|
-
description:
|
|
161
|
-
version: 1.0
|
|
185
|
+
name: my-complex-skill
|
|
186
|
+
description: Performs magic operations
|
|
187
|
+
version: 1.2.0
|
|
162
188
|
dependencies:
|
|
163
|
-
-
|
|
164
|
-
-
|
|
189
|
+
- simple-helper-skill
|
|
190
|
+
- another-dependency
|
|
165
191
|
---
|
|
166
|
-
#
|
|
192
|
+
# Skill Instructions
|
|
167
193
|
...
|
|
168
194
|
```
|
|
169
195
|
|
|
170
196
|
---
|
|
171
197
|
|
|
172
|
-
### š Authentication (Optional)
|
|
173
|
-
|
|
174
|
-
To increase GitHub API rate limits (avoiding "API rate limit exceeded" errors), you can configure a Personal Access Token.
|
|
175
|
-
|
|
176
|
-
```bash
|
|
177
|
-
# Set token
|
|
178
|
-
antikit config set-token ghp_xxxxxxxxxxxx
|
|
179
|
-
|
|
180
|
-
# Check config
|
|
181
|
-
antikit config list
|
|
182
|
-
# or
|
|
183
|
-
antikit config ls
|
|
184
|
-
|
|
185
|
-
# Remove token
|
|
186
|
-
antikit config remove-token
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
---
|
|
190
|
-
|
|
191
198
|
## Requirements
|
|
192
199
|
|
|
193
200
|
- Node.js >= 18.0.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "antikit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.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",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
12
|
-
"
|
|
12
|
+
"release": "commit-and-tag-version",
|
|
13
13
|
"prepare": "husky",
|
|
14
14
|
"format": "prettier --write ."
|
|
15
15
|
},
|
|
@@ -47,16 +47,18 @@
|
|
|
47
47
|
"@inquirer/prompts": "^8.2.0",
|
|
48
48
|
"chalk": "^5.3.0",
|
|
49
49
|
"commander": "^12.1.0",
|
|
50
|
+
"marked": "^15.0.12",
|
|
51
|
+
"marked-terminal": "^7.3.0",
|
|
50
52
|
"omelette": "^0.4.17",
|
|
51
53
|
"ora": "^8.1.1",
|
|
52
54
|
"simple-git": "^3.27.0"
|
|
53
55
|
},
|
|
54
56
|
"devDependencies": {
|
|
55
|
-
"@
|
|
56
|
-
"@
|
|
57
|
+
"@commitlint/cli": "^20.3.1",
|
|
58
|
+
"@commitlint/config-conventional": "^20.3.1",
|
|
59
|
+
"commit-and-tag-version": "^12.6.1",
|
|
57
60
|
"husky": "^9.1.7",
|
|
58
61
|
"lint-staged": "^16.2.7",
|
|
59
|
-
"prettier": "^3.7.4"
|
|
60
|
-
"semantic-release": "^24.2.0"
|
|
62
|
+
"prettier": "^3.7.4"
|
|
61
63
|
}
|
|
62
64
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { marked } from 'marked';
|
|
5
|
+
import TerminalRenderer from 'marked-terminal';
|
|
6
|
+
import { getSkillsDir, skillExists } from '../utils/local.js';
|
|
7
|
+
import { fetchSkillInfo } from '../utils/github.js';
|
|
8
|
+
|
|
9
|
+
// Setup marked to render to terminal
|
|
10
|
+
marked.setOptions({
|
|
11
|
+
renderer: new TerminalRenderer()
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export async function showSkillInfo(skillName, options) {
|
|
15
|
+
if (!skillName) {
|
|
16
|
+
console.error(chalk.red('Please provide a skill name.'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const skillsDir = getSkillsDir();
|
|
21
|
+
const localSkillPath = path.join(skillsDir, skillName, 'SKILL.md');
|
|
22
|
+
|
|
23
|
+
// 1. Try Local First
|
|
24
|
+
if (skillExists(skillName) && fs.existsSync(localSkillPath)) {
|
|
25
|
+
console.log(chalk.bold.green(`\nš Viewing local docs for: ${skillName}\n`));
|
|
26
|
+
const content = fs.readFileSync(localSkillPath, 'utf8');
|
|
27
|
+
console.log(marked(content));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 2. Try Remote if not found locally
|
|
32
|
+
console.log(chalk.yellow(`Skill "${skillName}" not found locally. Searching remote...`));
|
|
33
|
+
const info = await fetchSkillInfo(skillName);
|
|
34
|
+
|
|
35
|
+
if (info && info.content) {
|
|
36
|
+
// Note: fetchSkillInfo currently returns parsed metadata (desc, version).
|
|
37
|
+
// I need to update fetchSkillInfo or create a new internal function to get THE RAW CONTENT for rendering.
|
|
38
|
+
// Wait, fetchSkillInfo in github.js calculates metadata from content but currently DOES NOT return the full content string.
|
|
39
|
+
// I should update fetchSkillInfo to return 'raw' content as well.
|
|
40
|
+
|
|
41
|
+
// Let's rely on the updated logic I'm about to add to github.js to return 'content'
|
|
42
|
+
console.log(chalk.bold.blue(`\nš Viewing remote docs for: ${skillName}\n`));
|
|
43
|
+
console.log(marked(info.content));
|
|
44
|
+
} else {
|
|
45
|
+
// Fallback if I haven't updated github.js yet or skill not found
|
|
46
|
+
console.error(chalk.red(`\nā Skill "${skillName}" not found in any configured source.`));
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/commands/list.js
CHANGED
|
@@ -111,7 +111,14 @@ function displaySkillsList(skills) {
|
|
|
111
111
|
return acc;
|
|
112
112
|
}, {});
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
const sortedSourceNames = Object.keys(bySource).sort((a, b) => {
|
|
115
|
+
if (a === 'official') return -1;
|
|
116
|
+
if (b === 'official') return 1;
|
|
117
|
+
return a.localeCompare(b);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
for (const sourceName of sortedSourceNames) {
|
|
121
|
+
const sourceSkills = bySource[sourceName];
|
|
115
122
|
console.log(chalk.magenta.bold(`\nš¦ ${sourceName}`));
|
|
116
123
|
for (const skill of sourceSkills) {
|
|
117
124
|
let status = chalk.dim(' ');
|
|
@@ -133,8 +140,10 @@ function displaySkillsList(skills) {
|
|
|
133
140
|
}
|
|
134
141
|
|
|
135
142
|
async function interactiveInstall(skills) {
|
|
136
|
-
// Sort skills by Source then Name
|
|
143
|
+
// Sort skills by Source (Official first) then Name
|
|
137
144
|
skills.sort((a, b) => {
|
|
145
|
+
if (a.source === 'official' && b.source !== 'official') return -1;
|
|
146
|
+
if (b.source === 'official' && a.source !== 'official') return 1;
|
|
138
147
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
139
148
|
return a.name.localeCompare(b.name);
|
|
140
149
|
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export async function validateSkill(targetPath = '.') {
|
|
6
|
+
const absolutePath = path.resolve(targetPath);
|
|
7
|
+
|
|
8
|
+
// If targetPath is a file (SKILL.md), use it. Iterate up if needed
|
|
9
|
+
let skillMdPath = absolutePath;
|
|
10
|
+
if (!skillMdPath.endsWith('SKILL.md')) {
|
|
11
|
+
skillMdPath = path.join(absolutePath, 'SKILL.md');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
console.log(chalk.bold(`Inspecting: ${skillMdPath}\n`));
|
|
15
|
+
|
|
16
|
+
if (!fs.existsSync(skillMdPath)) {
|
|
17
|
+
console.error(chalk.red('ā SKILL.md not found!'));
|
|
18
|
+
console.log(
|
|
19
|
+
chalk.dim('Make sure you are in the root directory of the skill or provide a path.')
|
|
20
|
+
);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const content = fs.readFileSync(skillMdPath, 'utf8');
|
|
25
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
26
|
+
|
|
27
|
+
if (!match) {
|
|
28
|
+
console.error(chalk.red('ā Missing or invalid YAML Frontmatter.'));
|
|
29
|
+
console.log('File must start with:\n---\nkey: value\n---');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const frontmatter = match[1];
|
|
34
|
+
|
|
35
|
+
// Simple parser
|
|
36
|
+
const parse = key => {
|
|
37
|
+
const m = frontmatter.match(new RegExp(`${key}:\\s*(.+)`));
|
|
38
|
+
return m ? m[1].trim() : null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const errors = [];
|
|
42
|
+
const name = parse('name');
|
|
43
|
+
const desc = parse('description');
|
|
44
|
+
const version = parse('version');
|
|
45
|
+
|
|
46
|
+
if (!name) errors.push('Missing "name" field');
|
|
47
|
+
if (!desc) errors.push('Missing "description" field');
|
|
48
|
+
if (!version) errors.push('Missing "version" field');
|
|
49
|
+
|
|
50
|
+
if (errors.length > 0) {
|
|
51
|
+
console.error(chalk.red('ā Validation Failed:'));
|
|
52
|
+
errors.forEach(e => console.error(chalk.red(` - ${e}`)));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Validate dependencies format if exists
|
|
57
|
+
if (frontmatter.includes('dependencies:')) {
|
|
58
|
+
// Check indentation vaguely
|
|
59
|
+
// const deps = frontmatter.match(/dependencies:\s*\n(\s+-\s+.+\n)+/);
|
|
60
|
+
// Regex validation for list is hard, let's skip deep validation for now.
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(chalk.green('ā
Skill Metadata is Valid!'));
|
|
64
|
+
console.log(chalk.dim('Name: ') + chalk.cyan(name));
|
|
65
|
+
console.log(chalk.dim('Version: ') + chalk.cyan(version));
|
|
66
|
+
console.log(chalk.dim('Description:') + desc);
|
|
67
|
+
}
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,8 @@ import { listRemoteSkills } from './commands/list.js';
|
|
|
7
7
|
import { listLocalSkills } from './commands/local.js';
|
|
8
8
|
import { installSkill } from './commands/install.js';
|
|
9
9
|
import { removeSkill } from './commands/remove.js';
|
|
10
|
+
import { showSkillInfo } from './commands/info.js';
|
|
11
|
+
import { validateSkill } from './commands/validate.js';
|
|
10
12
|
import { updateCli } from './commands/update.js';
|
|
11
13
|
import { upgradeSkills } from './commands/upgrade.js';
|
|
12
14
|
import { listSources, addNewSource, removeExistingSource, setDefault } from './commands/source.js';
|
|
@@ -59,6 +61,17 @@ program
|
|
|
59
61
|
.description('Remove an installed skill')
|
|
60
62
|
.action(removeSkill);
|
|
61
63
|
|
|
64
|
+
program
|
|
65
|
+
.command('info <skill>')
|
|
66
|
+
.alias('doc')
|
|
67
|
+
.description('Show skill documentation (SKILL.md)')
|
|
68
|
+
.action(showSkillInfo);
|
|
69
|
+
|
|
70
|
+
program
|
|
71
|
+
.command('validate [path]')
|
|
72
|
+
.description('Validate SKILL.md structure and metadata')
|
|
73
|
+
.action(validateSkill);
|
|
74
|
+
|
|
62
75
|
program
|
|
63
76
|
.command('update')
|
|
64
77
|
.alias('up')
|
package/src/utils/github.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
1
2
|
import { getSources, getToken } from './configManager.js';
|
|
2
3
|
|
|
3
4
|
const GITHUB_API = 'https://api.github.com';
|
|
4
5
|
|
|
6
|
+
// Global flag to prevent duplicate rate limit logs
|
|
7
|
+
let hasLoggedRateLimit = false;
|
|
8
|
+
|
|
5
9
|
function getHeaders() {
|
|
6
10
|
const headers = {
|
|
7
11
|
Accept: 'application/vnd.github.v3+json',
|
|
@@ -14,6 +18,22 @@ function getHeaders() {
|
|
|
14
18
|
return headers;
|
|
15
19
|
}
|
|
16
20
|
|
|
21
|
+
function logRateLimitError() {
|
|
22
|
+
if (hasLoggedRateLimit) return;
|
|
23
|
+
hasLoggedRateLimit = true;
|
|
24
|
+
|
|
25
|
+
console.error(chalk.yellow('\nā ļø GitHub API rate limit exceeded.'));
|
|
26
|
+
console.error(
|
|
27
|
+
chalk.dim('You are seeing this because unauthenticated requests are limited to 60/hr.')
|
|
28
|
+
);
|
|
29
|
+
console.error('\nTo fix this:');
|
|
30
|
+
console.error(
|
|
31
|
+
`1. Create a token: ${chalk.underline('https://github.com/settings/tokens/new?description=antikit-cli&scopes=repo')}`
|
|
32
|
+
);
|
|
33
|
+
console.error(`2. Run command: ${chalk.cyan('antikit config set-token <your_token>')}`);
|
|
34
|
+
console.error();
|
|
35
|
+
}
|
|
36
|
+
|
|
17
37
|
/**
|
|
18
38
|
* Fetch skills using GraphQL (Optimized: 1 request per source)
|
|
19
39
|
*/
|
|
@@ -44,8 +64,7 @@ async function fetchSkillsViaGraphQL(source, token) {
|
|
|
44
64
|
}
|
|
45
65
|
`;
|
|
46
66
|
|
|
47
|
-
const branch = source.branch || 'main';
|
|
48
|
-
// Correct expression for path. If path is provided, it's "branch:path", else just "branch:"
|
|
67
|
+
const branch = source.branch || 'main';
|
|
49
68
|
const expression = source.path ? `${branch}:${source.path}` : `${branch}:`;
|
|
50
69
|
|
|
51
70
|
try {
|
|
@@ -79,7 +98,6 @@ async function fetchSkillsViaGraphQL(source, token) {
|
|
|
79
98
|
let description = null;
|
|
80
99
|
let version = '0.0.0';
|
|
81
100
|
|
|
82
|
-
// Attempt to parse SKILL.md content if it exists
|
|
83
101
|
const skillFile = item.object.file && item.object.file[0];
|
|
84
102
|
if (skillFile && skillFile.object && skillFile.object.text) {
|
|
85
103
|
const content = skillFile.object.text;
|
|
@@ -100,9 +118,10 @@ async function fetchSkillsViaGraphQL(source, token) {
|
|
|
100
118
|
source: source.name,
|
|
101
119
|
owner: source.owner,
|
|
102
120
|
repo: source.repo,
|
|
121
|
+
branch: source.branch || 'main',
|
|
103
122
|
basePath: source.path,
|
|
104
|
-
description,
|
|
105
|
-
version
|
|
123
|
+
description,
|
|
124
|
+
version
|
|
106
125
|
};
|
|
107
126
|
});
|
|
108
127
|
} catch (e) {
|
|
@@ -114,7 +133,7 @@ async function fetchSkillsViaGraphQL(source, token) {
|
|
|
114
133
|
* Fetch list of skills from a specific source
|
|
115
134
|
*/
|
|
116
135
|
async function fetchSkillsFromSource(source) {
|
|
117
|
-
// Try GraphQL first if token exists
|
|
136
|
+
// Try GraphQL first if token exists
|
|
118
137
|
const token = getToken() || process.env.ANTIKIT_GITHUB_TOKEN || process.env.GITHUB_TOKEN;
|
|
119
138
|
if (token) {
|
|
120
139
|
const gqlResult = await fetchSkillsViaGraphQL(source, token);
|
|
@@ -126,7 +145,6 @@ async function fetchSkillsFromSource(source) {
|
|
|
126
145
|
if (source.path) {
|
|
127
146
|
url += `/${source.path}`;
|
|
128
147
|
}
|
|
129
|
-
// ... rest of function
|
|
130
148
|
|
|
131
149
|
const response = await fetch(url, {
|
|
132
150
|
headers: getHeaders()
|
|
@@ -137,11 +155,9 @@ async function fetchSkillsFromSource(source) {
|
|
|
137
155
|
|
|
138
156
|
// Check for rate limit
|
|
139
157
|
if (response.status === 403 && data.message.includes('rate limit')) {
|
|
140
|
-
|
|
141
|
-
console.error(
|
|
142
|
-
'Please set GITHUB_TOKEN or ANTIKIT_GITHUB_TOKEN environment variable to increase limit.\n'
|
|
143
|
-
);
|
|
158
|
+
logRateLimitError();
|
|
144
159
|
}
|
|
160
|
+
|
|
145
161
|
// Handle empty repository
|
|
146
162
|
if (data.message === 'This repository is empty.') {
|
|
147
163
|
return [];
|
|
@@ -153,7 +169,7 @@ async function fetchSkillsFromSource(source) {
|
|
|
153
169
|
const contents = await response.json();
|
|
154
170
|
|
|
155
171
|
if (!Array.isArray(contents)) {
|
|
156
|
-
return [];
|
|
172
|
+
return [];
|
|
157
173
|
}
|
|
158
174
|
|
|
159
175
|
// Filter only directories (skills)
|
|
@@ -162,24 +178,39 @@ async function fetchSkillsFromSource(source) {
|
|
|
162
178
|
.map(item => ({
|
|
163
179
|
name: item.name,
|
|
164
180
|
url: item.html_url,
|
|
165
|
-
path: item.path,
|
|
181
|
+
path: item.path,
|
|
166
182
|
source: source.name,
|
|
167
183
|
owner: source.owner,
|
|
168
184
|
repo: source.repo,
|
|
169
185
|
branch: source.branch || 'main',
|
|
170
|
-
basePath: source.path
|
|
186
|
+
basePath: source.path
|
|
171
187
|
}));
|
|
172
188
|
|
|
173
189
|
return skills;
|
|
174
190
|
}
|
|
175
191
|
|
|
176
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Fetch list of skills from all configured sources
|
|
194
|
+
*/
|
|
195
|
+
export async function fetchRemoteSkills(sourceName = null) {
|
|
196
|
+
const sources = getSources();
|
|
197
|
+
const targetSources = sourceName ? sources.filter(s => s.name === sourceName) : sources;
|
|
198
|
+
|
|
199
|
+
if (targetSources.length === 0) {
|
|
200
|
+
throw new Error(`Source "${sourceName}" not found.`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Reset rate limit flag before new fetch
|
|
204
|
+
hasLoggedRateLimit = false;
|
|
205
|
+
|
|
206
|
+
const results = await Promise.all(targetSources.map(source => fetchSkillsFromSource(source)));
|
|
207
|
+
return results.flat();
|
|
208
|
+
}
|
|
177
209
|
|
|
178
210
|
/**
|
|
179
211
|
* Fetch SKILL.md content for a specific skill
|
|
180
212
|
*/
|
|
181
213
|
export async function fetchSkillInfo(skillName, owner, repo, path = null, branch = null) {
|
|
182
|
-
// If owner/repo not provided, search in all sources
|
|
183
214
|
if (!owner || !repo) {
|
|
184
215
|
const skills = await fetchRemoteSkills();
|
|
185
216
|
const skill = skills.find(s => s.name === skillName);
|
|
@@ -192,23 +223,23 @@ export async function fetchSkillInfo(skillName, owner, repo, path = null, branch
|
|
|
192
223
|
|
|
193
224
|
let content = null;
|
|
194
225
|
|
|
195
|
-
// Optimized: Use Raw URL if branch is known
|
|
226
|
+
// Optimized: Use Raw URL if branch is known
|
|
196
227
|
if (branch) {
|
|
197
228
|
let rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
|
|
198
229
|
if (path) rawUrl += `/${path}`;
|
|
199
230
|
rawUrl += `/${skillName}/SKILL.md`;
|
|
200
231
|
|
|
201
232
|
try {
|
|
202
|
-
const res = await fetch(rawUrl
|
|
233
|
+
const res = await fetch(rawUrl, {
|
|
234
|
+
headers: getHeaders()
|
|
235
|
+
});
|
|
203
236
|
if (res.ok) {
|
|
204
237
|
content = await res.text();
|
|
205
238
|
}
|
|
206
|
-
} catch (e) {
|
|
207
|
-
// Ignore fetch error, fallback to API
|
|
208
|
-
}
|
|
239
|
+
} catch (e) {}
|
|
209
240
|
}
|
|
210
241
|
|
|
211
|
-
// Fallback: Use API
|
|
242
|
+
// Fallback: Use API
|
|
212
243
|
if (!content) {
|
|
213
244
|
let url = `${GITHUB_API}/repos/${owner}/${repo}/contents`;
|
|
214
245
|
if (path) {
|
|
@@ -221,6 +252,15 @@ export async function fetchSkillInfo(skillName, owner, repo, path = null, branch
|
|
|
221
252
|
});
|
|
222
253
|
|
|
223
254
|
if (!response.ok) {
|
|
255
|
+
// Check for rate limit also here
|
|
256
|
+
if (response.status === 403) {
|
|
257
|
+
// We can check body/headers but usually 403 here means rate limit if 404 is handled
|
|
258
|
+
// But simpler just to ignore or log if we strictly check msg
|
|
259
|
+
try {
|
|
260
|
+
const d = await response.json();
|
|
261
|
+
if (d.message.includes('rate limit')) logRateLimitError();
|
|
262
|
+
} catch {}
|
|
263
|
+
}
|
|
224
264
|
return null;
|
|
225
265
|
}
|
|
226
266
|
|
|
@@ -228,7 +268,6 @@ export async function fetchSkillInfo(skillName, owner, repo, path = null, branch
|
|
|
228
268
|
content = Buffer.from(data.content, 'base64').toString('utf-8');
|
|
229
269
|
}
|
|
230
270
|
|
|
231
|
-
// Extract info from YAML frontmatter
|
|
232
271
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
233
272
|
if (match) {
|
|
234
273
|
const frontmatter = match[1];
|
|
@@ -237,11 +276,12 @@ export async function fetchSkillInfo(skillName, owner, repo, path = null, branch
|
|
|
237
276
|
|
|
238
277
|
return {
|
|
239
278
|
description: descMatch ? descMatch[1].trim() : null,
|
|
240
|
-
version: versionMatch ? versionMatch[1].trim() : '0.0.0'
|
|
279
|
+
version: versionMatch ? versionMatch[1].trim() : '0.0.0',
|
|
280
|
+
content // Return raw content
|
|
241
281
|
};
|
|
242
282
|
}
|
|
243
283
|
|
|
244
|
-
return { description: null, version: '0.0.0' };
|
|
284
|
+
return { description: null, version: '0.0.0', content };
|
|
245
285
|
}
|
|
246
286
|
|
|
247
287
|
/**
|