antikit 1.9.1 → 1.10.1

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
@@ -167,6 +167,27 @@ dependencies:
167
167
  ...
168
168
  ```
169
169
 
170
+ ---
171
+
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
+
170
191
  ## Requirements
171
192
 
172
193
  - Node.js >= 18.0.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antikit",
3
- "version": "1.9.1",
3
+ "version": "1.10.1",
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,53 @@
1
+ import chalk from 'chalk';
2
+ import {
3
+ setToken,
4
+ getToken,
5
+ removeToken,
6
+ getConfigPath,
7
+ loadConfig
8
+ } from '../utils/configManager.js';
9
+
10
+ /**
11
+ * List all configurations
12
+ */
13
+ export function listConfig() {
14
+ const config = loadConfig();
15
+ console.log(chalk.bold('\nCurrent Configuration:'));
16
+ console.log(chalk.dim('─'.repeat(40)));
17
+
18
+ if (config.githubToken) {
19
+ console.log(`GitHub Token: ${chalk.green('********' + config.githubToken.slice(-4))}`);
20
+ } else {
21
+ console.log(`GitHub Token: ${chalk.dim('(not set)')}`);
22
+ }
23
+
24
+ // List other configs if any...
25
+
26
+ console.log();
27
+ console.log(chalk.dim(`Config file: ${getConfigPath()}`));
28
+ console.log();
29
+ }
30
+
31
+ /**
32
+ * Set GitHub Token
33
+ */
34
+ export function setGitHubToken(token) {
35
+ if (!token) {
36
+ console.error(chalk.red('Error: Token is required'));
37
+ process.exit(1);
38
+ }
39
+
40
+ setToken(token);
41
+ console.log(chalk.green('\nāœ“ GitHub Token saved successfully.'));
42
+ console.log(chalk.dim('API rate limit increased.'));
43
+ console.log();
44
+ }
45
+
46
+ /**
47
+ * Remove GitHub Token
48
+ */
49
+ export function removeGitHubToken() {
50
+ removeToken();
51
+ console.log(chalk.green('\nāœ“ GitHub Token removed.'));
52
+ console.log();
53
+ }
@@ -47,10 +47,22 @@ export async function listRemoteSkills(options) {
47
47
  const infoSpinner = ora('Fetching skill info...').start();
48
48
  const skillsWithInfo = await Promise.all(
49
49
  skills.map(async skill => {
50
- // Pass basePath to fetch correct SKILL.md location
51
- const info = await fetchSkillInfo(skill.name, skill.owner, skill.repo, skill.basePath);
52
- const description = info ? info.description : null;
53
- const remoteVersion = info ? info.version : '0.0.0';
50
+ let description = skill.description;
51
+ let remoteVersion = skill.version || '0.0.0';
52
+
53
+ // Only fetch info if not already fetched (REST fallback)
54
+ if (description === undefined || description === null) {
55
+ // Pass basePath and branch to fetch correct SKILL.md location optimized
56
+ const info = await fetchSkillInfo(
57
+ skill.name,
58
+ skill.owner,
59
+ skill.repo,
60
+ skill.basePath,
61
+ skill.branch
62
+ );
63
+ description = info ? info.description : null;
64
+ remoteVersion = info ? info.version : '0.0.0';
65
+ }
54
66
 
55
67
  const installed = skillExists(skill.name);
56
68
  let updateAvailable = false;
package/src/index.js CHANGED
@@ -10,6 +10,7 @@ import { removeSkill } from './commands/remove.js';
10
10
  import { updateCli } from './commands/update.js';
11
11
  import { upgradeSkills } from './commands/upgrade.js';
12
12
  import { listSources, addNewSource, removeExistingSource, setDefault } from './commands/source.js';
13
+ import { listConfig, setGitHubToken, removeGitHubToken } from './commands/config.js';
13
14
  import { checkForUpdates } from './utils/updateNotifier.js';
14
15
  import { setupCompletion } from './utils/completion.js';
15
16
 
@@ -110,4 +111,19 @@ sourceCmd
110
111
 
111
112
  sourceCmd.command('default <name>').description('Set default source').action(setDefault);
112
113
 
114
+ // Config management commands
115
+ const configCmd = program.command('config').description('Manage CLI configuration');
116
+
117
+ configCmd.command('list').alias('ls').description('List current configuration').action(listConfig);
118
+
119
+ configCmd
120
+ .command('set-token <token>')
121
+ .description('Set GitHub Personal Access Token')
122
+ .action(setGitHubToken);
123
+
124
+ configCmd
125
+ .command('remove-token')
126
+ .description('Remove GitHub Personal Access Token')
127
+ .action(removeGitHubToken);
128
+
113
129
  program.parse();
@@ -163,6 +163,32 @@ export function setDefaultSource(name) {
163
163
  return config.sources;
164
164
  }
165
165
 
166
+ /**
167
+ * Set GitHub Token
168
+ */
169
+ export function setToken(token) {
170
+ const config = loadConfig();
171
+ config.githubToken = token;
172
+ saveConfig(config);
173
+ }
174
+
175
+ /**
176
+ * Get GitHub Token
177
+ */
178
+ export function getToken() {
179
+ const config = loadConfig();
180
+ return config.githubToken;
181
+ }
182
+
183
+ /**
184
+ * Remove GitHub Token
185
+ */
186
+ export function removeToken() {
187
+ const config = loadConfig();
188
+ delete config.githubToken;
189
+ saveConfig(config);
190
+ }
191
+
166
192
  /**
167
193
  * Get config file path (for display)
168
194
  */
@@ -1,4 +1,4 @@
1
- import { getSources } from './configManager.js';
1
+ import { getSources, getToken } from './configManager.js';
2
2
 
3
3
  const GITHUB_API = 'https://api.github.com';
4
4
 
@@ -7,21 +7,126 @@ function getHeaders() {
7
7
  Accept: 'application/vnd.github.v3+json',
8
8
  'User-Agent': 'antikit-cli'
9
9
  };
10
- const token = process.env.ANTIKIT_GITHUB_TOKEN || process.env.GITHUB_TOKEN;
10
+ const token = getToken() || process.env.ANTIKIT_GITHUB_TOKEN || process.env.GITHUB_TOKEN;
11
11
  if (token) {
12
12
  headers.Authorization = `token ${token}`;
13
13
  }
14
14
  return headers;
15
15
  }
16
16
 
17
+ /**
18
+ * Fetch skills using GraphQL (Optimized: 1 request per source)
19
+ */
20
+ async function fetchSkillsViaGraphQL(source, token) {
21
+ const query = `
22
+ query ($owner: String!, $repo: String!, $expression: String!) {
23
+ repository(owner: $owner, name: $repo) {
24
+ object(expression: $expression) {
25
+ ... on Tree {
26
+ entries {
27
+ name
28
+ type
29
+ object {
30
+ ... on Tree {
31
+ file: entries(name: "SKILL.md") {
32
+ object {
33
+ ... on Blob {
34
+ text
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ `;
46
+
47
+ const branch = source.branch || 'main'; // This logic might need verifying branch exists, but usually main/master
48
+ // Correct expression for path. If path is provided, it's "branch:path", else just "branch:"
49
+ const expression = source.path ? `${branch}:${source.path}` : `${branch}:`;
50
+
51
+ try {
52
+ const response = await fetch('https://api.github.com/graphql', {
53
+ method: 'POST',
54
+ headers: {
55
+ Authorization: `token ${token}`,
56
+ 'User-Agent': 'antikit-cli'
57
+ },
58
+ body: JSON.stringify({
59
+ query,
60
+ variables: {
61
+ owner: source.owner,
62
+ repo: source.repo,
63
+ expression
64
+ }
65
+ })
66
+ });
67
+
68
+ const { data, errors } = await response.json();
69
+
70
+ if (errors || !data || !data.repository || !data.repository.object) {
71
+ return null; // Fallback to REST
72
+ }
73
+
74
+ const entries = data.repository.object.entries || [];
75
+
76
+ return entries
77
+ .filter(item => item.type === 'tree' && !item.name.startsWith('.'))
78
+ .map(item => {
79
+ let description = null;
80
+ let version = '0.0.0';
81
+
82
+ // Attempt to parse SKILL.md content if it exists
83
+ const skillFile = item.object.file && item.object.file[0];
84
+ if (skillFile && skillFile.object && skillFile.object.text) {
85
+ const content = skillFile.object.text;
86
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
87
+ if (match) {
88
+ const frontmatter = match[1];
89
+ const descMatch = frontmatter.match(/description:\s*(.+)/);
90
+ const verMatch = frontmatter.match(/version:\s*(.+)/);
91
+ if (descMatch) description = descMatch[1].trim();
92
+ if (verMatch) version = verMatch[1].trim();
93
+ }
94
+ }
95
+
96
+ return {
97
+ name: item.name,
98
+ url: `https://github.com/${source.owner}/${source.repo}/tree/${branch}/${source.path ? source.path + '/' : ''}${item.name}`,
99
+ path: source.path ? `${source.path}/${item.name}` : item.name,
100
+ source: source.name,
101
+ owner: source.owner,
102
+ repo: source.repo,
103
+ basePath: source.path,
104
+ description, // Pre-fetched!
105
+ version // Pre-fetched!
106
+ };
107
+ });
108
+ } catch (e) {
109
+ return null; // Fallback
110
+ }
111
+ }
112
+
17
113
  /**
18
114
  * Fetch list of skills from a specific source
19
115
  */
20
116
  async function fetchSkillsFromSource(source) {
117
+ // Try GraphQL first if token exists (Much faster)
118
+ const token = getToken() || process.env.ANTIKIT_GITHUB_TOKEN || process.env.GITHUB_TOKEN;
119
+ if (token) {
120
+ const gqlResult = await fetchSkillsViaGraphQL(source, token);
121
+ if (gqlResult) return gqlResult;
122
+ }
123
+
124
+ // Fallback to REST API
21
125
  let url = `${GITHUB_API}/repos/${source.owner}/${source.repo}/contents`;
22
126
  if (source.path) {
23
127
  url += `/${source.path}`;
24
128
  }
129
+ // ... rest of function
25
130
 
26
131
  const response = await fetch(url, {
27
132
  headers: getHeaders()
@@ -61,36 +166,19 @@ async function fetchSkillsFromSource(source) {
61
166
  source: source.name,
62
167
  owner: source.owner,
63
168
  repo: source.repo,
169
+ branch: source.branch || 'main',
64
170
  basePath: source.path // Keep track of base path
65
171
  }));
66
172
 
67
173
  return skills;
68
174
  }
69
175
 
70
- /**
71
- * Fetch list of skills from all configured sources
72
- */
73
- export async function fetchRemoteSkills(sourceName = null) {
74
- const sources = getSources();
75
-
76
- // Filter by source name if provided
77
- const targetSources = sourceName ? sources.filter(s => s.name === sourceName) : sources;
78
-
79
- if (targetSources.length === 0) {
80
- throw new Error(`Source "${sourceName}" not found.`);
81
- }
82
-
83
- // Fetch from all sources in parallel
84
- const results = await Promise.all(targetSources.map(source => fetchSkillsFromSource(source)));
85
-
86
- // Flatten and return all skills
87
- return results.flat();
88
- }
176
+ // ... (fetchRemoteSkills remains same)
89
177
 
90
178
  /**
91
179
  * Fetch SKILL.md content for a specific skill
92
180
  */
93
- export async function fetchSkillInfo(skillName, owner, repo, path = null) {
181
+ export async function fetchSkillInfo(skillName, owner, repo, path = null, branch = null) {
94
182
  // If owner/repo not provided, search in all sources
95
183
  if (!owner || !repo) {
96
184
  const skills = await fetchRemoteSkills();
@@ -99,24 +187,46 @@ export async function fetchSkillInfo(skillName, owner, repo, path = null) {
99
187
  owner = skill.owner;
100
188
  repo = skill.repo;
101
189
  path = skill.basePath;
190
+ branch = skill.branch;
102
191
  }
103
192
 
104
- let url = `${GITHUB_API}/repos/${owner}/${repo}/contents`;
105
- if (path) {
106
- url += `/${path}`;
193
+ let content = null;
194
+
195
+ // Optimized: Use Raw URL if branch is known (avoids API rate limit)
196
+ if (branch) {
197
+ let rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
198
+ if (path) rawUrl += `/${path}`;
199
+ rawUrl += `/${skillName}/SKILL.md`;
200
+
201
+ try {
202
+ const res = await fetch(rawUrl);
203
+ if (res.ok) {
204
+ content = await res.text();
205
+ }
206
+ } catch (e) {
207
+ // Ignore fetch error, fallback to API
208
+ }
107
209
  }
108
- url += `/${skillName}/SKILL.md`;
109
210
 
110
- const response = await fetch(url, {
111
- headers: getHeaders()
112
- });
211
+ // Fallback: Use API (Counts against rate limit, but works if branch is wrong/private repo needs Auth)
212
+ if (!content) {
213
+ let url = `${GITHUB_API}/repos/${owner}/${repo}/contents`;
214
+ if (path) {
215
+ url += `/${path}`;
216
+ }
217
+ url += `/${skillName}/SKILL.md`;
113
218
 
114
- if (!response.ok) {
115
- return null;
116
- }
219
+ const response = await fetch(url, {
220
+ headers: getHeaders()
221
+ });
117
222
 
118
- const data = await response.json();
119
- const content = Buffer.from(data.content, 'base64').toString('utf-8');
223
+ if (!response.ok) {
224
+ return null;
225
+ }
226
+
227
+ const data = await response.json();
228
+ content = Buffer.from(data.content, 'base64').toString('utf-8');
229
+ }
120
230
 
121
231
  // Extract info from YAML frontmatter
122
232
  const match = content.match(/^---\n([\s\S]*?)\n---/);