antikit 1.12.5 → 1.12.7
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/package.json +7 -3
- package/src/commands/install.js +12 -14
- package/src/commands/list.js +57 -49
- package/src/utils/configManager.js +8 -7
- package/src/utils/constants.js +36 -0
- package/src/utils/errors.js +107 -0
- package/src/utils/github.js +22 -16
- package/src/utils/local.js +11 -2
- package/src/utils/logger.js +61 -0
- package/src/utils/updateNotifier.js +47 -12
- package/src/utils/version.js +48 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "antikit",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.7",
|
|
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",
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"antikit": "src/index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest",
|
|
13
|
+
"test:coverage": "vitest run --coverage",
|
|
12
14
|
"release": "commit-and-tag-version",
|
|
13
15
|
"prepare": "husky",
|
|
14
16
|
"format": "prettier --write ."
|
|
@@ -57,9 +59,11 @@
|
|
|
57
59
|
"devDependencies": {
|
|
58
60
|
"@commitlint/cli": "^20.3.1",
|
|
59
61
|
"@commitlint/config-conventional": "^20.3.1",
|
|
62
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
60
63
|
"commit-and-tag-version": "^12.6.1",
|
|
61
64
|
"husky": "^9.1.7",
|
|
62
65
|
"lint-staged": "^16.2.7",
|
|
63
|
-
"prettier": "^3.7.4"
|
|
66
|
+
"prettier": "^3.7.4",
|
|
67
|
+
"vitest": "^3.0.0"
|
|
64
68
|
}
|
|
65
69
|
}
|
package/src/commands/install.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
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, writeFileSync } from 'fs';
|
|
4
|
+
import { existsSync, mkdirSync, cpSync, rmSync, writeFileSync, readFileSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { homedir } from 'os';
|
|
7
7
|
import { getOrCreateSkillsDir, skillExists } from '../utils/local.js';
|
|
8
8
|
import { fetchRemoteSkills, getSkillCloneUrl } from '../utils/github.js';
|
|
9
|
+
import { DEFAULT_VERSION, parseVersionFromContent } from '../utils/version.js';
|
|
10
|
+
import { METADATA_FILE, SKILL_MD } from '../utils/constants.js';
|
|
11
|
+
import { debug } from '../utils/logger.js';
|
|
12
|
+
import { AntikitError, ErrorCodes } from '../utils/errors.js';
|
|
9
13
|
|
|
10
14
|
const CACHE_DIR = join(homedir(), '.antikit');
|
|
11
15
|
|
|
@@ -81,10 +85,8 @@ export async function installSkill(skillName, options = {}) {
|
|
|
81
85
|
|
|
82
86
|
// --- Check dependencies ---
|
|
83
87
|
try {
|
|
84
|
-
const mdPath = join(sourcePath,
|
|
88
|
+
const mdPath = join(sourcePath, SKILL_MD);
|
|
85
89
|
if (existsSync(mdPath)) {
|
|
86
|
-
// Import locally to avoid cluttering top imports if possible, or just use fs
|
|
87
|
-
const { readFileSync } = await import('fs');
|
|
88
90
|
const content = readFileSync(mdPath, 'utf-8');
|
|
89
91
|
|
|
90
92
|
// Parse frontmatter dependencies
|
|
@@ -136,7 +138,7 @@ export async function installSkill(skillName, options = {}) {
|
|
|
136
138
|
}
|
|
137
139
|
} catch (e) {
|
|
138
140
|
// Dep check failed, but continue installing main skill
|
|
139
|
-
|
|
141
|
+
debug('Dependency check failed:', e.message);
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
if (options.force && existsSync(destPath)) {
|
|
@@ -147,15 +149,11 @@ export async function installSkill(skillName, options = {}) {
|
|
|
147
149
|
|
|
148
150
|
// Save skill metadata for future upgrades
|
|
149
151
|
try {
|
|
150
|
-
let version =
|
|
151
|
-
const mdPath = join(destPath,
|
|
152
|
+
let version = DEFAULT_VERSION;
|
|
153
|
+
const mdPath = join(destPath, SKILL_MD);
|
|
152
154
|
if (existsSync(mdPath)) {
|
|
153
|
-
// We likely already imported readFileSync if inside try block,
|
|
154
|
-
// but for safety use dynamic import or assume imported
|
|
155
|
-
const { readFileSync } = await import('fs');
|
|
156
155
|
const content = readFileSync(mdPath, 'utf-8');
|
|
157
|
-
|
|
158
|
-
if (vMatch) version = vMatch[1].trim();
|
|
156
|
+
version = parseVersionFromContent(content);
|
|
159
157
|
}
|
|
160
158
|
|
|
161
159
|
const metadata = {
|
|
@@ -168,9 +166,9 @@ export async function installSkill(skillName, options = {}) {
|
|
|
168
166
|
version,
|
|
169
167
|
installedAt: Date.now()
|
|
170
168
|
};
|
|
171
|
-
writeFileSync(join(destPath,
|
|
169
|
+
writeFileSync(join(destPath, METADATA_FILE), JSON.stringify(metadata, null, 2));
|
|
172
170
|
} catch (e) {
|
|
173
|
-
|
|
171
|
+
debug('Failed to write metadata:', e.message);
|
|
174
172
|
}
|
|
175
173
|
|
|
176
174
|
// Cleanup temp
|
package/src/commands/list.js
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { checkbox, confirm, Separator } from '@inquirer/prompts';
|
|
4
|
+
import Table from 'cli-table3';
|
|
4
5
|
import { fetchRemoteSkills, fetchSkillInfo } from '../utils/github.js';
|
|
5
6
|
import { skillExists, getOrCreateSkillsDir } from '../utils/local.js';
|
|
6
7
|
import { installSkill } from './install.js';
|
|
7
8
|
import { existsSync, readFileSync } from 'fs';
|
|
8
9
|
import { join } from 'path';
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
}
|
|
10
|
+
import { compareVersions, DEFAULT_VERSION } from '../utils/version.js';
|
|
11
|
+
import { METADATA_FILE, OFFICIAL_SOURCE } from '../utils/constants.js';
|
|
20
12
|
|
|
21
13
|
export async function listRemoteSkills(options) {
|
|
22
14
|
const sourceName = options.source || null;
|
|
@@ -48,7 +40,7 @@ export async function listRemoteSkills(options) {
|
|
|
48
40
|
const skillsWithInfo = await Promise.all(
|
|
49
41
|
skills.map(async skill => {
|
|
50
42
|
let description = skill.description;
|
|
51
|
-
let remoteVersion = skill.version ||
|
|
43
|
+
let remoteVersion = skill.version || DEFAULT_VERSION;
|
|
52
44
|
|
|
53
45
|
// Only fetch info if not already fetched (REST fallback)
|
|
54
46
|
if (description === undefined || description === null) {
|
|
@@ -61,19 +53,19 @@ export async function listRemoteSkills(options) {
|
|
|
61
53
|
skill.branch
|
|
62
54
|
);
|
|
63
55
|
description = info ? info.description : null;
|
|
64
|
-
remoteVersion = info ? info.version :
|
|
56
|
+
remoteVersion = info ? info.version : DEFAULT_VERSION;
|
|
65
57
|
}
|
|
66
58
|
|
|
67
59
|
const installed = skillExists(skill.name);
|
|
68
60
|
let updateAvailable = false;
|
|
69
|
-
let localVersion =
|
|
61
|
+
let localVersion = DEFAULT_VERSION;
|
|
70
62
|
|
|
71
63
|
if (installed) {
|
|
72
64
|
try {
|
|
73
|
-
const metaPath = join(skillsDir, skill.name,
|
|
65
|
+
const metaPath = join(skillsDir, skill.name, METADATA_FILE);
|
|
74
66
|
if (existsSync(metaPath)) {
|
|
75
67
|
const meta = JSON.parse(readFileSync(metaPath, 'utf8'));
|
|
76
|
-
localVersion = meta.version ||
|
|
68
|
+
localVersion = meta.version || DEFAULT_VERSION;
|
|
77
69
|
updateAvailable = compareVersions(remoteVersion, localVersion) > 0;
|
|
78
70
|
}
|
|
79
71
|
} catch {}
|
|
@@ -100,50 +92,66 @@ export async function listRemoteSkills(options) {
|
|
|
100
92
|
}
|
|
101
93
|
|
|
102
94
|
function displaySkillsList(skills) {
|
|
103
|
-
console.log(chalk.bold('
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (b === 'official') return 1;
|
|
117
|
-
return a.localeCompare(b);
|
|
95
|
+
console.log(chalk.bold('\nAvailable Skills:'));
|
|
96
|
+
|
|
97
|
+
const table = new Table({
|
|
98
|
+
head: [
|
|
99
|
+
chalk.cyan('Source'),
|
|
100
|
+
chalk.cyan('Skill Name'),
|
|
101
|
+
chalk.cyan('Version'),
|
|
102
|
+
chalk.cyan('Status'),
|
|
103
|
+
chalk.cyan('Description')
|
|
104
|
+
],
|
|
105
|
+
colWidths: [15, 25, 12, 12, Math.max(20, (process.stdout.columns || 80) - 74)],
|
|
106
|
+
wordWrap: true,
|
|
107
|
+
style: { head: [], border: [] }
|
|
118
108
|
});
|
|
119
109
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
110
|
+
// Sort: Official first, then Source, then Name
|
|
111
|
+
skills.sort((a, b) => {
|
|
112
|
+
if (a.source === OFFICIAL_SOURCE && b.source !== OFFICIAL_SOURCE) return -1;
|
|
113
|
+
if (b.source === OFFICIAL_SOURCE && a.source !== OFFICIAL_SOURCE) return 1;
|
|
114
|
+
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
115
|
+
return a.name.localeCompare(b.name);
|
|
116
|
+
});
|
|
128
117
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
118
|
+
skills.forEach(skill => {
|
|
119
|
+
let status = '';
|
|
120
|
+
if (skill.installed) {
|
|
121
|
+
status = skill.updateAvailable ? chalk.yellow('Update') : chalk.green('Installed');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let versionDisplay = skill.remoteVersion || DEFAULT_VERSION;
|
|
125
|
+
if (skill.installed && skill.localVersion) {
|
|
126
|
+
if (skill.updateAvailable) {
|
|
127
|
+
versionDisplay = `${chalk.dim(skill.localVersion)}→${chalk.yellow(skill.remoteVersion)}`;
|
|
128
|
+
} else {
|
|
129
|
+
versionDisplay = skill.localVersion;
|
|
134
130
|
}
|
|
135
131
|
}
|
|
136
|
-
}
|
|
137
132
|
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
table.push([
|
|
134
|
+
chalk.magenta(skill.source),
|
|
135
|
+
chalk.bold(skill.name),
|
|
136
|
+
versionDisplay,
|
|
137
|
+
status,
|
|
138
|
+
skill.description || chalk.dim('')
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
console.log(table.toString());
|
|
143
|
+
console.log(
|
|
144
|
+
chalk.dim(
|
|
145
|
+
`\nUse ${chalk.white('antikit list -i')} to select and install skills interactively.\n`
|
|
146
|
+
)
|
|
147
|
+
);
|
|
140
148
|
}
|
|
141
149
|
|
|
142
150
|
async function interactiveInstall(skills) {
|
|
143
151
|
// Sort skills by Source (Official first) then Name
|
|
144
152
|
skills.sort((a, b) => {
|
|
145
|
-
if (a.source ===
|
|
146
|
-
if (b.source ===
|
|
153
|
+
if (a.source === OFFICIAL_SOURCE && b.source !== OFFICIAL_SOURCE) return -1;
|
|
154
|
+
if (b.source === OFFICIAL_SOURCE && a.source !== OFFICIAL_SOURCE) return 1;
|
|
147
155
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
148
156
|
return a.name.localeCompare(b.name);
|
|
149
157
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
3
|
import { join } from 'path';
|
|
4
|
+
import { OFFICIAL_SOURCE, DEFAULT_BRANCH } from './constants.js';
|
|
4
5
|
|
|
5
6
|
const CONFIG_DIR = join(homedir(), '.antikit');
|
|
6
7
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
@@ -8,24 +9,24 @@ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
|
8
9
|
const DEFAULT_CONFIG = {
|
|
9
10
|
sources: [
|
|
10
11
|
{
|
|
11
|
-
name:
|
|
12
|
+
name: OFFICIAL_SOURCE,
|
|
12
13
|
owner: 'vunamhung',
|
|
13
14
|
repo: 'antiskills',
|
|
14
|
-
branch:
|
|
15
|
+
branch: DEFAULT_BRANCH,
|
|
15
16
|
default: true
|
|
16
17
|
},
|
|
17
18
|
{
|
|
18
19
|
name: 'claudekit',
|
|
19
20
|
owner: 'mrgoonie',
|
|
20
21
|
repo: 'claudekit-skills',
|
|
21
|
-
branch:
|
|
22
|
+
branch: DEFAULT_BRANCH,
|
|
22
23
|
path: '.claude/skills'
|
|
23
24
|
},
|
|
24
25
|
{
|
|
25
26
|
name: 'ui-ux-pro',
|
|
26
27
|
owner: 'nextlevelbuilder',
|
|
27
28
|
repo: 'ui-ux-pro-max-skill',
|
|
28
|
-
branch:
|
|
29
|
+
branch: DEFAULT_BRANCH,
|
|
29
30
|
path: '.claude/skills'
|
|
30
31
|
}
|
|
31
32
|
]
|
|
@@ -76,8 +77,8 @@ export function getSources() {
|
|
|
76
77
|
|
|
77
78
|
// Enforce 'official' source always at the top
|
|
78
79
|
return sources.sort((a, b) => {
|
|
79
|
-
if (a.name ===
|
|
80
|
-
if (b.name ===
|
|
80
|
+
if (a.name === OFFICIAL_SOURCE) return -1;
|
|
81
|
+
if (b.name === OFFICIAL_SOURCE) return 1;
|
|
81
82
|
return 0;
|
|
82
83
|
});
|
|
83
84
|
}
|
|
@@ -85,7 +86,7 @@ export function getSources() {
|
|
|
85
86
|
/**
|
|
86
87
|
* Add a new source
|
|
87
88
|
*/
|
|
88
|
-
export function addSource(name, owner, repo, branch =
|
|
89
|
+
export function addSource(name, owner, repo, branch = DEFAULT_BRANCH, path = null) {
|
|
89
90
|
const config = loadConfig();
|
|
90
91
|
|
|
91
92
|
// Check if source with same name exists
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for antikit
|
|
3
|
+
* Eliminates magic strings throughout the codebase
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Source names
|
|
7
|
+
export const OFFICIAL_SOURCE = 'official';
|
|
8
|
+
|
|
9
|
+
// File names
|
|
10
|
+
export const SKILL_MD = 'SKILL.md';
|
|
11
|
+
export const METADATA_FILE = '.antikit-skill.json';
|
|
12
|
+
|
|
13
|
+
// Directory names
|
|
14
|
+
export const SKILLS_DIR = '.agent/skills';
|
|
15
|
+
|
|
16
|
+
// Cache settings
|
|
17
|
+
export const CACHE_TTL = 1000 * 60 * 60; // 1 hour
|
|
18
|
+
export const UPDATE_CHECK_INTERVAL = 1000 * 60 * 60 * 6; // 6 hours
|
|
19
|
+
|
|
20
|
+
// API endpoints
|
|
21
|
+
export const GITHUB_API = 'https://api.github.com';
|
|
22
|
+
export const GITHUB_GRAPHQL = 'https://api.github.com/graphql';
|
|
23
|
+
export const GITHUB_RAW = 'https://raw.githubusercontent.com';
|
|
24
|
+
|
|
25
|
+
// Default branch
|
|
26
|
+
export const DEFAULT_BRANCH = 'main';
|
|
27
|
+
|
|
28
|
+
// Status indicators
|
|
29
|
+
export const STATUS = {
|
|
30
|
+
INSTALLED: 'installed',
|
|
31
|
+
UPDATE_AVAILABLE: 'update',
|
|
32
|
+
NOT_INSTALLED: 'not_installed'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Max limits
|
|
36
|
+
export const MAX_DIRECTORY_DEPTH = 50;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error classes for antikit
|
|
3
|
+
* Provides structured error handling with error codes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base error class for antikit
|
|
8
|
+
*/
|
|
9
|
+
export class AntikitError extends Error {
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} message - Error message
|
|
12
|
+
* @param {string} code - Error code (e.g., 'SKILL_NOT_FOUND')
|
|
13
|
+
* @param {Object} [context] - Additional context data
|
|
14
|
+
*/
|
|
15
|
+
constructor(message, code, context = {}) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'AntikitError';
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.context = context;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Error codes enum
|
|
25
|
+
*/
|
|
26
|
+
export const ErrorCodes = {
|
|
27
|
+
// Skill errors
|
|
28
|
+
SKILL_NOT_FOUND: 'SKILL_NOT_FOUND',
|
|
29
|
+
SKILL_ALREADY_EXISTS: 'SKILL_ALREADY_EXISTS',
|
|
30
|
+
SKILL_INVALID_METADATA: 'SKILL_INVALID_METADATA',
|
|
31
|
+
SKILL_MISSING_METADATA: 'SKILL_MISSING_METADATA',
|
|
32
|
+
|
|
33
|
+
// Source errors
|
|
34
|
+
SOURCE_NOT_FOUND: 'SOURCE_NOT_FOUND',
|
|
35
|
+
SOURCE_ALREADY_EXISTS: 'SOURCE_ALREADY_EXISTS',
|
|
36
|
+
SOURCE_CANNOT_REMOVE_DEFAULT: 'SOURCE_CANNOT_REMOVE_DEFAULT',
|
|
37
|
+
|
|
38
|
+
// GitHub errors
|
|
39
|
+
GITHUB_RATE_LIMIT: 'GITHUB_RATE_LIMIT',
|
|
40
|
+
GITHUB_NOT_FOUND: 'GITHUB_NOT_FOUND',
|
|
41
|
+
GITHUB_AUTH_FAILED: 'GITHUB_AUTH_FAILED',
|
|
42
|
+
GITHUB_NETWORK_ERROR: 'GITHUB_NETWORK_ERROR',
|
|
43
|
+
|
|
44
|
+
// Config errors
|
|
45
|
+
CONFIG_INVALID: 'CONFIG_INVALID',
|
|
46
|
+
CONFIG_NOT_FOUND: 'CONFIG_NOT_FOUND',
|
|
47
|
+
|
|
48
|
+
// Git errors
|
|
49
|
+
GIT_CLONE_FAILED: 'GIT_CLONE_FAILED',
|
|
50
|
+
GIT_NOT_INSTALLED: 'GIT_NOT_INSTALLED',
|
|
51
|
+
|
|
52
|
+
// General errors
|
|
53
|
+
INVALID_INPUT: 'INVALID_INPUT',
|
|
54
|
+
DIRECTORY_NOT_FOUND: 'DIRECTORY_NOT_FOUND',
|
|
55
|
+
PERMISSION_DENIED: 'PERMISSION_DENIED',
|
|
56
|
+
UNKNOWN: 'UNKNOWN'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a skill not found error
|
|
61
|
+
*/
|
|
62
|
+
export function skillNotFoundError(skillName, source = null) {
|
|
63
|
+
return new AntikitError(
|
|
64
|
+
`Skill "${skillName}" not found${source ? ` in source "${source}"` : ''}`,
|
|
65
|
+
ErrorCodes.SKILL_NOT_FOUND,
|
|
66
|
+
{ skillName, source }
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create a rate limit error
|
|
72
|
+
*/
|
|
73
|
+
export function rateLimitError() {
|
|
74
|
+
return new AntikitError(
|
|
75
|
+
'GitHub API rate limit exceeded. Configure a token to increase limits.',
|
|
76
|
+
ErrorCodes.GITHUB_RATE_LIMIT
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a source not found error
|
|
82
|
+
*/
|
|
83
|
+
export function sourceNotFoundError(sourceName) {
|
|
84
|
+
return new AntikitError(`Source "${sourceName}" not found.`, ErrorCodes.SOURCE_NOT_FOUND, {
|
|
85
|
+
sourceName
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Wrap an unknown error with context
|
|
91
|
+
*/
|
|
92
|
+
export function wrapError(error, context = {}) {
|
|
93
|
+
if (error instanceof AntikitError) {
|
|
94
|
+
return error;
|
|
95
|
+
}
|
|
96
|
+
return new AntikitError(error.message || 'Unknown error occurred', ErrorCodes.UNKNOWN, {
|
|
97
|
+
...context,
|
|
98
|
+
originalError: error
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if error is a specific type
|
|
104
|
+
*/
|
|
105
|
+
export function isErrorCode(error, code) {
|
|
106
|
+
return error instanceof AntikitError && error.code === code;
|
|
107
|
+
}
|
package/src/utils/github.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { getSources, getToken } from './configManager.js';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { debug } from './logger.js';
|
|
4
|
+
import { GITHUB_API, GITHUB_GRAPHQL, GITHUB_RAW, DEFAULT_BRANCH } from './constants.js';
|
|
5
|
+
import { DEFAULT_VERSION } from './version.js';
|
|
5
6
|
|
|
6
7
|
// Global flag to prevent duplicate rate limit logs
|
|
7
8
|
let hasLoggedRateLimit = false;
|
|
@@ -64,11 +65,11 @@ async function fetchSkillsViaGraphQL(source, token) {
|
|
|
64
65
|
}
|
|
65
66
|
`;
|
|
66
67
|
|
|
67
|
-
const branch = source.branch ||
|
|
68
|
+
const branch = source.branch || DEFAULT_BRANCH;
|
|
68
69
|
const expression = source.path ? `${branch}:${source.path}` : `${branch}:`;
|
|
69
70
|
|
|
70
71
|
try {
|
|
71
|
-
const response = await fetch(
|
|
72
|
+
const response = await fetch(GITHUB_GRAPHQL, {
|
|
72
73
|
method: 'POST',
|
|
73
74
|
headers: {
|
|
74
75
|
Authorization: `token ${token}`,
|
|
@@ -96,7 +97,7 @@ async function fetchSkillsViaGraphQL(source, token) {
|
|
|
96
97
|
.filter(item => item.type === 'tree' && !item.name.startsWith('.'))
|
|
97
98
|
.map(item => {
|
|
98
99
|
let description = null;
|
|
99
|
-
let version =
|
|
100
|
+
let version = DEFAULT_VERSION;
|
|
100
101
|
|
|
101
102
|
const skillFile = item.object.file && item.object.file[0];
|
|
102
103
|
if (skillFile && skillFile.object && skillFile.object.text) {
|
|
@@ -118,14 +119,15 @@ async function fetchSkillsViaGraphQL(source, token) {
|
|
|
118
119
|
source: source.name,
|
|
119
120
|
owner: source.owner,
|
|
120
121
|
repo: source.repo,
|
|
121
|
-
branch: source.branch ||
|
|
122
|
+
branch: source.branch || DEFAULT_BRANCH,
|
|
122
123
|
basePath: source.path,
|
|
123
124
|
description,
|
|
124
125
|
version
|
|
125
126
|
};
|
|
126
127
|
});
|
|
127
128
|
} catch (e) {
|
|
128
|
-
|
|
129
|
+
debug('GraphQL fetch failed:', e.message);
|
|
130
|
+
return null; // Fallback to REST
|
|
129
131
|
}
|
|
130
132
|
}
|
|
131
133
|
|
|
@@ -153,8 +155,8 @@ async function fetchSkillsFromSource(source) {
|
|
|
153
155
|
if (!response.ok) {
|
|
154
156
|
const data = await response.json().catch(() => ({}));
|
|
155
157
|
|
|
156
|
-
// Check for rate limit
|
|
157
|
-
if (response.status === 403 && data.message
|
|
158
|
+
// Check for rate limit (with null-safe access)
|
|
159
|
+
if (response.status === 403 && data.message?.includes('rate limit')) {
|
|
158
160
|
logRateLimitError();
|
|
159
161
|
}
|
|
160
162
|
|
|
@@ -182,7 +184,7 @@ async function fetchSkillsFromSource(source) {
|
|
|
182
184
|
source: source.name,
|
|
183
185
|
owner: source.owner,
|
|
184
186
|
repo: source.repo,
|
|
185
|
-
branch: source.branch ||
|
|
187
|
+
branch: source.branch || DEFAULT_BRANCH,
|
|
186
188
|
basePath: source.path
|
|
187
189
|
}));
|
|
188
190
|
|
|
@@ -225,7 +227,7 @@ export async function fetchSkillInfo(skillName, owner, repo, path = null, branch
|
|
|
225
227
|
|
|
226
228
|
// Optimized: Use Raw URL if branch is known
|
|
227
229
|
if (branch) {
|
|
228
|
-
let rawUrl =
|
|
230
|
+
let rawUrl = `${GITHUB_RAW}/${owner}/${repo}/${branch}`;
|
|
229
231
|
if (path) rawUrl += `/${path}`;
|
|
230
232
|
rawUrl += `/${skillName}/SKILL.md`;
|
|
231
233
|
|
|
@@ -236,7 +238,9 @@ export async function fetchSkillInfo(skillName, owner, repo, path = null, branch
|
|
|
236
238
|
if (res.ok) {
|
|
237
239
|
content = await res.text();
|
|
238
240
|
}
|
|
239
|
-
} catch (e) {
|
|
241
|
+
} catch (e) {
|
|
242
|
+
debug('Raw URL fetch failed:', e.message);
|
|
243
|
+
}
|
|
240
244
|
}
|
|
241
245
|
|
|
242
246
|
// Fallback: Use API
|
|
@@ -258,8 +262,10 @@ export async function fetchSkillInfo(skillName, owner, repo, path = null, branch
|
|
|
258
262
|
// But simpler just to ignore or log if we strictly check msg
|
|
259
263
|
try {
|
|
260
264
|
const d = await response.json();
|
|
261
|
-
if (d.message
|
|
262
|
-
} catch {
|
|
265
|
+
if (d.message?.includes('rate limit')) logRateLimitError();
|
|
266
|
+
} catch (e) {
|
|
267
|
+
debug('Rate limit check failed:', e.message);
|
|
268
|
+
}
|
|
263
269
|
}
|
|
264
270
|
return null;
|
|
265
271
|
}
|
|
@@ -276,12 +282,12 @@ export async function fetchSkillInfo(skillName, owner, repo, path = null, branch
|
|
|
276
282
|
|
|
277
283
|
return {
|
|
278
284
|
description: descMatch ? descMatch[1].trim() : null,
|
|
279
|
-
version: versionMatch ? versionMatch[1].trim() :
|
|
285
|
+
version: versionMatch ? versionMatch[1].trim() : DEFAULT_VERSION,
|
|
280
286
|
content // Return raw content
|
|
281
287
|
};
|
|
282
288
|
}
|
|
283
289
|
|
|
284
|
-
return { description: null, version:
|
|
290
|
+
return { description: null, version: DEFAULT_VERSION, content };
|
|
285
291
|
}
|
|
286
292
|
|
|
287
293
|
/**
|
package/src/utils/local.js
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, rmSync, mkdirSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { CONFIG } from '../config.js';
|
|
4
|
+
import { MAX_DIRECTORY_DEPTH, SKILL_MD } from './constants.js';
|
|
5
|
+
import { debug } from './logger.js';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Find local skills directory by traversing up from cwd
|
|
9
|
+
* Limited to MAX_DIRECTORY_DEPTH to prevent potential infinite loops
|
|
7
10
|
*/
|
|
8
11
|
export function findLocalSkillsDir() {
|
|
9
12
|
let dir = process.cwd();
|
|
13
|
+
let depth = 0;
|
|
10
14
|
|
|
11
|
-
while (dir !== '/') {
|
|
15
|
+
while (dir !== '/' && depth < MAX_DIRECTORY_DEPTH) {
|
|
12
16
|
const skillsPath = join(dir, CONFIG.LOCAL_SKILLS_DIR);
|
|
13
17
|
if (existsSync(skillsPath)) {
|
|
14
18
|
return skillsPath;
|
|
15
19
|
}
|
|
16
20
|
dir = join(dir, '..');
|
|
21
|
+
depth++;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (depth >= MAX_DIRECTORY_DEPTH) {
|
|
25
|
+
debug('Max directory depth reached while searching for skills directory');
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
return null;
|
|
@@ -51,7 +60,7 @@ export function getLocalSkills() {
|
|
|
51
60
|
.filter(entry => entry.isDirectory() && !entry.name.startsWith('.'))
|
|
52
61
|
.map(entry => {
|
|
53
62
|
const skillPath = join(skillsDir, entry.name);
|
|
54
|
-
const skillMdPath = join(skillPath,
|
|
63
|
+
const skillMdPath = join(skillPath, SKILL_MD);
|
|
55
64
|
|
|
56
65
|
let description = null;
|
|
57
66
|
if (existsSync(skillMdPath)) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple logging utility with debug support
|
|
3
|
+
* Enable debug logs with DEBUG=antikit environment variable
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
const DEBUG = process.env.DEBUG === 'antikit' || process.env.DEBUG === '*';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Log debug message (only when DEBUG=antikit)
|
|
12
|
+
*/
|
|
13
|
+
export function debug(...args) {
|
|
14
|
+
if (DEBUG) {
|
|
15
|
+
console.error(chalk.dim('[debug]'), ...args);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Log info message
|
|
21
|
+
*/
|
|
22
|
+
export function info(...args) {
|
|
23
|
+
console.log(chalk.blue('ℹ'), ...args);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Log warning message
|
|
28
|
+
*/
|
|
29
|
+
export function warn(...args) {
|
|
30
|
+
console.warn(chalk.yellow('⚠'), ...args);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Log error message
|
|
35
|
+
*/
|
|
36
|
+
export function error(...args) {
|
|
37
|
+
console.error(chalk.red('✖'), ...args);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Log success message
|
|
42
|
+
*/
|
|
43
|
+
export function success(...args) {
|
|
44
|
+
console.log(chalk.green('✓'), ...args);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a scoped logger
|
|
49
|
+
*/
|
|
50
|
+
export function createLogger(scope) {
|
|
51
|
+
const prefix = chalk.dim(`[${scope}]`);
|
|
52
|
+
return {
|
|
53
|
+
debug: (...args) => debug(prefix, ...args),
|
|
54
|
+
info: (...args) => info(prefix, ...args),
|
|
55
|
+
warn: (...args) => warn(prefix, ...args),
|
|
56
|
+
error: (...args) => error(prefix, ...args),
|
|
57
|
+
success: (...args) => success(prefix, ...args)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default { debug, info, warn, error, success, createLogger };
|
|
@@ -4,6 +4,8 @@ import { join, dirname } from 'path';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { spawn } from 'child_process';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
+
import { compareVersions } from './version.js';
|
|
8
|
+
import { debug } from './logger.js';
|
|
7
9
|
|
|
8
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
11
|
const CONFIG_DIR = join(homedir(), '.antikit');
|
|
@@ -13,17 +15,12 @@ const UPDATE_CHECK_FILE = join(CONFIG_DIR, 'update-check.json');
|
|
|
13
15
|
const CHECK_INTERVAL = 1000 * 60 * 60 * 6;
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
|
-
*
|
|
18
|
+
* Ensure config directory exists
|
|
17
19
|
*/
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
for (let i = 0; i < 3; i++) {
|
|
23
|
-
if (parts1[i] > parts2[i]) return 1;
|
|
24
|
-
if (parts1[i] < parts2[i]) return -1;
|
|
20
|
+
function ensureConfigDir() {
|
|
21
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
22
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
25
23
|
}
|
|
26
|
-
return 0;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
/**
|
|
@@ -33,7 +30,45 @@ function loadUpdateCache() {
|
|
|
33
30
|
try {
|
|
34
31
|
if (!existsSync(UPDATE_CHECK_FILE)) return null;
|
|
35
32
|
return JSON.parse(readFileSync(UPDATE_CHECK_FILE, 'utf-8'));
|
|
36
|
-
} catch {
|
|
33
|
+
} catch (e) {
|
|
34
|
+
debug('Failed to load update cache:', e.message);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Save update cache data
|
|
41
|
+
*/
|
|
42
|
+
function saveUpdateCache(data) {
|
|
43
|
+
try {
|
|
44
|
+
ensureConfigDir();
|
|
45
|
+
writeFileSync(UPDATE_CHECK_FILE, JSON.stringify(data, null, 2));
|
|
46
|
+
} catch (e) {
|
|
47
|
+
debug('Failed to save update cache:', e.message);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Fetch latest version from npm registry
|
|
53
|
+
* @param {string} packageName
|
|
54
|
+
* @returns {Promise<string|null>}
|
|
55
|
+
*/
|
|
56
|
+
async function fetchLatestVersion(packageName) {
|
|
57
|
+
try {
|
|
58
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
59
|
+
headers: { Accept: 'application/json' },
|
|
60
|
+
signal: AbortSignal.timeout(5000)
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
debug('NPM registry returned non-ok status:', response.status);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const data = await response.json();
|
|
69
|
+
return data.version || null;
|
|
70
|
+
} catch (e) {
|
|
71
|
+
debug('Failed to fetch latest version:', e.message);
|
|
37
72
|
return null;
|
|
38
73
|
}
|
|
39
74
|
}
|
|
@@ -103,8 +138,8 @@ export function checkForUpdates(packageName, currentVersion) {
|
|
|
103
138
|
stdio: 'ignore'
|
|
104
139
|
});
|
|
105
140
|
child.unref();
|
|
106
|
-
} catch {
|
|
107
|
-
|
|
141
|
+
} catch (e) {
|
|
142
|
+
debug('Failed to spawn update checker:', e.message);
|
|
108
143
|
}
|
|
109
144
|
}
|
|
110
145
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version utilities for antikit
|
|
3
|
+
* Centralized version comparison and constants
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_VERSION = '0.0.0';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Compare two semantic versions
|
|
10
|
+
* @param {string} v1 - First version (e.g., '1.2.3')
|
|
11
|
+
* @param {string} v2 - Second version (e.g., '1.2.4')
|
|
12
|
+
* @returns {number} 1 if v1 > v2, -1 if v1 < v2, 0 if equal
|
|
13
|
+
*/
|
|
14
|
+
export function compareVersions(v1, v2) {
|
|
15
|
+
if (!v1 || !v2) return 0;
|
|
16
|
+
|
|
17
|
+
const parts1 = v1.split('.').map(Number);
|
|
18
|
+
const parts2 = v2.split('.').map(Number);
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < 3; i++) {
|
|
21
|
+
const p1 = parts1[i] || 0;
|
|
22
|
+
const p2 = parts2[i] || 0;
|
|
23
|
+
if (p1 > p2) return 1;
|
|
24
|
+
if (p1 < p2) return -1;
|
|
25
|
+
}
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if version string is valid semantic version
|
|
31
|
+
* @param {string} version
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
export function isValidVersion(version) {
|
|
35
|
+
if (!version || typeof version !== 'string') return false;
|
|
36
|
+
return /^\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/.test(version);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parse version from SKILL.md frontmatter content
|
|
41
|
+
* @param {string} content - SKILL.md content
|
|
42
|
+
* @returns {string} version or DEFAULT_VERSION
|
|
43
|
+
*/
|
|
44
|
+
export function parseVersionFromContent(content) {
|
|
45
|
+
if (!content) return DEFAULT_VERSION;
|
|
46
|
+
const match = content.match(/^version:\s*(.+)/m);
|
|
47
|
+
return match ? match[1].trim() : DEFAULT_VERSION;
|
|
48
|
+
}
|