reskill 0.1.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/LICENSE +21 -0
- package/README.md +199 -0
- package/dist/cli/commands/index.d.ts +9 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/info.d.ts +7 -0
- package/dist/cli/commands/info.d.ts.map +1 -0
- package/dist/cli/commands/init.d.ts +7 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/install.d.ts +11 -0
- package/dist/cli/commands/install.d.ts.map +1 -0
- package/dist/cli/commands/link.d.ts +5 -0
- package/dist/cli/commands/link.d.ts.map +1 -0
- package/dist/cli/commands/list.d.ts +7 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/outdated.d.ts +7 -0
- package/dist/cli/commands/outdated.d.ts.map +1 -0
- package/dist/cli/commands/uninstall.d.ts +7 -0
- package/dist/cli/commands/uninstall.d.ts.map +1 -0
- package/dist/cli/commands/update.d.ts +7 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +1293 -0
- package/dist/core/cache-manager.d.ts +80 -0
- package/dist/core/cache-manager.d.ts.map +1 -0
- package/dist/core/config-loader.d.ts +76 -0
- package/dist/core/config-loader.d.ts.map +1 -0
- package/dist/core/git-resolver.d.ts +73 -0
- package/dist/core/git-resolver.d.ts.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/lock-manager.d.ts +75 -0
- package/dist/core/lock-manager.d.ts.map +1 -0
- package/dist/core/skill-manager.d.ts +98 -0
- package/dist/core/skill-manager.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1033 -0
- package/dist/types/index.d.ts +213 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/fs.d.ts +86 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/git.d.ts +112 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/logger.d.ts +43 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/package.json +69 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1033 @@
|
|
|
1
|
+
import * as __WEBPACK_EXTERNAL_MODULE_node_fs__ from "node:fs";
|
|
2
|
+
import * as __WEBPACK_EXTERNAL_MODULE_semver__ from "semver";
|
|
3
|
+
import * as __WEBPACK_EXTERNAL_MODULE_node_child_process__ from "node:child_process";
|
|
4
|
+
import * as __WEBPACK_EXTERNAL_MODULE_node_util__ from "node:util";
|
|
5
|
+
import * as __WEBPACK_EXTERNAL_MODULE_node_path__ from "node:path";
|
|
6
|
+
import * as __WEBPACK_EXTERNAL_MODULE_chalk__ from "chalk";
|
|
7
|
+
var __webpack_modules__ = {
|
|
8
|
+
"node:fs": function(module) {
|
|
9
|
+
module.exports = __WEBPACK_EXTERNAL_MODULE_node_fs__;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var __webpack_module_cache__ = {};
|
|
13
|
+
function __webpack_require__(moduleId) {
|
|
14
|
+
var cachedModule = __webpack_module_cache__[moduleId];
|
|
15
|
+
if (void 0 !== cachedModule) return cachedModule.exports;
|
|
16
|
+
var module = __webpack_module_cache__[moduleId] = {
|
|
17
|
+
exports: {}
|
|
18
|
+
};
|
|
19
|
+
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
20
|
+
return module.exports;
|
|
21
|
+
}
|
|
22
|
+
const execAsync = (0, __WEBPACK_EXTERNAL_MODULE_node_util__.promisify)(__WEBPACK_EXTERNAL_MODULE_node_child_process__.exec);
|
|
23
|
+
class GitCloneError extends Error {
|
|
24
|
+
repoUrl;
|
|
25
|
+
originalError;
|
|
26
|
+
isAuthError;
|
|
27
|
+
constructor(repoUrl, originalError){
|
|
28
|
+
const isAuthError = GitCloneError.isAuthenticationError(originalError.message);
|
|
29
|
+
let message = `Failed to clone repository: ${repoUrl}`;
|
|
30
|
+
if (isAuthError) {
|
|
31
|
+
message += '\n\nTip: For private repos, ensure git SSH keys or credentials are configured:';
|
|
32
|
+
message += '\n - SSH: Check ~/.ssh/id_rsa or ~/.ssh/id_ed25519';
|
|
33
|
+
message += '\n - HTTPS: Run \'git config --global credential.helper store\'';
|
|
34
|
+
message += '\n - Or use a personal access token in the URL';
|
|
35
|
+
}
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'GitCloneError';
|
|
38
|
+
this.repoUrl = repoUrl;
|
|
39
|
+
this.originalError = originalError;
|
|
40
|
+
this.isAuthError = isAuthError;
|
|
41
|
+
}
|
|
42
|
+
static isAuthenticationError(message) {
|
|
43
|
+
const authPatterns = [
|
|
44
|
+
/permission denied/i,
|
|
45
|
+
/could not read from remote/i,
|
|
46
|
+
/authentication failed/i,
|
|
47
|
+
/fatal: repository.*not found/i,
|
|
48
|
+
/host key verification failed/i,
|
|
49
|
+
/access denied/i,
|
|
50
|
+
/unauthorized/i,
|
|
51
|
+
/403/,
|
|
52
|
+
/401/
|
|
53
|
+
];
|
|
54
|
+
return authPatterns.some((pattern)=>pattern.test(message));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function git(args, cwd) {
|
|
58
|
+
const { stdout } = await execAsync(`git ${args.join(' ')}`, {
|
|
59
|
+
cwd,
|
|
60
|
+
encoding: 'utf-8'
|
|
61
|
+
});
|
|
62
|
+
return stdout.trim();
|
|
63
|
+
}
|
|
64
|
+
async function getRemoteTags(repoUrl) {
|
|
65
|
+
try {
|
|
66
|
+
const output = await git([
|
|
67
|
+
'ls-remote',
|
|
68
|
+
'--tags',
|
|
69
|
+
'--refs',
|
|
70
|
+
repoUrl
|
|
71
|
+
]);
|
|
72
|
+
if (!output) return [];
|
|
73
|
+
const tags = [];
|
|
74
|
+
const lines = output.split('\n');
|
|
75
|
+
for (const line of lines){
|
|
76
|
+
const [commit, ref] = line.split('\t');
|
|
77
|
+
if (commit && ref) {
|
|
78
|
+
const tagName = ref.replace('refs/tags/', '');
|
|
79
|
+
tags.push({
|
|
80
|
+
name: tagName,
|
|
81
|
+
commit
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return tags;
|
|
86
|
+
} catch {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function getLatestTag(repoUrl) {
|
|
91
|
+
const tags = await getRemoteTags(repoUrl);
|
|
92
|
+
if (0 === tags.length) return null;
|
|
93
|
+
const sortedTags = tags.sort((a, b)=>{
|
|
94
|
+
const aVer = a.name.replace(/^v/, '');
|
|
95
|
+
const bVer = b.name.replace(/^v/, '');
|
|
96
|
+
return compareVersions(bVer, aVer);
|
|
97
|
+
});
|
|
98
|
+
return sortedTags[0];
|
|
99
|
+
}
|
|
100
|
+
async function clone(repoUrl, destPath, options) {
|
|
101
|
+
const args = [
|
|
102
|
+
'clone'
|
|
103
|
+
];
|
|
104
|
+
if (options?.depth) args.push('--depth', options.depth.toString());
|
|
105
|
+
if (options?.branch) args.push('--branch', options.branch);
|
|
106
|
+
args.push(repoUrl, destPath);
|
|
107
|
+
try {
|
|
108
|
+
await git(args);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new GitCloneError(repoUrl, error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function getCurrentCommit(cwd) {
|
|
114
|
+
return git([
|
|
115
|
+
'rev-parse',
|
|
116
|
+
'HEAD'
|
|
117
|
+
], cwd);
|
|
118
|
+
}
|
|
119
|
+
async function getDefaultBranch(repoUrl) {
|
|
120
|
+
try {
|
|
121
|
+
const output = await git([
|
|
122
|
+
'ls-remote',
|
|
123
|
+
'--symref',
|
|
124
|
+
repoUrl,
|
|
125
|
+
'HEAD'
|
|
126
|
+
]);
|
|
127
|
+
const match = output.match(/ref: refs\/heads\/(\S+)/);
|
|
128
|
+
return match ? match[1] : 'main';
|
|
129
|
+
} catch {
|
|
130
|
+
return 'main';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function compareVersions(a, b) {
|
|
134
|
+
const aParts = a.split('.').map((p)=>parseInt(p, 10) || 0);
|
|
135
|
+
const bParts = b.split('.').map((p)=>parseInt(p, 10) || 0);
|
|
136
|
+
const maxLength = Math.max(aParts.length, bParts.length);
|
|
137
|
+
for(let i = 0; i < maxLength; i++){
|
|
138
|
+
const aPart = aParts[i] || 0;
|
|
139
|
+
const bPart = bParts[i] || 0;
|
|
140
|
+
if (aPart > bPart) return 1;
|
|
141
|
+
if (aPart < bPart) return -1;
|
|
142
|
+
}
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
function buildRepoUrl(registry, ownerRepo) {
|
|
146
|
+
const registryUrls = {
|
|
147
|
+
github: 'https://github.com',
|
|
148
|
+
gitlab: 'https://gitlab.com'
|
|
149
|
+
};
|
|
150
|
+
const baseUrl = registryUrls[registry] || `https://${registry}`;
|
|
151
|
+
return `${baseUrl}/${ownerRepo}`;
|
|
152
|
+
}
|
|
153
|
+
function isGitUrl(source) {
|
|
154
|
+
return source.startsWith('git@') || source.startsWith('git://') || source.startsWith('http://') || source.startsWith('https://') || source.endsWith('.git');
|
|
155
|
+
}
|
|
156
|
+
function parseGitUrl(url) {
|
|
157
|
+
const cleanUrl = url.replace(/\.git$/, '');
|
|
158
|
+
const sshMatch = cleanUrl.match(/^git@([^:]+):(.+)$/);
|
|
159
|
+
if (sshMatch) {
|
|
160
|
+
const [, host, path] = sshMatch;
|
|
161
|
+
const parts = path.split('/');
|
|
162
|
+
if (parts.length >= 2) {
|
|
163
|
+
const owner = parts.slice(0, -1).join('/');
|
|
164
|
+
const repo = parts[parts.length - 1];
|
|
165
|
+
return {
|
|
166
|
+
host,
|
|
167
|
+
owner,
|
|
168
|
+
repo,
|
|
169
|
+
url,
|
|
170
|
+
type: 'ssh'
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const httpMatch = cleanUrl.match(/^(https?|git):\/\/([^/]+)\/(.+)$/);
|
|
175
|
+
if (httpMatch) {
|
|
176
|
+
const [, protocol, host, path] = httpMatch;
|
|
177
|
+
const parts = path.split('/');
|
|
178
|
+
if (parts.length >= 2) {
|
|
179
|
+
const owner = parts.slice(0, -1).join('/');
|
|
180
|
+
const repo = parts[parts.length - 1];
|
|
181
|
+
return {
|
|
182
|
+
host,
|
|
183
|
+
owner,
|
|
184
|
+
repo,
|
|
185
|
+
url,
|
|
186
|
+
type: 'git' === protocol ? 'git' : 'https'
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
class GitResolver {
|
|
193
|
+
defaultRegistry;
|
|
194
|
+
constructor(defaultRegistry = 'github'){
|
|
195
|
+
this.defaultRegistry = defaultRegistry;
|
|
196
|
+
}
|
|
197
|
+
parseRef(ref) {
|
|
198
|
+
const raw = ref;
|
|
199
|
+
if (isGitUrl(ref)) return this.parseGitUrlRef(ref);
|
|
200
|
+
let remaining = ref;
|
|
201
|
+
let registry = this.defaultRegistry;
|
|
202
|
+
let version;
|
|
203
|
+
const registryMatch = remaining.match(/^([a-zA-Z0-9.-]+):(.+)$/);
|
|
204
|
+
if (registryMatch) {
|
|
205
|
+
registry = registryMatch[1];
|
|
206
|
+
remaining = registryMatch[2];
|
|
207
|
+
}
|
|
208
|
+
const atIndex = remaining.lastIndexOf('@');
|
|
209
|
+
if (atIndex > 0) {
|
|
210
|
+
version = remaining.slice(atIndex + 1);
|
|
211
|
+
remaining = remaining.slice(0, atIndex);
|
|
212
|
+
}
|
|
213
|
+
const parts = remaining.split('/');
|
|
214
|
+
if (parts.length < 2) throw new Error(`Invalid skill reference: ${ref}. Expected format: owner/repo[@version]`);
|
|
215
|
+
const owner = parts[0];
|
|
216
|
+
const repo = parts[1];
|
|
217
|
+
const subPath = parts.length > 2 ? parts.slice(2).join('/') : void 0;
|
|
218
|
+
return {
|
|
219
|
+
registry,
|
|
220
|
+
owner,
|
|
221
|
+
repo,
|
|
222
|
+
subPath,
|
|
223
|
+
version,
|
|
224
|
+
raw
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
parseGitUrlRef(ref) {
|
|
228
|
+
const raw = ref;
|
|
229
|
+
let gitUrl = ref;
|
|
230
|
+
let version;
|
|
231
|
+
let subPath;
|
|
232
|
+
const gitSuffixIndex = ref.indexOf('.git');
|
|
233
|
+
if (-1 !== gitSuffixIndex) {
|
|
234
|
+
const afterGit = ref.slice(gitSuffixIndex + 4);
|
|
235
|
+
if (afterGit) {
|
|
236
|
+
const atIndex = afterGit.lastIndexOf('@');
|
|
237
|
+
if (-1 !== atIndex) {
|
|
238
|
+
version = afterGit.slice(atIndex + 1);
|
|
239
|
+
const pathPart = afterGit.slice(0, atIndex);
|
|
240
|
+
if (pathPart.startsWith('/')) subPath = pathPart.slice(1);
|
|
241
|
+
} else if (afterGit.startsWith('/')) subPath = afterGit.slice(1);
|
|
242
|
+
gitUrl = ref.slice(0, gitSuffixIndex + 4);
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
const atIndex = ref.lastIndexOf('@');
|
|
246
|
+
if (atIndex > 4) {
|
|
247
|
+
version = ref.slice(atIndex + 1);
|
|
248
|
+
gitUrl = ref.slice(0, atIndex);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const parsed = parseGitUrl(gitUrl);
|
|
252
|
+
if (!parsed) throw new Error(`Invalid Git URL: ${ref}. Expected format: git@host:owner/repo.git or https://host/owner/repo.git`);
|
|
253
|
+
return {
|
|
254
|
+
registry: parsed.host,
|
|
255
|
+
owner: parsed.owner,
|
|
256
|
+
repo: parsed.repo,
|
|
257
|
+
subPath,
|
|
258
|
+
version,
|
|
259
|
+
raw,
|
|
260
|
+
gitUrl
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
parseVersion(versionSpec) {
|
|
264
|
+
if (!versionSpec) return {
|
|
265
|
+
type: 'branch',
|
|
266
|
+
value: 'main',
|
|
267
|
+
raw: ''
|
|
268
|
+
};
|
|
269
|
+
const raw = versionSpec;
|
|
270
|
+
if ('latest' === versionSpec) return {
|
|
271
|
+
type: 'latest',
|
|
272
|
+
value: 'latest',
|
|
273
|
+
raw
|
|
274
|
+
};
|
|
275
|
+
if (versionSpec.startsWith('branch:')) return {
|
|
276
|
+
type: 'branch',
|
|
277
|
+
value: versionSpec.slice(7),
|
|
278
|
+
raw
|
|
279
|
+
};
|
|
280
|
+
if (versionSpec.startsWith('commit:')) return {
|
|
281
|
+
type: 'commit',
|
|
282
|
+
value: versionSpec.slice(7),
|
|
283
|
+
raw
|
|
284
|
+
};
|
|
285
|
+
if (/^[\^~><]/.test(versionSpec)) return {
|
|
286
|
+
type: 'range',
|
|
287
|
+
value: versionSpec,
|
|
288
|
+
raw
|
|
289
|
+
};
|
|
290
|
+
return {
|
|
291
|
+
type: 'exact',
|
|
292
|
+
value: versionSpec,
|
|
293
|
+
raw
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
buildRepoUrl(parsed) {
|
|
297
|
+
if (parsed.gitUrl) return parsed.gitUrl;
|
|
298
|
+
return buildRepoUrl(parsed.registry, `${parsed.owner}/${parsed.repo}`);
|
|
299
|
+
}
|
|
300
|
+
async resolveVersion(repoUrl, versionSpec) {
|
|
301
|
+
switch(versionSpec.type){
|
|
302
|
+
case 'exact':
|
|
303
|
+
return {
|
|
304
|
+
ref: versionSpec.value
|
|
305
|
+
};
|
|
306
|
+
case 'latest':
|
|
307
|
+
{
|
|
308
|
+
const latestTag = await getLatestTag(repoUrl);
|
|
309
|
+
if (!latestTag) {
|
|
310
|
+
const defaultBranch = await getDefaultBranch(repoUrl);
|
|
311
|
+
return {
|
|
312
|
+
ref: defaultBranch
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
ref: latestTag.name,
|
|
317
|
+
commit: latestTag.commit
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
case 'range':
|
|
321
|
+
{
|
|
322
|
+
const tags = await getRemoteTags(repoUrl);
|
|
323
|
+
const matchingTags = tags.filter((tag)=>{
|
|
324
|
+
const version = tag.name.replace(/^v/, '');
|
|
325
|
+
return __WEBPACK_EXTERNAL_MODULE_semver__.satisfies(version, versionSpec.value);
|
|
326
|
+
});
|
|
327
|
+
if (0 === matchingTags.length) throw new Error(`No version found matching ${versionSpec.raw} for ${repoUrl}`);
|
|
328
|
+
matchingTags.sort((a, b)=>{
|
|
329
|
+
const aVer = a.name.replace(/^v/, '');
|
|
330
|
+
const bVer = b.name.replace(/^v/, '');
|
|
331
|
+
return __WEBPACK_EXTERNAL_MODULE_semver__.compare(bVer, aVer);
|
|
332
|
+
});
|
|
333
|
+
return {
|
|
334
|
+
ref: matchingTags[0].name,
|
|
335
|
+
commit: matchingTags[0].commit
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
case 'branch':
|
|
339
|
+
return {
|
|
340
|
+
ref: versionSpec.value
|
|
341
|
+
};
|
|
342
|
+
case 'commit':
|
|
343
|
+
return {
|
|
344
|
+
ref: versionSpec.value,
|
|
345
|
+
commit: versionSpec.value
|
|
346
|
+
};
|
|
347
|
+
default:
|
|
348
|
+
throw new Error(`Unknown version type: ${versionSpec.type}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
async resolve(ref) {
|
|
352
|
+
const parsed = this.parseRef(ref);
|
|
353
|
+
const repoUrl = this.buildRepoUrl(parsed);
|
|
354
|
+
const versionSpec = this.parseVersion(parsed.version);
|
|
355
|
+
const resolved = await this.resolveVersion(repoUrl, versionSpec);
|
|
356
|
+
return {
|
|
357
|
+
parsed,
|
|
358
|
+
repoUrl,
|
|
359
|
+
ref: resolved.ref,
|
|
360
|
+
commit: resolved.commit
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
var external_node_fs_ = __webpack_require__("node:fs");
|
|
365
|
+
function exists(filePath) {
|
|
366
|
+
return external_node_fs_.existsSync(filePath);
|
|
367
|
+
}
|
|
368
|
+
function readJson(filePath) {
|
|
369
|
+
const content = external_node_fs_.readFileSync(filePath, 'utf-8');
|
|
370
|
+
return JSON.parse(content);
|
|
371
|
+
}
|
|
372
|
+
function writeJson(filePath, data, indent = 2) {
|
|
373
|
+
const dir = __WEBPACK_EXTERNAL_MODULE_node_path__.dirname(filePath);
|
|
374
|
+
if (!exists(dir)) external_node_fs_.mkdirSync(dir, {
|
|
375
|
+
recursive: true
|
|
376
|
+
});
|
|
377
|
+
external_node_fs_.writeFileSync(filePath, JSON.stringify(data, null, indent) + '\n', 'utf-8');
|
|
378
|
+
}
|
|
379
|
+
function ensureDir(dirPath) {
|
|
380
|
+
if (!exists(dirPath)) external_node_fs_.mkdirSync(dirPath, {
|
|
381
|
+
recursive: true
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
function remove(targetPath) {
|
|
385
|
+
if (exists(targetPath)) external_node_fs_.rmSync(targetPath, {
|
|
386
|
+
recursive: true,
|
|
387
|
+
force: true
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
function copyDir(src, dest, options) {
|
|
391
|
+
const exclude = options?.exclude || [];
|
|
392
|
+
ensureDir(dest);
|
|
393
|
+
const entries = external_node_fs_.readdirSync(src, {
|
|
394
|
+
withFileTypes: true
|
|
395
|
+
});
|
|
396
|
+
for (const entry of entries){
|
|
397
|
+
if (exclude.includes(entry.name)) continue;
|
|
398
|
+
const srcPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(src, entry.name);
|
|
399
|
+
const destPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(dest, entry.name);
|
|
400
|
+
if (entry.isDirectory()) copyDir(srcPath, destPath, options);
|
|
401
|
+
else external_node_fs_.copyFileSync(srcPath, destPath);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function listDir(dirPath) {
|
|
405
|
+
if (!exists(dirPath)) return [];
|
|
406
|
+
return external_node_fs_.readdirSync(dirPath);
|
|
407
|
+
}
|
|
408
|
+
function isDirectory(targetPath) {
|
|
409
|
+
if (!exists(targetPath)) return false;
|
|
410
|
+
return external_node_fs_.statSync(targetPath).isDirectory();
|
|
411
|
+
}
|
|
412
|
+
function isSymlink(targetPath) {
|
|
413
|
+
if (!exists(targetPath)) return false;
|
|
414
|
+
return external_node_fs_.lstatSync(targetPath).isSymbolicLink();
|
|
415
|
+
}
|
|
416
|
+
function createSymlink(target, linkPath) {
|
|
417
|
+
const linkDir = __WEBPACK_EXTERNAL_MODULE_node_path__.dirname(linkPath);
|
|
418
|
+
ensureDir(linkDir);
|
|
419
|
+
if (exists(linkPath)) remove(linkPath);
|
|
420
|
+
external_node_fs_.symlinkSync(target, linkPath, 'dir');
|
|
421
|
+
}
|
|
422
|
+
function getRealPath(linkPath) {
|
|
423
|
+
return external_node_fs_.realpathSync(linkPath);
|
|
424
|
+
}
|
|
425
|
+
function getSkillsJsonPath(projectRoot) {
|
|
426
|
+
const root = projectRoot || process.cwd();
|
|
427
|
+
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(root, 'skills.json');
|
|
428
|
+
}
|
|
429
|
+
function getSkillsLockPath(projectRoot) {
|
|
430
|
+
const root = projectRoot || process.cwd();
|
|
431
|
+
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(root, 'skills.lock');
|
|
432
|
+
}
|
|
433
|
+
function getCacheDir() {
|
|
434
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
435
|
+
return process.env.RESKILL_CACHE_DIR || __WEBPACK_EXTERNAL_MODULE_node_path__.join(home, '.reskill-cache');
|
|
436
|
+
}
|
|
437
|
+
function getHomeDir() {
|
|
438
|
+
return process.env.HOME || process.env.USERPROFILE || '';
|
|
439
|
+
}
|
|
440
|
+
function getGlobalSkillsDir() {
|
|
441
|
+
const home = getHomeDir();
|
|
442
|
+
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(home, '.claude', 'skills');
|
|
443
|
+
}
|
|
444
|
+
class CacheManager {
|
|
445
|
+
cacheDir;
|
|
446
|
+
constructor(cacheDir){
|
|
447
|
+
this.cacheDir = cacheDir || getCacheDir();
|
|
448
|
+
}
|
|
449
|
+
getCacheDir() {
|
|
450
|
+
return this.cacheDir;
|
|
451
|
+
}
|
|
452
|
+
getSkillCachePath(parsed, version) {
|
|
453
|
+
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.cacheDir, parsed.registry, parsed.owner, parsed.repo, version);
|
|
454
|
+
}
|
|
455
|
+
isCached(parsed, version) {
|
|
456
|
+
const cachePath = this.getSkillCachePath(parsed, version);
|
|
457
|
+
return exists(cachePath) && isDirectory(cachePath);
|
|
458
|
+
}
|
|
459
|
+
async get(parsed, version) {
|
|
460
|
+
const cachePath = this.getSkillCachePath(parsed, version);
|
|
461
|
+
if (!this.isCached(parsed, version)) return null;
|
|
462
|
+
const commitFile = __WEBPACK_EXTERNAL_MODULE_node_path__.join(cachePath, '.reskill-commit');
|
|
463
|
+
let commit = '';
|
|
464
|
+
try {
|
|
465
|
+
const fs = await import("node:fs");
|
|
466
|
+
if (exists(commitFile)) commit = fs.readFileSync(commitFile, 'utf-8').trim();
|
|
467
|
+
} catch {}
|
|
468
|
+
return {
|
|
469
|
+
path: cachePath,
|
|
470
|
+
commit
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
async cache(repoUrl, parsed, ref, version) {
|
|
474
|
+
const cachePath = this.getSkillCachePath(parsed, version);
|
|
475
|
+
if (exists(cachePath)) remove(cachePath);
|
|
476
|
+
ensureDir(__WEBPACK_EXTERNAL_MODULE_node_path__.dirname(cachePath));
|
|
477
|
+
const tempPath = `${cachePath}.tmp`;
|
|
478
|
+
remove(tempPath);
|
|
479
|
+
await clone(repoUrl, tempPath, {
|
|
480
|
+
depth: 1,
|
|
481
|
+
branch: ref
|
|
482
|
+
});
|
|
483
|
+
const commit = await getCurrentCommit(tempPath);
|
|
484
|
+
if (parsed.subPath) {
|
|
485
|
+
const subDir = __WEBPACK_EXTERNAL_MODULE_node_path__.join(tempPath, parsed.subPath);
|
|
486
|
+
if (!exists(subDir)) {
|
|
487
|
+
remove(tempPath);
|
|
488
|
+
throw new Error(`Subpath ${parsed.subPath} not found in repository`);
|
|
489
|
+
}
|
|
490
|
+
copyDir(subDir, cachePath, {
|
|
491
|
+
exclude: [
|
|
492
|
+
'.git'
|
|
493
|
+
]
|
|
494
|
+
});
|
|
495
|
+
} else copyDir(tempPath, cachePath, {
|
|
496
|
+
exclude: [
|
|
497
|
+
'.git'
|
|
498
|
+
]
|
|
499
|
+
});
|
|
500
|
+
const fs = await import("node:fs");
|
|
501
|
+
fs.writeFileSync(__WEBPACK_EXTERNAL_MODULE_node_path__.join(cachePath, '.reskill-commit'), commit);
|
|
502
|
+
remove(tempPath);
|
|
503
|
+
return {
|
|
504
|
+
path: cachePath,
|
|
505
|
+
commit
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
async copyTo(parsed, version, destPath) {
|
|
509
|
+
const cached = await this.get(parsed, version);
|
|
510
|
+
if (!cached) throw new Error(`Skill ${parsed.raw} version ${version} not found in cache`);
|
|
511
|
+
if (exists(destPath)) remove(destPath);
|
|
512
|
+
copyDir(cached.path, destPath, {
|
|
513
|
+
exclude: [
|
|
514
|
+
'.reskill-commit'
|
|
515
|
+
]
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
clearSkill(parsed, version) {
|
|
519
|
+
if (version) {
|
|
520
|
+
const cachePath = this.getSkillCachePath(parsed, version);
|
|
521
|
+
remove(cachePath);
|
|
522
|
+
} else {
|
|
523
|
+
const skillDir = __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.cacheDir, parsed.registry, parsed.owner, parsed.repo);
|
|
524
|
+
remove(skillDir);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
clearAll() {
|
|
528
|
+
remove(this.cacheDir);
|
|
529
|
+
}
|
|
530
|
+
getStats() {
|
|
531
|
+
if (!exists(this.cacheDir)) return {
|
|
532
|
+
totalSkills: 0,
|
|
533
|
+
registries: []
|
|
534
|
+
};
|
|
535
|
+
const registries = listDir(this.cacheDir).filter((name)=>isDirectory(__WEBPACK_EXTERNAL_MODULE_node_path__.join(this.cacheDir, name)));
|
|
536
|
+
let totalSkills = 0;
|
|
537
|
+
for (const registry of registries){
|
|
538
|
+
const registryPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.cacheDir, registry);
|
|
539
|
+
const owners = listDir(registryPath).filter((name)=>isDirectory(__WEBPACK_EXTERNAL_MODULE_node_path__.join(registryPath, name)));
|
|
540
|
+
for (const owner of owners){
|
|
541
|
+
const ownerPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(registryPath, owner);
|
|
542
|
+
const repos = listDir(ownerPath).filter((name)=>isDirectory(__WEBPACK_EXTERNAL_MODULE_node_path__.join(ownerPath, name)));
|
|
543
|
+
totalSkills += repos.length;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
totalSkills,
|
|
548
|
+
registries
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
const DEFAULT_SKILLS_JSON = {
|
|
553
|
+
skills: {},
|
|
554
|
+
defaults: {
|
|
555
|
+
registry: 'github',
|
|
556
|
+
installDir: '.skills'
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
const DEFAULT_REGISTRIES = {
|
|
560
|
+
github: 'https://github.com',
|
|
561
|
+
gitlab: 'https://gitlab.com'
|
|
562
|
+
};
|
|
563
|
+
class ConfigLoader {
|
|
564
|
+
projectRoot;
|
|
565
|
+
configPath;
|
|
566
|
+
config = null;
|
|
567
|
+
constructor(projectRoot){
|
|
568
|
+
this.projectRoot = projectRoot || process.cwd();
|
|
569
|
+
this.configPath = getSkillsJsonPath(this.projectRoot);
|
|
570
|
+
}
|
|
571
|
+
getProjectRoot() {
|
|
572
|
+
return this.projectRoot;
|
|
573
|
+
}
|
|
574
|
+
getConfigPath() {
|
|
575
|
+
return this.configPath;
|
|
576
|
+
}
|
|
577
|
+
exists() {
|
|
578
|
+
return exists(this.configPath);
|
|
579
|
+
}
|
|
580
|
+
load() {
|
|
581
|
+
if (this.config) return this.config;
|
|
582
|
+
if (!this.exists()) throw new Error(`skills.json not found in ${this.projectRoot}. Run 'reskill init' first.`);
|
|
583
|
+
try {
|
|
584
|
+
this.config = readJson(this.configPath);
|
|
585
|
+
return this.config;
|
|
586
|
+
} catch (error) {
|
|
587
|
+
throw new Error(`Failed to parse skills.json: ${error.message}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
reload() {
|
|
591
|
+
this.config = null;
|
|
592
|
+
return this.load();
|
|
593
|
+
}
|
|
594
|
+
save(config) {
|
|
595
|
+
const toSave = config || this.config;
|
|
596
|
+
if (!toSave) throw new Error('No config to save');
|
|
597
|
+
writeJson(this.configPath, toSave);
|
|
598
|
+
this.config = toSave;
|
|
599
|
+
}
|
|
600
|
+
create(options) {
|
|
601
|
+
const config = {
|
|
602
|
+
...DEFAULT_SKILLS_JSON,
|
|
603
|
+
...options,
|
|
604
|
+
skills: options?.skills || {},
|
|
605
|
+
defaults: {
|
|
606
|
+
...DEFAULT_SKILLS_JSON.defaults,
|
|
607
|
+
...options?.defaults
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
this.save(config);
|
|
611
|
+
return config;
|
|
612
|
+
}
|
|
613
|
+
getDefaults() {
|
|
614
|
+
const config = this.config || (this.exists() ? this.load() : DEFAULT_SKILLS_JSON);
|
|
615
|
+
return {
|
|
616
|
+
registry: config.defaults?.registry || DEFAULT_SKILLS_JSON.defaults.registry,
|
|
617
|
+
installDir: config.defaults?.installDir || DEFAULT_SKILLS_JSON.defaults.installDir
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
getRegistryUrl(registryName) {
|
|
621
|
+
const config = this.config || (this.exists() ? this.load() : DEFAULT_SKILLS_JSON);
|
|
622
|
+
if (config.registries?.[registryName]) return config.registries[registryName];
|
|
623
|
+
if (DEFAULT_REGISTRIES[registryName]) return DEFAULT_REGISTRIES[registryName];
|
|
624
|
+
return `https://${registryName}`;
|
|
625
|
+
}
|
|
626
|
+
getInstallDir() {
|
|
627
|
+
const defaults = this.getDefaults();
|
|
628
|
+
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.projectRoot, defaults.installDir);
|
|
629
|
+
}
|
|
630
|
+
addSkill(name, ref) {
|
|
631
|
+
if (!this.config) this.load();
|
|
632
|
+
this.config.skills[name] = ref;
|
|
633
|
+
this.save();
|
|
634
|
+
}
|
|
635
|
+
removeSkill(name) {
|
|
636
|
+
if (!this.config) this.load();
|
|
637
|
+
if (this.config.skills[name]) {
|
|
638
|
+
delete this.config.skills[name];
|
|
639
|
+
this.save();
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
getSkills() {
|
|
645
|
+
if (!this.config) {
|
|
646
|
+
if (!this.exists()) return {};
|
|
647
|
+
this.load();
|
|
648
|
+
}
|
|
649
|
+
return {
|
|
650
|
+
...this.config.skills
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
hasSkill(name) {
|
|
654
|
+
const skills = this.getSkills();
|
|
655
|
+
return name in skills;
|
|
656
|
+
}
|
|
657
|
+
getSkillRef(name) {
|
|
658
|
+
const skills = this.getSkills();
|
|
659
|
+
return skills[name];
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const LOCKFILE_VERSION = 1;
|
|
663
|
+
class LockManager {
|
|
664
|
+
projectRoot;
|
|
665
|
+
lockPath;
|
|
666
|
+
lockData = null;
|
|
667
|
+
constructor(projectRoot){
|
|
668
|
+
this.projectRoot = projectRoot || process.cwd();
|
|
669
|
+
this.lockPath = getSkillsLockPath(this.projectRoot);
|
|
670
|
+
}
|
|
671
|
+
getLockPath() {
|
|
672
|
+
return this.lockPath;
|
|
673
|
+
}
|
|
674
|
+
exists() {
|
|
675
|
+
return exists(this.lockPath);
|
|
676
|
+
}
|
|
677
|
+
load() {
|
|
678
|
+
if (this.lockData) return this.lockData;
|
|
679
|
+
if (!this.exists()) {
|
|
680
|
+
this.lockData = {
|
|
681
|
+
lockfileVersion: LOCKFILE_VERSION,
|
|
682
|
+
skills: {}
|
|
683
|
+
};
|
|
684
|
+
return this.lockData;
|
|
685
|
+
}
|
|
686
|
+
try {
|
|
687
|
+
this.lockData = readJson(this.lockPath);
|
|
688
|
+
return this.lockData;
|
|
689
|
+
} catch (error) {
|
|
690
|
+
throw new Error(`Failed to parse skills.lock: ${error.message}`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
reload() {
|
|
694
|
+
this.lockData = null;
|
|
695
|
+
return this.load();
|
|
696
|
+
}
|
|
697
|
+
save(lockToSave) {
|
|
698
|
+
const toSave = lockToSave || this.lockData;
|
|
699
|
+
if (!toSave) throw new Error('No lock to save');
|
|
700
|
+
writeJson(this.lockPath, toSave);
|
|
701
|
+
this.lockData = toSave;
|
|
702
|
+
}
|
|
703
|
+
get(name) {
|
|
704
|
+
const lock = this.load();
|
|
705
|
+
return lock.skills[name];
|
|
706
|
+
}
|
|
707
|
+
set(name, skill) {
|
|
708
|
+
const lock = this.load();
|
|
709
|
+
lock.skills[name] = skill;
|
|
710
|
+
this.save();
|
|
711
|
+
}
|
|
712
|
+
remove(name) {
|
|
713
|
+
const lock = this.load();
|
|
714
|
+
if (lock.skills[name]) {
|
|
715
|
+
delete lock.skills[name];
|
|
716
|
+
this.save();
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
lockSkill(name, options) {
|
|
722
|
+
const lockedSkill = {
|
|
723
|
+
source: options.source,
|
|
724
|
+
version: options.version,
|
|
725
|
+
resolved: options.resolved,
|
|
726
|
+
commit: options.commit,
|
|
727
|
+
installedAt: new Date().toISOString()
|
|
728
|
+
};
|
|
729
|
+
this.set(name, lockedSkill);
|
|
730
|
+
return lockedSkill;
|
|
731
|
+
}
|
|
732
|
+
getAll() {
|
|
733
|
+
const lock = this.load();
|
|
734
|
+
return {
|
|
735
|
+
...lock.skills
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
has(name) {
|
|
739
|
+
const lock = this.load();
|
|
740
|
+
return name in lock.skills;
|
|
741
|
+
}
|
|
742
|
+
isVersionMatch(name, version) {
|
|
743
|
+
const locked = this.get(name);
|
|
744
|
+
if (!locked) return false;
|
|
745
|
+
return locked.version === version;
|
|
746
|
+
}
|
|
747
|
+
clear() {
|
|
748
|
+
this.lockData = {
|
|
749
|
+
lockfileVersion: LOCKFILE_VERSION,
|
|
750
|
+
skills: {}
|
|
751
|
+
};
|
|
752
|
+
this.save();
|
|
753
|
+
}
|
|
754
|
+
delete() {
|
|
755
|
+
if (this.exists()) {
|
|
756
|
+
const fs = __webpack_require__("node:fs");
|
|
757
|
+
fs.unlinkSync(this.lockPath);
|
|
758
|
+
}
|
|
759
|
+
this.lockData = null;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
const logger = {
|
|
763
|
+
info (message) {
|
|
764
|
+
console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].blue('ℹ'), message);
|
|
765
|
+
},
|
|
766
|
+
success (message) {
|
|
767
|
+
console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].green('✅'), message);
|
|
768
|
+
},
|
|
769
|
+
warn (message) {
|
|
770
|
+
console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].yellow('⚠️'), message);
|
|
771
|
+
},
|
|
772
|
+
error (message) {
|
|
773
|
+
console.error(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].red('❌'), message);
|
|
774
|
+
},
|
|
775
|
+
debug (message) {
|
|
776
|
+
if (process.env.DEBUG || process.env.VERBOSE) console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].gray('🔍'), __WEBPACK_EXTERNAL_MODULE_chalk__["default"].gray(message));
|
|
777
|
+
},
|
|
778
|
+
package (message) {
|
|
779
|
+
console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].cyan('📦'), message);
|
|
780
|
+
},
|
|
781
|
+
log (message) {
|
|
782
|
+
console.log(message);
|
|
783
|
+
},
|
|
784
|
+
newline () {
|
|
785
|
+
console.log();
|
|
786
|
+
},
|
|
787
|
+
table (headers, rows) {
|
|
788
|
+
const widths = headers.map((h, i)=>{
|
|
789
|
+
const colValues = [
|
|
790
|
+
h,
|
|
791
|
+
...rows.map((r)=>r[i] || '')
|
|
792
|
+
];
|
|
793
|
+
return Math.max(...colValues.map((v)=>v.length));
|
|
794
|
+
});
|
|
795
|
+
const headerRow = headers.map((h, i)=>h.padEnd(widths[i])).join(' ');
|
|
796
|
+
console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold(headerRow));
|
|
797
|
+
for (const row of rows){
|
|
798
|
+
const rowStr = row.map((cell, i)=>(cell || '').padEnd(widths[i])).join(' ');
|
|
799
|
+
console.log(rowStr);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
class SkillManager {
|
|
804
|
+
projectRoot;
|
|
805
|
+
resolver;
|
|
806
|
+
cache;
|
|
807
|
+
config;
|
|
808
|
+
lockManager;
|
|
809
|
+
isGlobal;
|
|
810
|
+
constructor(projectRoot, options){
|
|
811
|
+
this.projectRoot = projectRoot || process.cwd();
|
|
812
|
+
this.isGlobal = options?.global || false;
|
|
813
|
+
this.config = new ConfigLoader(this.projectRoot);
|
|
814
|
+
this.lockManager = new LockManager(this.projectRoot);
|
|
815
|
+
this.cache = new CacheManager();
|
|
816
|
+
const defaults = this.config.getDefaults();
|
|
817
|
+
this.resolver = new GitResolver(defaults.registry);
|
|
818
|
+
}
|
|
819
|
+
isGlobalMode() {
|
|
820
|
+
return this.isGlobal;
|
|
821
|
+
}
|
|
822
|
+
getProjectRoot() {
|
|
823
|
+
return this.projectRoot;
|
|
824
|
+
}
|
|
825
|
+
getInstallDir() {
|
|
826
|
+
if (this.isGlobal) return getGlobalSkillsDir();
|
|
827
|
+
return this.config.getInstallDir();
|
|
828
|
+
}
|
|
829
|
+
getSkillPath(name) {
|
|
830
|
+
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.getInstallDir(), name);
|
|
831
|
+
}
|
|
832
|
+
async install(ref, options = {}) {
|
|
833
|
+
const { force = false, save = true } = options;
|
|
834
|
+
const resolved = await this.resolver.resolve(ref);
|
|
835
|
+
const { parsed, repoUrl } = resolved;
|
|
836
|
+
const version = resolved.ref;
|
|
837
|
+
const skillName = parsed.subPath ? __WEBPACK_EXTERNAL_MODULE_node_path__.basename(parsed.subPath) : parsed.repo;
|
|
838
|
+
const skillPath = this.getSkillPath(skillName);
|
|
839
|
+
if (exists(skillPath) && !force) {
|
|
840
|
+
const locked = this.lockManager.get(skillName);
|
|
841
|
+
if (locked && locked.version === version) {
|
|
842
|
+
logger.info(`${skillName}@${version} is already installed`);
|
|
843
|
+
return this.getInstalledSkill(skillName);
|
|
844
|
+
}
|
|
845
|
+
if (!force) {
|
|
846
|
+
logger.warn(`${skillName} is already installed. Use --force to reinstall.`);
|
|
847
|
+
return this.getInstalledSkill(skillName);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
logger["package"](`Installing ${skillName}@${version}...`);
|
|
851
|
+
let cacheResult = await this.cache.get(parsed, version);
|
|
852
|
+
if (cacheResult) logger.debug(`Using cached ${skillName}@${version}`);
|
|
853
|
+
else {
|
|
854
|
+
logger.debug(`Caching ${skillName}@${version} from ${repoUrl}`);
|
|
855
|
+
cacheResult = await this.cache.cache(repoUrl, parsed, version, version);
|
|
856
|
+
}
|
|
857
|
+
ensureDir(this.getInstallDir());
|
|
858
|
+
if (exists(skillPath)) remove(skillPath);
|
|
859
|
+
await this.cache.copyTo(parsed, version, skillPath);
|
|
860
|
+
if (!this.isGlobal) this.lockManager.lockSkill(skillName, {
|
|
861
|
+
source: `${parsed.registry}:${parsed.owner}/${parsed.repo}${parsed.subPath ? '/' + parsed.subPath : ''}`,
|
|
862
|
+
version,
|
|
863
|
+
resolved: repoUrl,
|
|
864
|
+
commit: cacheResult.commit
|
|
865
|
+
});
|
|
866
|
+
if (!this.isGlobal && save && this.config.exists()) this.config.addSkill(skillName, ref);
|
|
867
|
+
const locationHint = this.isGlobal ? '(global)' : '';
|
|
868
|
+
logger.success(`Installed ${skillName}@${version} to ${skillPath} ${locationHint}`.trim());
|
|
869
|
+
return this.getInstalledSkill(skillName);
|
|
870
|
+
}
|
|
871
|
+
async installAll(options = {}) {
|
|
872
|
+
const skills = this.config.getSkills();
|
|
873
|
+
const installed = [];
|
|
874
|
+
for (const [name, ref] of Object.entries(skills))try {
|
|
875
|
+
const skill = await this.install(ref, {
|
|
876
|
+
...options,
|
|
877
|
+
save: false
|
|
878
|
+
});
|
|
879
|
+
installed.push(skill);
|
|
880
|
+
} catch (error) {
|
|
881
|
+
logger.error(`Failed to install ${name}: ${error.message}`);
|
|
882
|
+
}
|
|
883
|
+
return installed;
|
|
884
|
+
}
|
|
885
|
+
uninstall(name) {
|
|
886
|
+
const skillPath = this.getSkillPath(name);
|
|
887
|
+
if (!exists(skillPath)) {
|
|
888
|
+
const location = this.isGlobal ? '(global)' : '';
|
|
889
|
+
logger.warn(`Skill ${name} is not installed ${location}`.trim());
|
|
890
|
+
return false;
|
|
891
|
+
}
|
|
892
|
+
remove(skillPath);
|
|
893
|
+
if (!this.isGlobal) this.lockManager.remove(name);
|
|
894
|
+
if (!this.isGlobal && this.config.exists()) this.config.removeSkill(name);
|
|
895
|
+
const locationHint = this.isGlobal ? '(global)' : '';
|
|
896
|
+
logger.success(`Uninstalled ${name} ${locationHint}`.trim());
|
|
897
|
+
return true;
|
|
898
|
+
}
|
|
899
|
+
async update(name) {
|
|
900
|
+
const updated = [];
|
|
901
|
+
if (name) {
|
|
902
|
+
const ref = this.config.getSkillRef(name);
|
|
903
|
+
if (!ref) {
|
|
904
|
+
logger.error(`Skill ${name} not found in skills.json`);
|
|
905
|
+
return [];
|
|
906
|
+
}
|
|
907
|
+
const skill = await this.install(ref, {
|
|
908
|
+
force: true,
|
|
909
|
+
save: false
|
|
910
|
+
});
|
|
911
|
+
updated.push(skill);
|
|
912
|
+
} else {
|
|
913
|
+
const skills = this.config.getSkills();
|
|
914
|
+
for (const [skillName, ref] of Object.entries(skills))try {
|
|
915
|
+
const skill = await this.install(ref, {
|
|
916
|
+
force: true,
|
|
917
|
+
save: false
|
|
918
|
+
});
|
|
919
|
+
updated.push(skill);
|
|
920
|
+
} catch (error) {
|
|
921
|
+
logger.error(`Failed to update ${skillName}: ${error.message}`);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return updated;
|
|
925
|
+
}
|
|
926
|
+
link(localPath, name) {
|
|
927
|
+
const absolutePath = __WEBPACK_EXTERNAL_MODULE_node_path__.resolve(localPath);
|
|
928
|
+
if (!exists(absolutePath)) throw new Error(`Path ${localPath} does not exist`);
|
|
929
|
+
const skillJsonPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(absolutePath, 'skill.json');
|
|
930
|
+
let skillName = name || __WEBPACK_EXTERNAL_MODULE_node_path__.basename(absolutePath);
|
|
931
|
+
if (exists(skillJsonPath)) try {
|
|
932
|
+
const skillJson = readJson(skillJsonPath);
|
|
933
|
+
skillName = name || skillJson.name || skillName;
|
|
934
|
+
} catch {}
|
|
935
|
+
const linkPath = this.getSkillPath(skillName);
|
|
936
|
+
ensureDir(this.getInstallDir());
|
|
937
|
+
createSymlink(absolutePath, linkPath);
|
|
938
|
+
logger.success(`Linked ${skillName} → ${absolutePath}`);
|
|
939
|
+
return {
|
|
940
|
+
name: skillName,
|
|
941
|
+
path: linkPath,
|
|
942
|
+
version: 'local',
|
|
943
|
+
source: absolutePath,
|
|
944
|
+
isLinked: true
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
unlink(name) {
|
|
948
|
+
const skillPath = this.getSkillPath(name);
|
|
949
|
+
if (!exists(skillPath)) {
|
|
950
|
+
logger.warn(`Skill ${name} is not installed`);
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
if (!isSymlink(skillPath)) {
|
|
954
|
+
logger.warn(`Skill ${name} is not a linked skill`);
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
remove(skillPath);
|
|
958
|
+
logger.success(`Unlinked ${name}`);
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
961
|
+
list() {
|
|
962
|
+
const installDir = this.getInstallDir();
|
|
963
|
+
if (!exists(installDir)) return [];
|
|
964
|
+
const skills = [];
|
|
965
|
+
const dirs = listDir(installDir);
|
|
966
|
+
for (const name of dirs){
|
|
967
|
+
const skillPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(installDir, name);
|
|
968
|
+
if (!isDirectory(skillPath)) continue;
|
|
969
|
+
const skill = this.getInstalledSkill(name);
|
|
970
|
+
if (skill) skills.push(skill);
|
|
971
|
+
}
|
|
972
|
+
return skills;
|
|
973
|
+
}
|
|
974
|
+
getInstalledSkill(name) {
|
|
975
|
+
const skillPath = this.getSkillPath(name);
|
|
976
|
+
if (!exists(skillPath)) return null;
|
|
977
|
+
const isLinked = isSymlink(skillPath);
|
|
978
|
+
const locked = this.lockManager.get(name);
|
|
979
|
+
let metadata;
|
|
980
|
+
const skillJsonPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(skillPath, 'skill.json');
|
|
981
|
+
if (exists(skillJsonPath)) try {
|
|
982
|
+
metadata = readJson(skillJsonPath);
|
|
983
|
+
} catch {}
|
|
984
|
+
return {
|
|
985
|
+
name,
|
|
986
|
+
path: skillPath,
|
|
987
|
+
version: isLinked ? 'local' : locked?.version || metadata?.version || 'unknown',
|
|
988
|
+
source: isLinked ? getRealPath(skillPath) : locked?.source || '',
|
|
989
|
+
metadata,
|
|
990
|
+
isLinked
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
getInfo(name) {
|
|
994
|
+
return {
|
|
995
|
+
installed: this.getInstalledSkill(name),
|
|
996
|
+
locked: this.lockManager.get(name),
|
|
997
|
+
config: this.config.getSkillRef(name)
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
async checkOutdated() {
|
|
1001
|
+
const results = [];
|
|
1002
|
+
const skills = this.config.getSkills();
|
|
1003
|
+
for (const [name, ref] of Object.entries(skills))try {
|
|
1004
|
+
const locked = this.lockManager.get(name);
|
|
1005
|
+
const current = locked?.version || 'unknown';
|
|
1006
|
+
const parsed = this.resolver.parseRef(ref);
|
|
1007
|
+
const repoUrl = this.resolver.buildRepoUrl(parsed);
|
|
1008
|
+
const latestResolved = await this.resolver.resolveVersion(repoUrl, {
|
|
1009
|
+
type: 'latest',
|
|
1010
|
+
value: 'latest',
|
|
1011
|
+
raw: 'latest'
|
|
1012
|
+
});
|
|
1013
|
+
const latest = latestResolved.ref;
|
|
1014
|
+
const updateAvailable = current !== latest && 'unknown' !== current;
|
|
1015
|
+
results.push({
|
|
1016
|
+
name,
|
|
1017
|
+
current,
|
|
1018
|
+
latest,
|
|
1019
|
+
updateAvailable
|
|
1020
|
+
});
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
logger.debug(`Failed to check ${name}: ${error.message}`);
|
|
1023
|
+
results.push({
|
|
1024
|
+
name,
|
|
1025
|
+
current: 'unknown',
|
|
1026
|
+
latest: 'unknown',
|
|
1027
|
+
updateAvailable: false
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
return results;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
export { CacheManager, ConfigLoader, DEFAULT_REGISTRIES, GitResolver, LockManager, SkillManager, logger };
|