reskill 1.1.0 → 1.1.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/dist/cli/index.js +1237 -232
- package/dist/index.js +931 -173
- package/dist/utils/git.d.ts +12 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -16,43 +16,71 @@ var __webpack_modules__ = {
|
|
|
16
16
|
module.exports = __WEBPACK_EXTERNAL_MODULE_node_fs__;
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
|
+
/************************************************************************/ // The module cache
|
|
19
20
|
var __webpack_module_cache__ = {};
|
|
21
|
+
// The require function
|
|
20
22
|
function __webpack_require__(moduleId) {
|
|
23
|
+
// Check if module is in cache
|
|
21
24
|
var cachedModule = __webpack_module_cache__[moduleId];
|
|
22
25
|
if (void 0 !== cachedModule) return cachedModule.exports;
|
|
26
|
+
// Create a new module (and put it into the cache)
|
|
23
27
|
var module = __webpack_module_cache__[moduleId] = {
|
|
24
28
|
exports: {}
|
|
25
29
|
};
|
|
30
|
+
// Execute the module function
|
|
26
31
|
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
32
|
+
// Return the exports of the module
|
|
27
33
|
return module.exports;
|
|
28
|
-
}
|
|
34
|
+
} /************************************************************************/
|
|
35
|
+
// EXTERNAL MODULE: external "node:fs"
|
|
29
36
|
var external_node_fs_ = __webpack_require__("node:fs");
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Logger utility for CLI output
|
|
39
|
+
*/ const logger_logger = {
|
|
40
|
+
/**
|
|
41
|
+
* Info message (blue)
|
|
42
|
+
*/ info (message) {
|
|
32
43
|
console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].blue('ℹ'), message);
|
|
33
44
|
},
|
|
34
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Success message (green)
|
|
47
|
+
*/ success (message) {
|
|
35
48
|
console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].green('✅'), message);
|
|
36
49
|
},
|
|
37
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Warning message (yellow)
|
|
52
|
+
*/ warn (message) {
|
|
38
53
|
console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].yellow('⚠️'), message);
|
|
39
54
|
},
|
|
40
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Error message (red)
|
|
57
|
+
*/ error (message) {
|
|
41
58
|
console.error(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].red('❌'), message);
|
|
42
59
|
},
|
|
43
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Debug message (gray, only in verbose mode)
|
|
62
|
+
*/ debug (message) {
|
|
44
63
|
if (process.env.DEBUG || process.env.VERBOSE) console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].gray('🔍'), __WEBPACK_EXTERNAL_MODULE_chalk__["default"].gray(message));
|
|
45
64
|
},
|
|
46
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Package/skill message (package emoji)
|
|
67
|
+
*/ package (message) {
|
|
47
68
|
console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].cyan('📦'), message);
|
|
48
69
|
},
|
|
49
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Plain message without icon
|
|
72
|
+
*/ log (message) {
|
|
50
73
|
console.log(message);
|
|
51
74
|
},
|
|
52
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Newline
|
|
77
|
+
*/ newline () {
|
|
53
78
|
console.log();
|
|
54
79
|
},
|
|
55
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Table-like output
|
|
82
|
+
*/ table (headers, rows) {
|
|
83
|
+
// Calculate column widths
|
|
56
84
|
const widths = headers.map((h, i)=>{
|
|
57
85
|
const colValues = [
|
|
58
86
|
h,
|
|
@@ -60,15 +88,30 @@ const logger_logger = {
|
|
|
60
88
|
];
|
|
61
89
|
return Math.max(...colValues.map((v)=>v.length));
|
|
62
90
|
});
|
|
91
|
+
// Print header
|
|
63
92
|
const headerRow = headers.map((h, i)=>h.padEnd(widths[i])).join(' ');
|
|
64
93
|
console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold(headerRow));
|
|
94
|
+
// Print separator (removed)
|
|
95
|
+
// console.log(widths.map(w => '-'.repeat(w)).join(' '));
|
|
96
|
+
// Print rows
|
|
65
97
|
for (const row of rows){
|
|
66
98
|
const rowStr = row.map((cell, i)=>(cell || '').padEnd(widths[i])).join(' ');
|
|
67
99
|
console.log(rowStr);
|
|
68
100
|
}
|
|
69
101
|
}
|
|
70
102
|
};
|
|
71
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Update Notifier - Check for CLI updates from npm registry
|
|
105
|
+
*
|
|
106
|
+
* Provides non-blocking update notifications to users when a newer version is available.
|
|
107
|
+
*/ /**
|
|
108
|
+
* Check for updates from npm registry
|
|
109
|
+
*
|
|
110
|
+
* @param packageName - Name of the package to check
|
|
111
|
+
* @param currentVersion - Current installed version
|
|
112
|
+
* @param options - Check options
|
|
113
|
+
* @returns Update check result, or null if check failed
|
|
114
|
+
*/ async function checkForUpdate(packageName, currentVersion, options = {}) {
|
|
72
115
|
const timeout = options.timeout ?? 3000;
|
|
73
116
|
try {
|
|
74
117
|
const controller = new AbortController();
|
|
@@ -88,10 +131,16 @@ async function checkForUpdate(packageName, currentVersion, options = {}) {
|
|
|
88
131
|
hasUpdate
|
|
89
132
|
};
|
|
90
133
|
} catch {
|
|
134
|
+
// Silently fail - don't interrupt user workflow
|
|
91
135
|
return null;
|
|
92
136
|
}
|
|
93
137
|
}
|
|
94
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Format update message for display
|
|
140
|
+
*
|
|
141
|
+
* @param result - Update check result
|
|
142
|
+
* @returns Formatted message string, or empty string if no update
|
|
143
|
+
*/ function formatUpdateMessage(result) {
|
|
95
144
|
if (!result.hasUpdate) return '';
|
|
96
145
|
return `
|
|
97
146
|
┌────────────────────────────────────────────────────┐
|
|
@@ -102,8 +151,15 @@ function formatUpdateMessage(result) {
|
|
|
102
151
|
└────────────────────────────────────────────────────┘
|
|
103
152
|
`;
|
|
104
153
|
}
|
|
105
|
-
|
|
106
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Agent Registry - Multi-Agent configuration definitions
|
|
156
|
+
*
|
|
157
|
+
* Supports global and project-level installation for 17 coding agents
|
|
158
|
+
* Reference: https://github.com/vercel-labs/add-skill
|
|
159
|
+
*/ const agent_registry_home = (0, __WEBPACK_EXTERNAL_MODULE_node_os__.homedir)();
|
|
160
|
+
/**
|
|
161
|
+
* All supported Agents configuration
|
|
162
|
+
*/ const agents = {
|
|
107
163
|
amp: {
|
|
108
164
|
name: 'amp',
|
|
109
165
|
displayName: 'Amp',
|
|
@@ -224,43 +280,68 @@ const agents = {
|
|
|
224
280
|
detectInstalled: async ()=>(0, external_node_fs_.existsSync)((0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(agent_registry_home, '.neovate'))
|
|
225
281
|
}
|
|
226
282
|
};
|
|
227
|
-
|
|
283
|
+
/**
|
|
284
|
+
* Detect installed Agents
|
|
285
|
+
*/ async function detectInstalledAgents() {
|
|
228
286
|
const installed = [];
|
|
229
287
|
for (const [type, config] of Object.entries(agents))if (await config.detectInstalled()) installed.push(type);
|
|
230
288
|
return installed;
|
|
231
289
|
}
|
|
232
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Get Agent configuration
|
|
292
|
+
*/ function getAgentConfig(type) {
|
|
233
293
|
return agents[type];
|
|
234
294
|
}
|
|
235
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Validate if Agent type is valid
|
|
297
|
+
*/ function isValidAgentType(type) {
|
|
236
298
|
return type in agents;
|
|
237
299
|
}
|
|
238
|
-
|
|
300
|
+
/**
|
|
301
|
+
* File system utilities
|
|
302
|
+
*/ /**
|
|
303
|
+
* Check if a file or directory exists
|
|
304
|
+
*/ function exists(filePath) {
|
|
239
305
|
return external_node_fs_.existsSync(filePath);
|
|
240
306
|
}
|
|
241
|
-
|
|
307
|
+
/**
|
|
308
|
+
* Read JSON file
|
|
309
|
+
*/ function readJson(filePath) {
|
|
242
310
|
const content = external_node_fs_.readFileSync(filePath, 'utf-8');
|
|
243
311
|
return JSON.parse(content);
|
|
244
312
|
}
|
|
245
|
-
|
|
313
|
+
/**
|
|
314
|
+
* Write JSON file
|
|
315
|
+
*/ function writeJson(filePath, data, indent = 2) {
|
|
246
316
|
const dir = __WEBPACK_EXTERNAL_MODULE_node_path__.dirname(filePath);
|
|
247
317
|
if (!exists(dir)) external_node_fs_.mkdirSync(dir, {
|
|
248
318
|
recursive: true
|
|
249
319
|
});
|
|
250
320
|
external_node_fs_.writeFileSync(filePath, `${JSON.stringify(data, null, indent)}\n`, 'utf-8');
|
|
251
321
|
}
|
|
252
|
-
|
|
322
|
+
/**
|
|
323
|
+
* Create directory recursively
|
|
324
|
+
*/ function ensureDir(dirPath) {
|
|
253
325
|
if (!exists(dirPath)) external_node_fs_.mkdirSync(dirPath, {
|
|
254
326
|
recursive: true
|
|
255
327
|
});
|
|
256
328
|
}
|
|
257
|
-
|
|
329
|
+
/**
|
|
330
|
+
* Remove file or directory
|
|
331
|
+
*/ function remove(targetPath) {
|
|
258
332
|
if (exists(targetPath)) external_node_fs_.rmSync(targetPath, {
|
|
259
333
|
recursive: true,
|
|
260
334
|
force: true
|
|
261
335
|
});
|
|
262
336
|
}
|
|
263
|
-
|
|
337
|
+
/**
|
|
338
|
+
* Copy directory recursively
|
|
339
|
+
*
|
|
340
|
+
* @param src - Source directory
|
|
341
|
+
* @param dest - Destination directory
|
|
342
|
+
* @param options.exclude - Array of filenames to exclude
|
|
343
|
+
* @param options.excludePrefix - Prefix for files to exclude (e.g., '_' to exclude _private.md)
|
|
344
|
+
*/ function copyDir(src, dest, options) {
|
|
264
345
|
const exclude = options?.exclude || [];
|
|
265
346
|
const excludePrefix = options?.excludePrefix || '_';
|
|
266
347
|
ensureDir(dest);
|
|
@@ -268,6 +349,7 @@ function copyDir(src, dest, options) {
|
|
|
268
349
|
withFileTypes: true
|
|
269
350
|
});
|
|
270
351
|
for (const entry of entries){
|
|
352
|
+
// Skip files in exclude list or starting with excludePrefix
|
|
271
353
|
if (exclude.includes(entry.name) || entry.name.startsWith(excludePrefix)) continue;
|
|
272
354
|
const srcPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(src, entry.name);
|
|
273
355
|
const destPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(dest, entry.name);
|
|
@@ -275,41 +357,62 @@ function copyDir(src, dest, options) {
|
|
|
275
357
|
else external_node_fs_.copyFileSync(srcPath, destPath);
|
|
276
358
|
}
|
|
277
359
|
}
|
|
278
|
-
|
|
360
|
+
/**
|
|
361
|
+
* List directory contents
|
|
362
|
+
*/ function listDir(dirPath) {
|
|
279
363
|
if (!exists(dirPath)) return [];
|
|
280
364
|
return external_node_fs_.readdirSync(dirPath);
|
|
281
365
|
}
|
|
282
|
-
|
|
366
|
+
/**
|
|
367
|
+
* Check if path is a directory
|
|
368
|
+
*/ function isDirectory(targetPath) {
|
|
283
369
|
if (!exists(targetPath)) return false;
|
|
284
370
|
return external_node_fs_.statSync(targetPath).isDirectory();
|
|
285
371
|
}
|
|
286
|
-
|
|
372
|
+
/**
|
|
373
|
+
* Check if path is a symbolic link
|
|
374
|
+
*/ function isSymlink(targetPath) {
|
|
287
375
|
if (!exists(targetPath)) return false;
|
|
288
376
|
return external_node_fs_.lstatSync(targetPath).isSymbolicLink();
|
|
289
377
|
}
|
|
290
|
-
|
|
378
|
+
/**
|
|
379
|
+
* Get real path of symbolic link
|
|
380
|
+
*/ function getRealPath(linkPath) {
|
|
291
381
|
return external_node_fs_.realpathSync(linkPath);
|
|
292
382
|
}
|
|
293
|
-
|
|
383
|
+
/**
|
|
384
|
+
* Get skills.json path for current project
|
|
385
|
+
*/ function getSkillsJsonPath(projectRoot) {
|
|
294
386
|
const root = projectRoot || process.cwd();
|
|
295
387
|
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(root, 'skills.json');
|
|
296
388
|
}
|
|
297
|
-
|
|
389
|
+
/**
|
|
390
|
+
* Get skills.lock path for current project
|
|
391
|
+
*/ function getSkillsLockPath(projectRoot) {
|
|
298
392
|
const root = projectRoot || process.cwd();
|
|
299
393
|
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(root, 'skills.lock');
|
|
300
394
|
}
|
|
301
|
-
|
|
395
|
+
/**
|
|
396
|
+
* Get global cache directory
|
|
397
|
+
*/ function getCacheDir() {
|
|
302
398
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
303
399
|
return process.env.RESKILL_CACHE_DIR || __WEBPACK_EXTERNAL_MODULE_node_path__.join(home, '.reskill-cache');
|
|
304
400
|
}
|
|
305
|
-
|
|
401
|
+
/**
|
|
402
|
+
* Get home directory
|
|
403
|
+
*/ function getHomeDir() {
|
|
306
404
|
return process.env.HOME || process.env.USERPROFILE || '';
|
|
307
405
|
}
|
|
308
|
-
|
|
406
|
+
/**
|
|
407
|
+
* Get global skills installation directory (~/.claude/skills)
|
|
408
|
+
* @deprecated Use getAgentGlobalSkillsDir from agent-registry instead
|
|
409
|
+
*/ function getGlobalSkillsDir() {
|
|
309
410
|
const home = getHomeDir();
|
|
310
411
|
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(home, '.claude', 'skills');
|
|
311
412
|
}
|
|
312
|
-
|
|
413
|
+
/**
|
|
414
|
+
* Shorten path display (replace home directory with ~)
|
|
415
|
+
*/ function shortenPath(fullPath, cwd) {
|
|
313
416
|
const home = getHomeDir();
|
|
314
417
|
const currentDir = cwd || process.cwd();
|
|
315
418
|
if (fullPath.startsWith(home)) return fullPath.replace(home, '~');
|
|
@@ -317,7 +420,29 @@ function shortenPath(fullPath, cwd) {
|
|
|
317
420
|
return fullPath;
|
|
318
421
|
}
|
|
319
422
|
const git_execAsync = (0, __WEBPACK_EXTERNAL_MODULE_node_util__.promisify)(__WEBPACK_EXTERNAL_MODULE_node_child_process__.exec);
|
|
320
|
-
|
|
423
|
+
/**
|
|
424
|
+
* Git utilities
|
|
425
|
+
*/ /**
|
|
426
|
+
* SSH command with auto-accept for new host keys
|
|
427
|
+
* Uses StrictHostKeyChecking=accept-new which:
|
|
428
|
+
* - Automatically accepts keys for hosts not in known_hosts
|
|
429
|
+
* - Still rejects connections if a known host's key has changed (security)
|
|
430
|
+
*/ const GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes';
|
|
431
|
+
/**
|
|
432
|
+
* Get environment variables for git commands that access remote repositories
|
|
433
|
+
* Configures SSH to auto-accept new host keys and disables interactive prompts
|
|
434
|
+
*/ function getGitEnv() {
|
|
435
|
+
return {
|
|
436
|
+
...process.env,
|
|
437
|
+
GIT_SSH_COMMAND,
|
|
438
|
+
// Disable interactive prompts for HTTPS as well
|
|
439
|
+
GIT_TERMINAL_PROMPT: '0'
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Custom error class for Git clone failures
|
|
444
|
+
* Provides helpful tips for private repository authentication
|
|
445
|
+
*/ class GitCloneError extends Error {
|
|
321
446
|
repoUrl;
|
|
322
447
|
originalError;
|
|
323
448
|
isAuthError;
|
|
@@ -332,6 +457,7 @@ class GitCloneError extends Error {
|
|
|
332
457
|
message += '\n - Check ~/.ssh/id_rsa or ~/.ssh/id_ed25519';
|
|
333
458
|
message += '\n - Ensure SSH key is added to your Git hosting service';
|
|
334
459
|
} else {
|
|
460
|
+
// HTTPS or unknown
|
|
335
461
|
message += "\n - Run 'git config --global credential.helper store'";
|
|
336
462
|
message += '\n - Or use a personal access token in the URL';
|
|
337
463
|
}
|
|
@@ -343,12 +469,16 @@ class GitCloneError extends Error {
|
|
|
343
469
|
this.isAuthError = isAuthError;
|
|
344
470
|
this.urlType = urlType;
|
|
345
471
|
}
|
|
346
|
-
|
|
472
|
+
/**
|
|
473
|
+
* Detect URL type from repository URL
|
|
474
|
+
*/ static detectUrlType(url) {
|
|
347
475
|
if (url.startsWith('git@') || url.startsWith('ssh://')) return 'ssh';
|
|
348
476
|
if (url.startsWith('http://') || url.startsWith('https://')) return 'https';
|
|
349
477
|
return 'unknown';
|
|
350
478
|
}
|
|
351
|
-
|
|
479
|
+
/**
|
|
480
|
+
* Check if an error message indicates an authentication problem
|
|
481
|
+
*/ static isAuthenticationError(message) {
|
|
352
482
|
const authPatterns = [
|
|
353
483
|
/permission denied/i,
|
|
354
484
|
/could not read from remote/i,
|
|
@@ -363,14 +493,19 @@ class GitCloneError extends Error {
|
|
|
363
493
|
return authPatterns.some((pattern)=>pattern.test(message));
|
|
364
494
|
}
|
|
365
495
|
}
|
|
366
|
-
|
|
496
|
+
/**
|
|
497
|
+
* Execute git command asynchronously
|
|
498
|
+
*/ async function git(args, cwd) {
|
|
367
499
|
const { stdout } = await git_execAsync(`git ${args.join(' ')}`, {
|
|
368
500
|
cwd,
|
|
369
|
-
encoding: 'utf-8'
|
|
501
|
+
encoding: 'utf-8',
|
|
502
|
+
env: getGitEnv()
|
|
370
503
|
});
|
|
371
504
|
return stdout.trim();
|
|
372
505
|
}
|
|
373
|
-
|
|
506
|
+
/**
|
|
507
|
+
* Get remote tags for a repository
|
|
508
|
+
*/ async function getRemoteTags(repoUrl) {
|
|
374
509
|
try {
|
|
375
510
|
const output = await git([
|
|
376
511
|
'ls-remote',
|
|
@@ -384,6 +519,7 @@ async function getRemoteTags(repoUrl) {
|
|
|
384
519
|
for (const line of lines){
|
|
385
520
|
const [commit, ref] = line.split('\t');
|
|
386
521
|
if (commit && ref) {
|
|
522
|
+
// Extract tag name from refs/tags/v1.0.0
|
|
387
523
|
const tagName = ref.replace('refs/tags/', '');
|
|
388
524
|
tags.push({
|
|
389
525
|
name: tagName,
|
|
@@ -396,9 +532,12 @@ async function getRemoteTags(repoUrl) {
|
|
|
396
532
|
return [];
|
|
397
533
|
}
|
|
398
534
|
}
|
|
399
|
-
|
|
535
|
+
/**
|
|
536
|
+
* Get latest tag from repository
|
|
537
|
+
*/ async function getLatestTag(repoUrl) {
|
|
400
538
|
const tags = await getRemoteTags(repoUrl);
|
|
401
539
|
if (0 === tags.length) return null;
|
|
540
|
+
// Sort by semver (simple version sort)
|
|
402
541
|
const sortedTags = tags.sort((a, b)=>{
|
|
403
542
|
const aVer = a.name.replace(/^v/, '');
|
|
404
543
|
const bVer = b.name.replace(/^v/, '');
|
|
@@ -406,7 +545,11 @@ async function getLatestTag(repoUrl) {
|
|
|
406
545
|
});
|
|
407
546
|
return sortedTags[0];
|
|
408
547
|
}
|
|
409
|
-
|
|
548
|
+
/**
|
|
549
|
+
* Clone a repository with shallow clone
|
|
550
|
+
*
|
|
551
|
+
* @throws {GitCloneError} When clone fails, with helpful tips for authentication issues
|
|
552
|
+
*/ async function clone(repoUrl, destPath, options) {
|
|
410
553
|
const args = [
|
|
411
554
|
'clone'
|
|
412
555
|
];
|
|
@@ -419,13 +562,17 @@ async function clone(repoUrl, destPath, options) {
|
|
|
419
562
|
throw new GitCloneError(repoUrl, error);
|
|
420
563
|
}
|
|
421
564
|
}
|
|
422
|
-
|
|
565
|
+
/**
|
|
566
|
+
* Get current commit hash
|
|
567
|
+
*/ async function getCurrentCommit(cwd) {
|
|
423
568
|
return git([
|
|
424
569
|
'rev-parse',
|
|
425
570
|
'HEAD'
|
|
426
571
|
], cwd);
|
|
427
572
|
}
|
|
428
|
-
|
|
573
|
+
/**
|
|
574
|
+
* Get default branch name
|
|
575
|
+
*/ async function getDefaultBranch(repoUrl) {
|
|
429
576
|
try {
|
|
430
577
|
const output = await git([
|
|
431
578
|
'ls-remote',
|
|
@@ -439,7 +586,9 @@ async function getDefaultBranch(repoUrl) {
|
|
|
439
586
|
return 'main';
|
|
440
587
|
}
|
|
441
588
|
}
|
|
442
|
-
|
|
589
|
+
/**
|
|
590
|
+
* Simple version comparison (for sorting)
|
|
591
|
+
*/ function compareVersions(a, b) {
|
|
443
592
|
const aParts = a.split('.').map((p)=>parseInt(p, 10) || 0);
|
|
444
593
|
const bParts = b.split('.').map((p)=>parseInt(p, 10) || 0);
|
|
445
594
|
const maxLength = Math.max(aParts.length, bParts.length);
|
|
@@ -451,7 +600,10 @@ function compareVersions(a, b) {
|
|
|
451
600
|
}
|
|
452
601
|
return 0;
|
|
453
602
|
}
|
|
454
|
-
|
|
603
|
+
/**
|
|
604
|
+
* Build repository URL from registry and path
|
|
605
|
+
*/ function buildRepoUrl(registry, ownerRepo) {
|
|
606
|
+
// Handle known registries
|
|
455
607
|
const registryUrls = {
|
|
456
608
|
github: 'https://github.com',
|
|
457
609
|
gitlab: 'https://gitlab.com'
|
|
@@ -459,16 +611,40 @@ function buildRepoUrl(registry, ownerRepo) {
|
|
|
459
611
|
const baseUrl = registryUrls[registry] || `https://${registry}`;
|
|
460
612
|
return `${baseUrl}/${ownerRepo}`;
|
|
461
613
|
}
|
|
462
|
-
|
|
614
|
+
/**
|
|
615
|
+
* Check if a source string is a complete Git URL (SSH, HTTPS, or git://)
|
|
616
|
+
*
|
|
617
|
+
* Supported formats:
|
|
618
|
+
* - SSH: git@github.com:user/repo.git
|
|
619
|
+
* - HTTPS: https://github.com/user/repo.git
|
|
620
|
+
* - Git protocol: git://github.com/user/repo.git
|
|
621
|
+
* - URLs ending with .git
|
|
622
|
+
*/ function isGitUrl(source) {
|
|
463
623
|
return source.startsWith('git@') || source.startsWith('git://') || source.startsWith('http://') || source.startsWith('https://') || source.endsWith('.git');
|
|
464
624
|
}
|
|
465
|
-
|
|
625
|
+
/**
|
|
626
|
+
* Parse a Git URL and extract host, owner, and repo information
|
|
627
|
+
*
|
|
628
|
+
* Supports:
|
|
629
|
+
* - SSH: git@github.com:user/repo.git
|
|
630
|
+
* - HTTPS: https://github.com/user/repo.git
|
|
631
|
+
* - Git protocol: git://github.com/user/repo.git
|
|
632
|
+
*
|
|
633
|
+
* Note: GitHub/GitLab web URLs (with /tree/, /blob/, etc.) are handled
|
|
634
|
+
* at a higher level in GitResolver.parseGitUrlRef() before calling this function.
|
|
635
|
+
*
|
|
636
|
+
* @param url The Git URL to parse
|
|
637
|
+
* @returns Parsed URL information or null if parsing fails
|
|
638
|
+
*/ function parseGitUrl(url) {
|
|
639
|
+
// Remove trailing .git if present
|
|
466
640
|
const cleanUrl = url.replace(/\.git$/, '');
|
|
641
|
+
// SSH format: git@github.com:user/repo
|
|
467
642
|
const sshMatch = cleanUrl.match(/^git@([^:]+):(.+)$/);
|
|
468
643
|
if (sshMatch) {
|
|
469
644
|
const [, host, path] = sshMatch;
|
|
470
645
|
const parts = path.split('/');
|
|
471
646
|
if (parts.length >= 2) {
|
|
647
|
+
// Handle nested paths like org/sub/repo
|
|
472
648
|
const owner = parts.slice(0, -1).join('/');
|
|
473
649
|
const repo = parts[parts.length - 1];
|
|
474
650
|
return {
|
|
@@ -480,6 +656,7 @@ function parseGitUrl(url) {
|
|
|
480
656
|
};
|
|
481
657
|
}
|
|
482
658
|
}
|
|
659
|
+
// HTTPS/Git protocol format: https://github.com/user/repo or git://github.com/user/repo
|
|
483
660
|
const httpMatch = cleanUrl.match(/^(https?|git):\/\/([^/]+)\/(.+)$/);
|
|
484
661
|
if (httpMatch) {
|
|
485
662
|
const [, protocol, host, path] = httpMatch;
|
|
@@ -498,50 +675,88 @@ function parseGitUrl(url) {
|
|
|
498
675
|
}
|
|
499
676
|
return null;
|
|
500
677
|
}
|
|
501
|
-
|
|
678
|
+
/**
|
|
679
|
+
* Installer - Multi-Agent installer
|
|
680
|
+
*
|
|
681
|
+
* Supports two installation modes:
|
|
682
|
+
* - symlink: Canonical location (.agents/skills/) + symlinks to each agent directory
|
|
683
|
+
* - copy: Direct copy to each agent directory
|
|
684
|
+
*
|
|
685
|
+
* Reference: https://github.com/vercel-labs/add-skill/blob/main/src/installer.ts
|
|
686
|
+
*/ const installer_AGENTS_DIR = '.agents';
|
|
502
687
|
const installer_SKILLS_SUBDIR = 'skills';
|
|
503
|
-
|
|
688
|
+
/**
|
|
689
|
+
* Default files to exclude when copying skills
|
|
690
|
+
* These files are typically used for repository metadata and should not be copied to agent directories
|
|
691
|
+
*/ const DEFAULT_EXCLUDE_FILES = [
|
|
504
692
|
'README.md',
|
|
505
693
|
'metadata.json',
|
|
506
694
|
'.reskill-commit'
|
|
507
695
|
];
|
|
508
|
-
|
|
509
|
-
|
|
696
|
+
/**
|
|
697
|
+
* Prefix for files that should be excluded (internal/private files)
|
|
698
|
+
*/ const EXCLUDE_PREFIX = '_';
|
|
699
|
+
/**
|
|
700
|
+
* Sanitize filename to prevent path traversal attacks
|
|
701
|
+
*/ function installer_sanitizeName(name) {
|
|
702
|
+
// Remove path separators and special characters
|
|
510
703
|
let sanitized = name.replace(/[/\\:\0]/g, '');
|
|
704
|
+
// Remove leading and trailing dots and spaces
|
|
511
705
|
sanitized = sanitized.replace(/^[.\s]+|[.\s]+$/g, '');
|
|
706
|
+
// Remove leading dots
|
|
512
707
|
sanitized = sanitized.replace(/^\.+/, '');
|
|
513
708
|
if (!sanitized || 0 === sanitized.length) sanitized = 'unnamed-skill';
|
|
514
709
|
if (sanitized.length > 255) sanitized = sanitized.substring(0, 255);
|
|
515
710
|
return sanitized;
|
|
516
711
|
}
|
|
517
|
-
|
|
712
|
+
/**
|
|
713
|
+
* Validate path safety
|
|
714
|
+
*/ function installer_isPathSafe(basePath, targetPath) {
|
|
518
715
|
const normalizedBase = __WEBPACK_EXTERNAL_MODULE_node_path__.normalize(__WEBPACK_EXTERNAL_MODULE_node_path__.resolve(basePath));
|
|
519
716
|
const normalizedTarget = __WEBPACK_EXTERNAL_MODULE_node_path__.normalize(__WEBPACK_EXTERNAL_MODULE_node_path__.resolve(targetPath));
|
|
520
717
|
return normalizedTarget.startsWith(normalizedBase + __WEBPACK_EXTERNAL_MODULE_node_path__.sep) || normalizedTarget === normalizedBase;
|
|
521
718
|
}
|
|
522
|
-
|
|
719
|
+
/**
|
|
720
|
+
* Get canonical skills directory path
|
|
721
|
+
*
|
|
722
|
+
* @param isGlobal - Whether installing globally
|
|
723
|
+
* @param cwd - Current working directory
|
|
724
|
+
* @param installDir - Custom installation directory (relative to cwd), overrides default
|
|
725
|
+
*/ function installer_getCanonicalSkillsDir(isGlobal, cwd, installDir) {
|
|
523
726
|
const baseDir = isGlobal ? (0, __WEBPACK_EXTERNAL_MODULE_node_os__.homedir)() : cwd || process.cwd();
|
|
727
|
+
// Use custom installDir if provided, otherwise use default
|
|
524
728
|
if (installDir && !isGlobal) return __WEBPACK_EXTERNAL_MODULE_node_path__.join(baseDir, installDir);
|
|
525
729
|
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(baseDir, installer_AGENTS_DIR, installer_SKILLS_SUBDIR);
|
|
526
730
|
}
|
|
527
|
-
|
|
731
|
+
/**
|
|
732
|
+
* Ensure directory exists
|
|
733
|
+
*/ function installer_ensureDir(dirPath) {
|
|
528
734
|
if (!external_node_fs_.existsSync(dirPath)) external_node_fs_.mkdirSync(dirPath, {
|
|
529
735
|
recursive: true
|
|
530
736
|
});
|
|
531
737
|
}
|
|
532
|
-
|
|
738
|
+
/**
|
|
739
|
+
* Remove file or directory
|
|
740
|
+
*/ function installer_remove(targetPath) {
|
|
533
741
|
if (external_node_fs_.existsSync(targetPath)) external_node_fs_.rmSync(targetPath, {
|
|
534
742
|
recursive: true,
|
|
535
743
|
force: true
|
|
536
744
|
});
|
|
537
745
|
}
|
|
538
|
-
|
|
746
|
+
/**
|
|
747
|
+
* Copy directory with file exclusion
|
|
748
|
+
*
|
|
749
|
+
* By default excludes:
|
|
750
|
+
* - Files in DEFAULT_EXCLUDE_FILES (README.md, metadata.json, .reskill-commit)
|
|
751
|
+
* - Files starting with EXCLUDE_PREFIX ('_')
|
|
752
|
+
*/ function copyDirectory(src, dest, options) {
|
|
539
753
|
const exclude = new Set(options?.exclude || DEFAULT_EXCLUDE_FILES);
|
|
540
754
|
installer_ensureDir(dest);
|
|
541
755
|
const entries = external_node_fs_.readdirSync(src, {
|
|
542
756
|
withFileTypes: true
|
|
543
757
|
});
|
|
544
758
|
for (const entry of entries){
|
|
759
|
+
// Skip files starting with EXCLUDE_PREFIX and files in exclude list
|
|
545
760
|
if (exclude.has(entry.name) || entry.name.startsWith(EXCLUDE_PREFIX)) continue;
|
|
546
761
|
const srcPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(src, entry.name);
|
|
547
762
|
const destPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(dest, entry.name);
|
|
@@ -549,8 +764,13 @@ function copyDirectory(src, dest, options) {
|
|
|
549
764
|
else external_node_fs_.copyFileSync(srcPath, destPath);
|
|
550
765
|
}
|
|
551
766
|
}
|
|
552
|
-
|
|
767
|
+
/**
|
|
768
|
+
* Create symbolic link
|
|
769
|
+
*
|
|
770
|
+
* @returns true if successful, false if needs to fallback to copy
|
|
771
|
+
*/ async function installer_createSymlink(target, linkPath) {
|
|
553
772
|
try {
|
|
773
|
+
// Check existing link
|
|
554
774
|
try {
|
|
555
775
|
const stats = external_node_fs_.lstatSync(linkPath);
|
|
556
776
|
if (stats.isSymbolicLink()) {
|
|
@@ -561,17 +781,24 @@ async function installer_createSymlink(target, linkPath) {
|
|
|
561
781
|
recursive: true
|
|
562
782
|
});
|
|
563
783
|
} catch (err) {
|
|
784
|
+
// ELOOP = circular symlink, ENOENT = does not exist
|
|
564
785
|
if (err && 'object' == typeof err && 'code' in err) {
|
|
565
786
|
if ('ELOOP' === err.code) try {
|
|
566
787
|
external_node_fs_.rmSync(linkPath, {
|
|
567
788
|
force: true
|
|
568
789
|
});
|
|
569
|
-
} catch {
|
|
790
|
+
} catch {
|
|
791
|
+
// If unable to delete, symlink creation will fail and trigger copy fallback
|
|
792
|
+
}
|
|
570
793
|
}
|
|
794
|
+
// For ENOENT or other errors, continue trying to create symlink
|
|
571
795
|
}
|
|
796
|
+
// Ensure parent directory exists
|
|
572
797
|
const linkDir = __WEBPACK_EXTERNAL_MODULE_node_path__.dirname(linkPath);
|
|
573
798
|
installer_ensureDir(linkDir);
|
|
799
|
+
// Calculate relative path
|
|
574
800
|
const relativePath = __WEBPACK_EXTERNAL_MODULE_node_path__.relative(linkDir, target);
|
|
801
|
+
// Windows uses junction, other systems use default
|
|
575
802
|
const symlinkType = 'win32' === (0, __WEBPACK_EXTERNAL_MODULE_node_os__.platform)() ? 'junction' : void 0;
|
|
576
803
|
external_node_fs_.symlinkSync(relativePath, linkPath, symlinkType);
|
|
577
804
|
return true;
|
|
@@ -579,7 +806,9 @@ async function installer_createSymlink(target, linkPath) {
|
|
|
579
806
|
return false;
|
|
580
807
|
}
|
|
581
808
|
}
|
|
582
|
-
|
|
809
|
+
/**
|
|
810
|
+
* Installer class - Multi-Agent installer
|
|
811
|
+
*/ class Installer {
|
|
583
812
|
cwd;
|
|
584
813
|
isGlobal;
|
|
585
814
|
installDir;
|
|
@@ -588,25 +817,39 @@ class Installer {
|
|
|
588
817
|
this.isGlobal = options.global || false;
|
|
589
818
|
this.installDir = options.installDir;
|
|
590
819
|
}
|
|
591
|
-
|
|
820
|
+
/**
|
|
821
|
+
* Get canonical installation path
|
|
822
|
+
*/ getCanonicalPath(skillName) {
|
|
592
823
|
const sanitized = installer_sanitizeName(skillName);
|
|
593
824
|
const canonicalBase = installer_getCanonicalSkillsDir(this.isGlobal, this.cwd, this.installDir);
|
|
594
825
|
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(canonicalBase, sanitized);
|
|
595
826
|
}
|
|
596
|
-
|
|
827
|
+
/**
|
|
828
|
+
* Get agent's skill installation path
|
|
829
|
+
*/ getAgentSkillPath(skillName, agentType) {
|
|
597
830
|
const agent = getAgentConfig(agentType);
|
|
598
831
|
const sanitized = installer_sanitizeName(skillName);
|
|
599
832
|
const agentBase = this.isGlobal ? agent.globalSkillsDir : __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.cwd, agent.skillsDir);
|
|
600
833
|
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(agentBase, sanitized);
|
|
601
834
|
}
|
|
602
|
-
|
|
835
|
+
/**
|
|
836
|
+
* Install skill to specified agent
|
|
837
|
+
*
|
|
838
|
+
* @param sourcePath - Skill source directory path
|
|
839
|
+
* @param skillName - Skill name
|
|
840
|
+
* @param agentType - Target agent type
|
|
841
|
+
* @param options - Installation options
|
|
842
|
+
*/ async installForAgent(sourcePath, skillName, agentType, options = {}) {
|
|
603
843
|
const agent = getAgentConfig(agentType);
|
|
604
844
|
const installMode = options.mode || 'symlink';
|
|
605
845
|
const sanitized = installer_sanitizeName(skillName);
|
|
846
|
+
// Canonical location
|
|
606
847
|
const canonicalBase = installer_getCanonicalSkillsDir(this.isGlobal, this.cwd, this.installDir);
|
|
607
848
|
const canonicalDir = __WEBPACK_EXTERNAL_MODULE_node_path__.join(canonicalBase, sanitized);
|
|
849
|
+
// Agent specific location
|
|
608
850
|
const agentBase = this.isGlobal ? agent.globalSkillsDir : __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.cwd, agent.skillsDir);
|
|
609
851
|
const agentDir = __WEBPACK_EXTERNAL_MODULE_node_path__.join(agentBase, sanitized);
|
|
852
|
+
// Validate path safety
|
|
610
853
|
if (!installer_isPathSafe(canonicalBase, canonicalDir)) return {
|
|
611
854
|
success: false,
|
|
612
855
|
path: agentDir,
|
|
@@ -620,6 +863,7 @@ class Installer {
|
|
|
620
863
|
error: 'Invalid skill name: potential path traversal detected'
|
|
621
864
|
};
|
|
622
865
|
try {
|
|
866
|
+
// Copy mode: directly copy to agent location
|
|
623
867
|
if ('copy' === installMode) {
|
|
624
868
|
installer_ensureDir(agentDir);
|
|
625
869
|
installer_remove(agentDir);
|
|
@@ -630,14 +874,18 @@ class Installer {
|
|
|
630
874
|
mode: 'copy'
|
|
631
875
|
};
|
|
632
876
|
}
|
|
877
|
+
// Symlink mode: copy to canonical location, then create symlink
|
|
633
878
|
installer_ensureDir(canonicalDir);
|
|
634
879
|
installer_remove(canonicalDir);
|
|
635
880
|
copyDirectory(sourcePath, canonicalDir);
|
|
636
881
|
const symlinkCreated = await installer_createSymlink(canonicalDir, agentDir);
|
|
637
882
|
if (!symlinkCreated) {
|
|
883
|
+
// Symlink failed, fallback to copy
|
|
638
884
|
try {
|
|
639
885
|
installer_remove(agentDir);
|
|
640
|
-
} catch {
|
|
886
|
+
} catch {
|
|
887
|
+
// Ignore cleanup errors
|
|
888
|
+
}
|
|
641
889
|
installer_ensureDir(agentDir);
|
|
642
890
|
copyDirectory(sourcePath, agentDir);
|
|
643
891
|
return {
|
|
@@ -663,7 +911,9 @@ class Installer {
|
|
|
663
911
|
};
|
|
664
912
|
}
|
|
665
913
|
}
|
|
666
|
-
|
|
914
|
+
/**
|
|
915
|
+
* Install skill to multiple agents
|
|
916
|
+
*/ async installToAgents(sourcePath, skillName, targetAgents, options = {}) {
|
|
667
917
|
const results = new Map();
|
|
668
918
|
for (const agent of targetAgents){
|
|
669
919
|
const result = await this.installForAgent(sourcePath, skillName, agent, options);
|
|
@@ -671,24 +921,33 @@ class Installer {
|
|
|
671
921
|
}
|
|
672
922
|
return results;
|
|
673
923
|
}
|
|
674
|
-
|
|
924
|
+
/**
|
|
925
|
+
* Check if skill is installed to specified agent
|
|
926
|
+
*/ isInstalled(skillName, agentType) {
|
|
675
927
|
const skillPath = this.getAgentSkillPath(skillName, agentType);
|
|
676
928
|
return external_node_fs_.existsSync(skillPath);
|
|
677
929
|
}
|
|
678
|
-
|
|
930
|
+
/**
|
|
931
|
+
* Uninstall skill from specified agent
|
|
932
|
+
*/ uninstallFromAgent(skillName, agentType) {
|
|
679
933
|
const skillPath = this.getAgentSkillPath(skillName, agentType);
|
|
680
934
|
if (!external_node_fs_.existsSync(skillPath)) return false;
|
|
681
935
|
installer_remove(skillPath);
|
|
682
936
|
return true;
|
|
683
937
|
}
|
|
684
|
-
|
|
938
|
+
/**
|
|
939
|
+
* Uninstall skill from multiple agents
|
|
940
|
+
*/ uninstallFromAgents(skillName, targetAgents) {
|
|
685
941
|
const results = new Map();
|
|
686
942
|
for (const agent of targetAgents)results.set(agent, this.uninstallFromAgent(skillName, agent));
|
|
943
|
+
// Also delete canonical location
|
|
687
944
|
const canonicalPath = this.getCanonicalPath(skillName);
|
|
688
945
|
if (external_node_fs_.existsSync(canonicalPath)) installer_remove(canonicalPath);
|
|
689
946
|
return results;
|
|
690
947
|
}
|
|
691
|
-
|
|
948
|
+
/**
|
|
949
|
+
* Get all skills installed to specified agent
|
|
950
|
+
*/ listInstalledSkills(agentType) {
|
|
692
951
|
const agent = getAgentConfig(agentType);
|
|
693
952
|
const skillsDir = this.isGlobal ? agent.globalSkillsDir : __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.cwd, agent.skillsDir);
|
|
694
953
|
if (!external_node_fs_.existsSync(skillsDir)) return [];
|
|
@@ -697,49 +956,95 @@ class Installer {
|
|
|
697
956
|
}).filter((entry)=>entry.isDirectory() || entry.isSymbolicLink()).map((entry)=>entry.name);
|
|
698
957
|
}
|
|
699
958
|
}
|
|
700
|
-
|
|
959
|
+
/**
|
|
960
|
+
* CacheManager - Manage global skill cache
|
|
961
|
+
*
|
|
962
|
+
* Cache directory structure:
|
|
963
|
+
* ~/.reskill-cache/
|
|
964
|
+
* ├── github/ # Shorthand format registry
|
|
965
|
+
* │ └── user/
|
|
966
|
+
* │ └── skill/
|
|
967
|
+
* │ ├── v1.0.0/
|
|
968
|
+
* │ └── v1.1.0/
|
|
969
|
+
* ├── github.com/ # Git URL format, using host as directory
|
|
970
|
+
* │ └── user/
|
|
971
|
+
* │ └── private-skill/
|
|
972
|
+
* │ └── v1.0.0/
|
|
973
|
+
* └── gitlab.company.com/ # Private GitLab instance
|
|
974
|
+
* └── team/
|
|
975
|
+
* └── skill/
|
|
976
|
+
* └── v2.0.0/
|
|
977
|
+
*
|
|
978
|
+
* For Git URL format (SSH/HTTPS):
|
|
979
|
+
* - git@github.com:user/repo.git -> github.com/user/repo/version
|
|
980
|
+
* - https://gitlab.company.com/team/skill.git -> gitlab.company.com/team/skill/version
|
|
981
|
+
*/ class CacheManager {
|
|
701
982
|
cacheDir;
|
|
702
983
|
constructor(cacheDir){
|
|
703
984
|
this.cacheDir = cacheDir || getCacheDir();
|
|
704
985
|
}
|
|
705
|
-
|
|
986
|
+
/**
|
|
987
|
+
* Get cache directory
|
|
988
|
+
*/ getCacheDir() {
|
|
706
989
|
return this.cacheDir;
|
|
707
990
|
}
|
|
708
|
-
|
|
991
|
+
/**
|
|
992
|
+
* Get skill path in cache
|
|
993
|
+
*
|
|
994
|
+
* For different reference formats, cache paths are:
|
|
995
|
+
* - github:user/repo@v1.0.0 -> ~/.reskill-cache/github/user/repo/v1.0.0
|
|
996
|
+
* - git@github.com:user/repo.git@v1.0.0 -> ~/.reskill-cache/github.com/user/repo/v1.0.0
|
|
997
|
+
* - https://gitlab.company.com/team/skill.git@v2.0.0 -> ~/.reskill-cache/gitlab.company.com/team/skill/v2.0.0
|
|
998
|
+
*/ getSkillCachePath(parsed, version) {
|
|
709
999
|
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.cacheDir, parsed.registry, parsed.owner, parsed.repo, version);
|
|
710
1000
|
}
|
|
711
|
-
|
|
1001
|
+
/**
|
|
1002
|
+
* Get cache path (alias for getSkillCachePath)
|
|
1003
|
+
*/ getCachePath(parsed, version) {
|
|
712
1004
|
return this.getSkillCachePath(parsed, version);
|
|
713
1005
|
}
|
|
714
|
-
|
|
1006
|
+
/**
|
|
1007
|
+
* Check if skill is cached
|
|
1008
|
+
*/ isCached(parsed, version) {
|
|
715
1009
|
const cachePath = this.getSkillCachePath(parsed, version);
|
|
716
1010
|
return exists(cachePath) && isDirectory(cachePath);
|
|
717
1011
|
}
|
|
718
|
-
|
|
1012
|
+
/**
|
|
1013
|
+
* Get cached skill
|
|
1014
|
+
*/ async get(parsed, version) {
|
|
719
1015
|
const cachePath = this.getSkillCachePath(parsed, version);
|
|
720
1016
|
if (!this.isCached(parsed, version)) return null;
|
|
1017
|
+
// Read cached commit info
|
|
721
1018
|
const commitFile = __WEBPACK_EXTERNAL_MODULE_node_path__.join(cachePath, '.reskill-commit');
|
|
722
1019
|
let commit = '';
|
|
723
1020
|
try {
|
|
724
1021
|
const fs = await import("node:fs");
|
|
725
1022
|
if (exists(commitFile)) commit = fs.readFileSync(commitFile, 'utf-8').trim();
|
|
726
|
-
} catch {
|
|
1023
|
+
} catch {
|
|
1024
|
+
// Ignore read errors
|
|
1025
|
+
}
|
|
727
1026
|
return {
|
|
728
1027
|
path: cachePath,
|
|
729
1028
|
commit
|
|
730
1029
|
};
|
|
731
1030
|
}
|
|
732
|
-
|
|
1031
|
+
/**
|
|
1032
|
+
* Cache skill
|
|
1033
|
+
*/ async cache(repoUrl, parsed, ref, version) {
|
|
733
1034
|
const cachePath = this.getSkillCachePath(parsed, version);
|
|
1035
|
+
// If exists, delete first
|
|
734
1036
|
if (exists(cachePath)) remove(cachePath);
|
|
735
1037
|
ensureDir(__WEBPACK_EXTERNAL_MODULE_node_path__.dirname(cachePath));
|
|
1038
|
+
// Clone repository
|
|
736
1039
|
const tempPath = `${cachePath}.tmp`;
|
|
737
1040
|
remove(tempPath);
|
|
738
1041
|
await clone(repoUrl, tempPath, {
|
|
739
1042
|
depth: 1,
|
|
740
1043
|
branch: ref
|
|
741
1044
|
});
|
|
1045
|
+
// Get commit hash
|
|
742
1046
|
const commit = await getCurrentCommit(tempPath);
|
|
1047
|
+
// If has subPath, only keep subdirectory
|
|
743
1048
|
if (parsed.subPath) {
|
|
744
1049
|
const subDir = __WEBPACK_EXTERNAL_MODULE_node_path__.join(tempPath, parsed.subPath);
|
|
745
1050
|
if (!exists(subDir)) {
|
|
@@ -756,35 +1061,51 @@ class CacheManager {
|
|
|
756
1061
|
'.git'
|
|
757
1062
|
]
|
|
758
1063
|
});
|
|
1064
|
+
// Save commit info
|
|
759
1065
|
const fs = await import("node:fs");
|
|
760
1066
|
fs.writeFileSync(__WEBPACK_EXTERNAL_MODULE_node_path__.join(cachePath, '.reskill-commit'), commit);
|
|
1067
|
+
// Clean up temp directory
|
|
761
1068
|
remove(tempPath);
|
|
762
1069
|
return {
|
|
763
1070
|
path: cachePath,
|
|
764
1071
|
commit
|
|
765
1072
|
};
|
|
766
1073
|
}
|
|
767
|
-
|
|
1074
|
+
/**
|
|
1075
|
+
* Copy from cache to target directory
|
|
1076
|
+
*
|
|
1077
|
+
* Uses the same exclude rules as Installer to ensure consistency:
|
|
1078
|
+
* - DEFAULT_EXCLUDE_FILES (README.md, metadata.json, .reskill-commit)
|
|
1079
|
+
*/ async copyTo(parsed, version, destPath) {
|
|
768
1080
|
const cached = await this.get(parsed, version);
|
|
769
1081
|
if (!cached) throw new Error(`Skill ${parsed.raw} version ${version} not found in cache`);
|
|
1082
|
+
// If target exists, delete first
|
|
770
1083
|
if (exists(destPath)) remove(destPath);
|
|
1084
|
+
// Use same exclude rules as Installer for consistency
|
|
771
1085
|
copyDir(cached.path, destPath, {
|
|
772
1086
|
exclude: DEFAULT_EXCLUDE_FILES
|
|
773
1087
|
});
|
|
774
1088
|
}
|
|
775
|
-
|
|
1089
|
+
/**
|
|
1090
|
+
* Clear cache for specific skill
|
|
1091
|
+
*/ clearSkill(parsed, version) {
|
|
776
1092
|
if (version) {
|
|
777
1093
|
const cachePath = this.getSkillCachePath(parsed, version);
|
|
778
1094
|
remove(cachePath);
|
|
779
1095
|
} else {
|
|
1096
|
+
// Clear all versions
|
|
780
1097
|
const skillDir = __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.cacheDir, parsed.registry, parsed.owner, parsed.repo);
|
|
781
1098
|
remove(skillDir);
|
|
782
1099
|
}
|
|
783
1100
|
}
|
|
784
|
-
|
|
1101
|
+
/**
|
|
1102
|
+
* Clear all cache
|
|
1103
|
+
*/ clearAll() {
|
|
785
1104
|
remove(this.cacheDir);
|
|
786
1105
|
}
|
|
787
|
-
|
|
1106
|
+
/**
|
|
1107
|
+
* Get cache statistics
|
|
1108
|
+
*/ getStats() {
|
|
788
1109
|
if (!exists(this.cacheDir)) return {
|
|
789
1110
|
totalSkills: 0,
|
|
790
1111
|
registries: []
|
|
@@ -805,11 +1126,20 @@ class CacheManager {
|
|
|
805
1126
|
registries
|
|
806
1127
|
};
|
|
807
1128
|
}
|
|
808
|
-
|
|
1129
|
+
/**
|
|
1130
|
+
* Get the remote commit hash for a specific ref without cloning
|
|
1131
|
+
*
|
|
1132
|
+
* Uses `git ls-remote` to fetch the commit hash efficiently.
|
|
1133
|
+
*
|
|
1134
|
+
* @param repoUrl - Repository URL
|
|
1135
|
+
* @param ref - Git reference (branch, tag, or commit)
|
|
1136
|
+
* @returns Commit hash string
|
|
1137
|
+
*/ async getRemoteCommit(repoUrl, ref) {
|
|
809
1138
|
const { exec } = await import("node:child_process");
|
|
810
1139
|
const { promisify } = await import("node:util");
|
|
811
1140
|
const execAsync = promisify(exec);
|
|
812
1141
|
try {
|
|
1142
|
+
// Try to get commit for the ref
|
|
813
1143
|
const { stdout } = await execAsync(`git ls-remote ${repoUrl} ${ref}`, {
|
|
814
1144
|
encoding: 'utf-8'
|
|
815
1145
|
});
|
|
@@ -817,6 +1147,7 @@ class CacheManager {
|
|
|
817
1147
|
const [commit] = stdout.trim().split('\t');
|
|
818
1148
|
return commit;
|
|
819
1149
|
}
|
|
1150
|
+
// If ref is not found directly, try refs/heads/ and refs/tags/
|
|
820
1151
|
const { stdout: allRefs } = await execAsync(`git ls-remote ${repoUrl}`, {
|
|
821
1152
|
encoding: 'utf-8'
|
|
822
1153
|
});
|
|
@@ -825,28 +1156,59 @@ class CacheManager {
|
|
|
825
1156
|
const [commit, refPath] = line.split('\t');
|
|
826
1157
|
if (refPath === `refs/heads/${ref}` || refPath === `refs/tags/${ref}` || refPath === ref) return commit;
|
|
827
1158
|
}
|
|
1159
|
+
// If still not found, return empty string (will trigger update)
|
|
828
1160
|
return '';
|
|
829
1161
|
} catch {
|
|
1162
|
+
// On error, return empty string to trigger update
|
|
830
1163
|
return '';
|
|
831
1164
|
}
|
|
832
1165
|
}
|
|
833
1166
|
}
|
|
834
|
-
|
|
1167
|
+
// ============================================================================
|
|
1168
|
+
// Constants
|
|
1169
|
+
// ============================================================================
|
|
1170
|
+
/**
|
|
1171
|
+
* Default skills.json configuration template
|
|
1172
|
+
*/ const DEFAULT_SKILLS_JSON = {
|
|
835
1173
|
skills: {},
|
|
836
1174
|
defaults: {
|
|
837
1175
|
installDir: '.skills'
|
|
838
1176
|
}
|
|
839
1177
|
};
|
|
840
|
-
|
|
1178
|
+
/**
|
|
1179
|
+
* Default values for SkillsDefaults fields
|
|
1180
|
+
*/ const DEFAULT_VALUES = {
|
|
841
1181
|
installDir: '.skills',
|
|
842
1182
|
targetAgents: [],
|
|
843
1183
|
installMode: 'symlink'
|
|
844
1184
|
};
|
|
845
|
-
|
|
1185
|
+
/**
|
|
1186
|
+
* Well-known registry URLs
|
|
1187
|
+
*/ const DEFAULT_REGISTRIES = {
|
|
846
1188
|
github: 'https://github.com',
|
|
847
1189
|
gitlab: 'https://gitlab.com'
|
|
848
1190
|
};
|
|
849
|
-
|
|
1191
|
+
// ============================================================================
|
|
1192
|
+
// ConfigLoader Class
|
|
1193
|
+
// ============================================================================
|
|
1194
|
+
/**
|
|
1195
|
+
* ConfigLoader - Load and manage skills.json configuration
|
|
1196
|
+
*
|
|
1197
|
+
* Handles reading, writing, and managing the project's skills.json file.
|
|
1198
|
+
* Provides methods for:
|
|
1199
|
+
* - Loading/saving configuration
|
|
1200
|
+
* - Managing skill dependencies
|
|
1201
|
+
* - Managing default settings (registry, installDir, targetAgents, installMode)
|
|
1202
|
+
*
|
|
1203
|
+
* @example
|
|
1204
|
+
* ```ts
|
|
1205
|
+
* const config = new ConfigLoader();
|
|
1206
|
+
* if (config.exists()) {
|
|
1207
|
+
* const defaults = config.getDefaults();
|
|
1208
|
+
* console.log(defaults.targetAgents);
|
|
1209
|
+
* }
|
|
1210
|
+
* ```
|
|
1211
|
+
*/ class ConfigLoader {
|
|
850
1212
|
projectRoot;
|
|
851
1213
|
configPath;
|
|
852
1214
|
config = null;
|
|
@@ -854,20 +1216,38 @@ class ConfigLoader {
|
|
|
854
1216
|
this.projectRoot = projectRoot ?? process.cwd();
|
|
855
1217
|
this.configPath = getSkillsJsonPath(this.projectRoot);
|
|
856
1218
|
}
|
|
857
|
-
|
|
1219
|
+
// ==========================================================================
|
|
1220
|
+
// Path Accessors
|
|
1221
|
+
// ==========================================================================
|
|
1222
|
+
/**
|
|
1223
|
+
* Get project root directory
|
|
1224
|
+
*/ getProjectRoot() {
|
|
858
1225
|
return this.projectRoot;
|
|
859
1226
|
}
|
|
860
|
-
|
|
1227
|
+
/**
|
|
1228
|
+
* Get configuration file path
|
|
1229
|
+
*/ getConfigPath() {
|
|
861
1230
|
return this.configPath;
|
|
862
1231
|
}
|
|
863
|
-
|
|
1232
|
+
/**
|
|
1233
|
+
* Get installation directory (resolved absolute path)
|
|
1234
|
+
*/ getInstallDir() {
|
|
864
1235
|
const { installDir } = this.getDefaults();
|
|
865
1236
|
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.projectRoot, installDir);
|
|
866
1237
|
}
|
|
867
|
-
|
|
1238
|
+
// ==========================================================================
|
|
1239
|
+
// File Operations
|
|
1240
|
+
// ==========================================================================
|
|
1241
|
+
/**
|
|
1242
|
+
* Check if configuration file exists
|
|
1243
|
+
*/ exists() {
|
|
868
1244
|
return exists(this.configPath);
|
|
869
1245
|
}
|
|
870
|
-
|
|
1246
|
+
/**
|
|
1247
|
+
* Load configuration from file
|
|
1248
|
+
*
|
|
1249
|
+
* @throws Error if file doesn't exist or is invalid JSON
|
|
1250
|
+
*/ load() {
|
|
871
1251
|
if (this.config) return this.config;
|
|
872
1252
|
if (!this.exists()) throw new Error(`skills.json not found in ${this.projectRoot}. Run 'reskill init' first.`);
|
|
873
1253
|
try {
|
|
@@ -877,22 +1257,37 @@ class ConfigLoader {
|
|
|
877
1257
|
throw new Error(`Failed to parse skills.json: ${error.message}`);
|
|
878
1258
|
}
|
|
879
1259
|
}
|
|
880
|
-
|
|
1260
|
+
/**
|
|
1261
|
+
* Reload configuration from file (ignores cache)
|
|
1262
|
+
*/ reload() {
|
|
881
1263
|
this.config = null;
|
|
882
1264
|
return this.load();
|
|
883
1265
|
}
|
|
884
|
-
|
|
1266
|
+
/**
|
|
1267
|
+
* Save configuration to file
|
|
1268
|
+
*
|
|
1269
|
+
* @param config - Configuration to save (uses cached config if not provided)
|
|
1270
|
+
* @throws Error if no configuration to save
|
|
1271
|
+
*/ save(config) {
|
|
885
1272
|
const toSave = config ?? this.config;
|
|
886
1273
|
if (!toSave) throw new Error('No config to save');
|
|
887
1274
|
writeJson(this.configPath, toSave);
|
|
888
1275
|
this.config = toSave;
|
|
889
1276
|
}
|
|
890
|
-
|
|
1277
|
+
/**
|
|
1278
|
+
* Ensure skills.json exists, create with defaults if not
|
|
1279
|
+
*
|
|
1280
|
+
* @returns true if file was created, false if it already existed
|
|
1281
|
+
*/ ensureExists() {
|
|
891
1282
|
if (this.exists()) return false;
|
|
892
1283
|
this.create();
|
|
893
1284
|
return true;
|
|
894
1285
|
}
|
|
895
|
-
|
|
1286
|
+
/**
|
|
1287
|
+
* Create new configuration file with defaults
|
|
1288
|
+
*
|
|
1289
|
+
* @param options - Optional overrides for default configuration
|
|
1290
|
+
*/ create(options) {
|
|
896
1291
|
const config = {
|
|
897
1292
|
...DEFAULT_SKILLS_JSON,
|
|
898
1293
|
...options,
|
|
@@ -905,7 +1300,15 @@ class ConfigLoader {
|
|
|
905
1300
|
this.save(config);
|
|
906
1301
|
return config;
|
|
907
1302
|
}
|
|
908
|
-
|
|
1303
|
+
// ==========================================================================
|
|
1304
|
+
// Defaults Management
|
|
1305
|
+
// ==========================================================================
|
|
1306
|
+
/**
|
|
1307
|
+
* Get default configuration values
|
|
1308
|
+
*
|
|
1309
|
+
* Returns a complete defaults object with all fields populated.
|
|
1310
|
+
* Uses stored values if available, falls back to defaults.
|
|
1311
|
+
*/ getDefaults() {
|
|
909
1312
|
const config = this.getConfigOrDefault();
|
|
910
1313
|
const storedDefaults = config.defaults ?? {};
|
|
911
1314
|
return {
|
|
@@ -914,7 +1317,13 @@ class ConfigLoader {
|
|
|
914
1317
|
installMode: storedDefaults.installMode ?? DEFAULT_VALUES.installMode
|
|
915
1318
|
};
|
|
916
1319
|
}
|
|
917
|
-
|
|
1320
|
+
/**
|
|
1321
|
+
* Update default configuration values
|
|
1322
|
+
*
|
|
1323
|
+
* Merges the provided updates with existing defaults and saves to file.
|
|
1324
|
+
*
|
|
1325
|
+
* @param updates - Partial defaults to merge
|
|
1326
|
+
*/ updateDefaults(updates) {
|
|
918
1327
|
this.ensureConfigLoaded();
|
|
919
1328
|
if (this.config) {
|
|
920
1329
|
this.config.defaults = {
|
|
@@ -924,20 +1333,42 @@ class ConfigLoader {
|
|
|
924
1333
|
this.save();
|
|
925
1334
|
}
|
|
926
1335
|
}
|
|
927
|
-
|
|
1336
|
+
// ==========================================================================
|
|
1337
|
+
// Registry Management
|
|
1338
|
+
// ==========================================================================
|
|
1339
|
+
/**
|
|
1340
|
+
* Get registry URL by name
|
|
1341
|
+
*
|
|
1342
|
+
* Resolution order:
|
|
1343
|
+
* 1. Custom registries defined in skills.json
|
|
1344
|
+
* 2. Well-known registries (github, gitlab)
|
|
1345
|
+
* 3. Assumes it's a custom domain (https://{registryName})
|
|
1346
|
+
*/ getRegistryUrl(registryName) {
|
|
928
1347
|
const config = this.getConfigOrDefault();
|
|
1348
|
+
// Check custom registries
|
|
929
1349
|
if (config.registries?.[registryName]) return config.registries[registryName];
|
|
1350
|
+
// Check well-known registries
|
|
930
1351
|
if (DEFAULT_REGISTRIES[registryName]) return DEFAULT_REGISTRIES[registryName];
|
|
1352
|
+
// Assume it's a custom domain
|
|
931
1353
|
return `https://${registryName}`;
|
|
932
1354
|
}
|
|
933
|
-
|
|
1355
|
+
// ==========================================================================
|
|
1356
|
+
// Skills Management
|
|
1357
|
+
// ==========================================================================
|
|
1358
|
+
/**
|
|
1359
|
+
* Add skill to configuration
|
|
1360
|
+
*/ addSkill(name, ref) {
|
|
934
1361
|
this.ensureConfigLoaded();
|
|
935
1362
|
if (this.config) {
|
|
936
1363
|
this.config.skills[name] = ref;
|
|
937
1364
|
this.save();
|
|
938
1365
|
}
|
|
939
1366
|
}
|
|
940
|
-
|
|
1367
|
+
/**
|
|
1368
|
+
* Remove skill from configuration
|
|
1369
|
+
*
|
|
1370
|
+
* @returns true if skill was removed, false if it didn't exist
|
|
1371
|
+
*/ removeSkill(name) {
|
|
941
1372
|
this.ensureConfigLoaded();
|
|
942
1373
|
if (this.config?.skills[name]) {
|
|
943
1374
|
delete this.config.skills[name];
|
|
@@ -946,7 +1377,9 @@ class ConfigLoader {
|
|
|
946
1377
|
}
|
|
947
1378
|
return false;
|
|
948
1379
|
}
|
|
949
|
-
|
|
1380
|
+
/**
|
|
1381
|
+
* Get all skills as a shallow copy
|
|
1382
|
+
*/ getSkills() {
|
|
950
1383
|
if (!this.config) {
|
|
951
1384
|
if (!this.exists()) return {};
|
|
952
1385
|
this.load();
|
|
@@ -955,41 +1388,85 @@ class ConfigLoader {
|
|
|
955
1388
|
...this.config?.skills
|
|
956
1389
|
};
|
|
957
1390
|
}
|
|
958
|
-
|
|
1391
|
+
/**
|
|
1392
|
+
* Check if skill exists in configuration
|
|
1393
|
+
*/ hasSkill(name) {
|
|
959
1394
|
const skills = this.getSkills();
|
|
960
1395
|
return name in skills;
|
|
961
1396
|
}
|
|
962
|
-
|
|
1397
|
+
/**
|
|
1398
|
+
* Get skill reference by name
|
|
1399
|
+
*/ getSkillRef(name) {
|
|
963
1400
|
const skills = this.getSkills();
|
|
964
1401
|
return skills[name];
|
|
965
1402
|
}
|
|
966
|
-
|
|
1403
|
+
// ==========================================================================
|
|
1404
|
+
// Private Helpers
|
|
1405
|
+
// ==========================================================================
|
|
1406
|
+
/**
|
|
1407
|
+
* Get loaded config or default (does not throw)
|
|
1408
|
+
*/ getConfigOrDefault() {
|
|
967
1409
|
if (this.config) return this.config;
|
|
968
1410
|
if (this.exists()) return this.load();
|
|
969
1411
|
return DEFAULT_SKILLS_JSON;
|
|
970
1412
|
}
|
|
971
|
-
|
|
1413
|
+
/**
|
|
1414
|
+
* Ensure config is loaded into memory
|
|
1415
|
+
*/ ensureConfigLoaded() {
|
|
972
1416
|
if (!this.config) this.load();
|
|
973
1417
|
}
|
|
974
1418
|
}
|
|
975
|
-
|
|
1419
|
+
/**
|
|
1420
|
+
* GitResolver - Parse skill references and versions
|
|
1421
|
+
*
|
|
1422
|
+
* Reference formats:
|
|
1423
|
+
* Full: <registry>:<owner>/<repo>@<version>
|
|
1424
|
+
* Short: <owner>/<repo>@<version>
|
|
1425
|
+
* Git URL: git@github.com:user/repo.git[@version]
|
|
1426
|
+
* HTTPS: https://github.com/user/repo.git[@version]
|
|
1427
|
+
*
|
|
1428
|
+
* Version formats:
|
|
1429
|
+
* - @v1.0.0 Exact version
|
|
1430
|
+
* - @latest Latest tag
|
|
1431
|
+
* - @^2.0.0 Semver range
|
|
1432
|
+
* - @branch:dev Branch
|
|
1433
|
+
* - @commit:abc Commit hash
|
|
1434
|
+
* - (none) Default branch
|
|
1435
|
+
*/ class GitResolver {
|
|
976
1436
|
defaultRegistry = 'github';
|
|
977
|
-
|
|
1437
|
+
/**
|
|
1438
|
+
* Parse skill reference string
|
|
1439
|
+
*
|
|
1440
|
+
* Supported formats:
|
|
1441
|
+
* - Short: owner/repo[@version]
|
|
1442
|
+
* - Full: registry:owner/repo[@version]
|
|
1443
|
+
* - SSH URL: git@github.com:user/repo.git[@version]
|
|
1444
|
+
* - HTTPS URL: https://github.com/user/repo.git[@version]
|
|
1445
|
+
* - Monorepo: git@github.com:org/repo.git/subpath[@version]
|
|
1446
|
+
*/ parseRef(ref) {
|
|
978
1447
|
const raw = ref;
|
|
1448
|
+
// First check if it's a Git URL (SSH, HTTPS, git://)
|
|
1449
|
+
// For Git URLs, need special handling for version separator
|
|
1450
|
+
// Format: git@host:user/repo.git[@version] or git@host:user/repo.git/subpath[@version]
|
|
979
1451
|
if (isGitUrl(ref)) return this.parseGitUrlRef(ref);
|
|
1452
|
+
// Standard format parsing for non-Git URLs
|
|
980
1453
|
let remaining = ref;
|
|
981
1454
|
let registry = this.defaultRegistry;
|
|
982
1455
|
let version;
|
|
1456
|
+
// Check for registry prefix (github:, gitlab:, custom.com:)
|
|
983
1457
|
const registryMatch = remaining.match(/^([a-zA-Z0-9.-]+):(.+)$/);
|
|
984
1458
|
if (registryMatch) {
|
|
985
1459
|
registry = registryMatch[1];
|
|
986
1460
|
remaining = registryMatch[2];
|
|
987
1461
|
}
|
|
1462
|
+
// Separate version part
|
|
988
1463
|
const atIndex = remaining.lastIndexOf('@');
|
|
989
1464
|
if (atIndex > 0) {
|
|
990
1465
|
version = remaining.slice(atIndex + 1);
|
|
991
1466
|
remaining = remaining.slice(0, atIndex);
|
|
992
1467
|
}
|
|
1468
|
+
// Parse owner/repo and possible subPath
|
|
1469
|
+
// E.g.: user/repo or org/monorepo/skills/pdf
|
|
993
1470
|
const parts = remaining.split('/');
|
|
994
1471
|
if (parts.length < 2) throw new Error(`Invalid skill reference: ${ref}. Expected format: owner/repo[@version]`);
|
|
995
1472
|
const owner = parts[0];
|
|
@@ -1004,16 +1481,30 @@ class GitResolver {
|
|
|
1004
1481
|
raw
|
|
1005
1482
|
};
|
|
1006
1483
|
}
|
|
1007
|
-
|
|
1484
|
+
/**
|
|
1485
|
+
* Parse Git URL format reference
|
|
1486
|
+
*
|
|
1487
|
+
* Supported formats:
|
|
1488
|
+
* - git@github.com:user/repo.git
|
|
1489
|
+
* - git@github.com:user/repo.git@v1.0.0
|
|
1490
|
+
* - git@github.com:user/repo.git/subpath@v1.0.0
|
|
1491
|
+
* - https://github.com/user/repo.git
|
|
1492
|
+
* - https://github.com/user/repo.git@v1.0.0
|
|
1493
|
+
* - https://github.com/user/repo/tree/branch/path (GitHub web URL)
|
|
1494
|
+
*/ parseGitUrlRef(ref) {
|
|
1008
1495
|
const raw = ref;
|
|
1009
1496
|
let gitUrl = ref;
|
|
1010
1497
|
let version;
|
|
1011
1498
|
let subPath;
|
|
1499
|
+
// Check for GitHub/GitLab web URL format: https://github.com/user/repo/tree/branch/path
|
|
1012
1500
|
const webUrlMatch = ref.match(/^(https?:\/\/[^/]+)\/([^/]+)\/([^/]+)\/(tree|blob|raw)\/([^/]+)(?:\/(.+))?$/);
|
|
1013
1501
|
if (webUrlMatch) {
|
|
1014
1502
|
const [, baseUrl, owner, repo, , branch, path] = webUrlMatch;
|
|
1503
|
+
// Build standard Git URL
|
|
1015
1504
|
gitUrl = `${baseUrl}/${owner}/${repo}.git`;
|
|
1505
|
+
// Extract branch as version
|
|
1016
1506
|
version = `branch:${branch}`;
|
|
1507
|
+
// Extract subpath
|
|
1017
1508
|
subPath = path;
|
|
1018
1509
|
return {
|
|
1019
1510
|
registry: new URL(baseUrl).hostname,
|
|
@@ -1025,25 +1516,33 @@ class GitResolver {
|
|
|
1025
1516
|
gitUrl
|
|
1026
1517
|
};
|
|
1027
1518
|
}
|
|
1519
|
+
// For URLs ending with .git, first check for /subpath@version or @version
|
|
1520
|
+
// Format: url.git/subpath@version or url.git@version
|
|
1028
1521
|
const gitSuffixIndex = ref.indexOf('.git');
|
|
1029
1522
|
if (-1 !== gitSuffixIndex) {
|
|
1030
1523
|
const afterGit = ref.slice(gitSuffixIndex + 4);
|
|
1031
1524
|
if (afterGit) {
|
|
1525
|
+
// Check version (@version)
|
|
1032
1526
|
const atIndex = afterGit.lastIndexOf('@');
|
|
1033
1527
|
if (-1 !== atIndex) {
|
|
1034
1528
|
version = afterGit.slice(atIndex + 1);
|
|
1035
1529
|
const pathPart = afterGit.slice(0, atIndex);
|
|
1036
1530
|
if (pathPart.startsWith('/')) subPath = pathPart.slice(1);
|
|
1037
1531
|
} else if (afterGit.startsWith('/')) subPath = afterGit.slice(1);
|
|
1532
|
+
// Extract clean Git URL (without subpath and version)
|
|
1038
1533
|
gitUrl = ref.slice(0, gitSuffixIndex + 4);
|
|
1039
1534
|
}
|
|
1040
1535
|
} else {
|
|
1536
|
+
// URL without .git suffix, try to separate version
|
|
1041
1537
|
const atIndex = ref.lastIndexOf('@');
|
|
1538
|
+
// For SSH URL, @ at the beginning is normal (git@...), need to skip
|
|
1042
1539
|
if (atIndex > 4) {
|
|
1540
|
+
// Make sure it's not the @ in git@host
|
|
1043
1541
|
version = ref.slice(atIndex + 1);
|
|
1044
1542
|
gitUrl = ref.slice(0, atIndex);
|
|
1045
1543
|
}
|
|
1046
1544
|
}
|
|
1545
|
+
// Parse Git URL to get host, owner, repo
|
|
1047
1546
|
const parsed = parseGitUrl(gitUrl);
|
|
1048
1547
|
if (!parsed) throw new Error(`Invalid Git URL: ${ref}. Expected format: git@host:owner/repo.git or https://host/owner/repo.git`);
|
|
1049
1548
|
return {
|
|
@@ -1056,53 +1555,71 @@ class GitResolver {
|
|
|
1056
1555
|
gitUrl
|
|
1057
1556
|
};
|
|
1058
1557
|
}
|
|
1059
|
-
|
|
1558
|
+
/**
|
|
1559
|
+
* Parse version specification
|
|
1560
|
+
*/ parseVersion(versionSpec) {
|
|
1060
1561
|
if (!versionSpec) return {
|
|
1061
1562
|
type: 'branch',
|
|
1062
1563
|
value: 'main',
|
|
1063
1564
|
raw: ''
|
|
1064
1565
|
};
|
|
1065
1566
|
const raw = versionSpec;
|
|
1567
|
+
// latest
|
|
1066
1568
|
if ('latest' === versionSpec) return {
|
|
1067
1569
|
type: 'latest',
|
|
1068
1570
|
value: 'latest',
|
|
1069
1571
|
raw
|
|
1070
1572
|
};
|
|
1573
|
+
// branch:xxx
|
|
1071
1574
|
if (versionSpec.startsWith('branch:')) return {
|
|
1072
1575
|
type: 'branch',
|
|
1073
1576
|
value: versionSpec.slice(7),
|
|
1074
1577
|
raw
|
|
1075
1578
|
};
|
|
1579
|
+
// commit:xxx
|
|
1076
1580
|
if (versionSpec.startsWith('commit:')) return {
|
|
1077
1581
|
type: 'commit',
|
|
1078
1582
|
value: versionSpec.slice(7),
|
|
1079
1583
|
raw
|
|
1080
1584
|
};
|
|
1585
|
+
// semver range (^, ~, >, <, etc.)
|
|
1081
1586
|
if (/^[\^~><]/.test(versionSpec)) return {
|
|
1082
1587
|
type: 'range',
|
|
1083
1588
|
value: versionSpec,
|
|
1084
1589
|
raw
|
|
1085
1590
|
};
|
|
1591
|
+
// exact version (v1.0.0 or 1.0.0)
|
|
1086
1592
|
return {
|
|
1087
1593
|
type: 'exact',
|
|
1088
1594
|
value: versionSpec,
|
|
1089
1595
|
raw
|
|
1090
1596
|
};
|
|
1091
1597
|
}
|
|
1092
|
-
|
|
1598
|
+
/**
|
|
1599
|
+
* Build repository URL
|
|
1600
|
+
*
|
|
1601
|
+
* If parsed contains gitUrl, return it directly;
|
|
1602
|
+
* Otherwise build HTTPS URL from registry and owner/repo
|
|
1603
|
+
*/ buildRepoUrl(parsed) {
|
|
1604
|
+
// If has complete Git URL, return directly
|
|
1093
1605
|
if (parsed.gitUrl) return parsed.gitUrl;
|
|
1094
1606
|
return buildRepoUrl(parsed.registry, `${parsed.owner}/${parsed.repo}`);
|
|
1095
1607
|
}
|
|
1096
|
-
|
|
1608
|
+
/**
|
|
1609
|
+
* Resolve version and get specific ref (tag name or commit)
|
|
1610
|
+
*/ async resolveVersion(repoUrl, versionSpec) {
|
|
1097
1611
|
switch(versionSpec.type){
|
|
1098
1612
|
case 'exact':
|
|
1613
|
+
// Use specified tag directly
|
|
1099
1614
|
return {
|
|
1100
1615
|
ref: versionSpec.value
|
|
1101
1616
|
};
|
|
1102
1617
|
case 'latest':
|
|
1103
1618
|
{
|
|
1619
|
+
// Get latest tag
|
|
1104
1620
|
const latestTag = await getLatestTag(repoUrl);
|
|
1105
1621
|
if (!latestTag) {
|
|
1622
|
+
// No tag, use default branch
|
|
1106
1623
|
const defaultBranch = await getDefaultBranch(repoUrl);
|
|
1107
1624
|
return {
|
|
1108
1625
|
ref: defaultBranch
|
|
@@ -1115,12 +1632,14 @@ class GitResolver {
|
|
|
1115
1632
|
}
|
|
1116
1633
|
case 'range':
|
|
1117
1634
|
{
|
|
1635
|
+
// Get all tags, find latest version satisfying semver range
|
|
1118
1636
|
const tags = await getRemoteTags(repoUrl);
|
|
1119
1637
|
const matchingTags = tags.filter((tag)=>{
|
|
1120
1638
|
const version = tag.name.replace(/^v/, '');
|
|
1121
1639
|
return __WEBPACK_EXTERNAL_MODULE_semver__.satisfies(version, versionSpec.value);
|
|
1122
1640
|
});
|
|
1123
1641
|
if (0 === matchingTags.length) throw new Error(`No version found matching ${versionSpec.raw} for ${repoUrl}`);
|
|
1642
|
+
// Sort by version, get latest
|
|
1124
1643
|
matchingTags.sort((a, b)=>{
|
|
1125
1644
|
const aVer = a.name.replace(/^v/, '');
|
|
1126
1645
|
const bVer = b.name.replace(/^v/, '');
|
|
@@ -1144,7 +1663,9 @@ class GitResolver {
|
|
|
1144
1663
|
throw new Error(`Unknown version type: ${versionSpec.type}`);
|
|
1145
1664
|
}
|
|
1146
1665
|
}
|
|
1147
|
-
|
|
1666
|
+
/**
|
|
1667
|
+
* Full resolution: from reference string to clone-ready information
|
|
1668
|
+
*/ async resolve(ref) {
|
|
1148
1669
|
const parsed = this.parseRef(ref);
|
|
1149
1670
|
const repoUrl = this.buildRepoUrl(parsed);
|
|
1150
1671
|
const versionSpec = this.parseVersion(parsed.version);
|
|
@@ -1157,8 +1678,14 @@ class GitResolver {
|
|
|
1157
1678
|
};
|
|
1158
1679
|
}
|
|
1159
1680
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1681
|
+
/**
|
|
1682
|
+
* Current lockfile version
|
|
1683
|
+
*/ const LOCKFILE_VERSION = 1;
|
|
1684
|
+
/**
|
|
1685
|
+
* LockManager - Manage skills.lock file
|
|
1686
|
+
*
|
|
1687
|
+
* Used for locking exact versions to ensure team consistency
|
|
1688
|
+
*/ class LockManager {
|
|
1162
1689
|
projectRoot;
|
|
1163
1690
|
lockPath;
|
|
1164
1691
|
lockData = null;
|
|
@@ -1166,15 +1693,22 @@ class LockManager {
|
|
|
1166
1693
|
this.projectRoot = projectRoot || process.cwd();
|
|
1167
1694
|
this.lockPath = getSkillsLockPath(this.projectRoot);
|
|
1168
1695
|
}
|
|
1169
|
-
|
|
1696
|
+
/**
|
|
1697
|
+
* Get lock file path
|
|
1698
|
+
*/ getLockPath() {
|
|
1170
1699
|
return this.lockPath;
|
|
1171
1700
|
}
|
|
1172
|
-
|
|
1701
|
+
/**
|
|
1702
|
+
* Check if lock file exists
|
|
1703
|
+
*/ exists() {
|
|
1173
1704
|
return exists(this.lockPath);
|
|
1174
1705
|
}
|
|
1175
|
-
|
|
1706
|
+
/**
|
|
1707
|
+
* Load lock file
|
|
1708
|
+
*/ load() {
|
|
1176
1709
|
if (this.lockData) return this.lockData;
|
|
1177
1710
|
if (!this.exists()) {
|
|
1711
|
+
// If not exists, create empty lock
|
|
1178
1712
|
this.lockData = {
|
|
1179
1713
|
lockfileVersion: LOCKFILE_VERSION,
|
|
1180
1714
|
skills: {}
|
|
@@ -1188,26 +1722,36 @@ class LockManager {
|
|
|
1188
1722
|
throw new Error(`Failed to parse skills.lock: ${error.message}`);
|
|
1189
1723
|
}
|
|
1190
1724
|
}
|
|
1191
|
-
|
|
1725
|
+
/**
|
|
1726
|
+
* Reload lock file
|
|
1727
|
+
*/ reload() {
|
|
1192
1728
|
this.lockData = null;
|
|
1193
1729
|
return this.load();
|
|
1194
1730
|
}
|
|
1195
|
-
|
|
1731
|
+
/**
|
|
1732
|
+
* Save lock file
|
|
1733
|
+
*/ save(lockToSave) {
|
|
1196
1734
|
const toSave = lockToSave || this.lockData;
|
|
1197
1735
|
if (!toSave) throw new Error('No lock to save');
|
|
1198
1736
|
writeJson(this.lockPath, toSave);
|
|
1199
1737
|
this.lockData = toSave;
|
|
1200
1738
|
}
|
|
1201
|
-
|
|
1739
|
+
/**
|
|
1740
|
+
* Get locked skill
|
|
1741
|
+
*/ get(name) {
|
|
1202
1742
|
const lock = this.load();
|
|
1203
1743
|
return lock.skills[name];
|
|
1204
1744
|
}
|
|
1205
|
-
|
|
1745
|
+
/**
|
|
1746
|
+
* Set locked skill
|
|
1747
|
+
*/ set(name, skill) {
|
|
1206
1748
|
const lock = this.load();
|
|
1207
1749
|
lock.skills[name] = skill;
|
|
1208
1750
|
this.save();
|
|
1209
1751
|
}
|
|
1210
|
-
|
|
1752
|
+
/**
|
|
1753
|
+
* Remove locked skill
|
|
1754
|
+
*/ remove(name) {
|
|
1211
1755
|
const lock = this.load();
|
|
1212
1756
|
if (lock.skills[name]) {
|
|
1213
1757
|
delete lock.skills[name];
|
|
@@ -1216,7 +1760,9 @@ class LockManager {
|
|
|
1216
1760
|
}
|
|
1217
1761
|
return false;
|
|
1218
1762
|
}
|
|
1219
|
-
|
|
1763
|
+
/**
|
|
1764
|
+
* Lock a skill
|
|
1765
|
+
*/ lockSkill(name, options) {
|
|
1220
1766
|
const lockedSkill = {
|
|
1221
1767
|
source: options.source,
|
|
1222
1768
|
version: options.version,
|
|
@@ -1228,29 +1774,39 @@ class LockManager {
|
|
|
1228
1774
|
this.set(name, lockedSkill);
|
|
1229
1775
|
return lockedSkill;
|
|
1230
1776
|
}
|
|
1231
|
-
|
|
1777
|
+
/**
|
|
1778
|
+
* Get all locked skills
|
|
1779
|
+
*/ getAll() {
|
|
1232
1780
|
const lock = this.load();
|
|
1233
1781
|
return {
|
|
1234
1782
|
...lock.skills
|
|
1235
1783
|
};
|
|
1236
1784
|
}
|
|
1237
|
-
|
|
1785
|
+
/**
|
|
1786
|
+
* Check if skill is locked
|
|
1787
|
+
*/ has(name) {
|
|
1238
1788
|
const lock = this.load();
|
|
1239
1789
|
return name in lock.skills;
|
|
1240
1790
|
}
|
|
1241
|
-
|
|
1791
|
+
/**
|
|
1792
|
+
* Check if locked version matches current version
|
|
1793
|
+
*/ isVersionMatch(name, version) {
|
|
1242
1794
|
const locked = this.get(name);
|
|
1243
1795
|
if (!locked) return false;
|
|
1244
1796
|
return locked.version === version;
|
|
1245
1797
|
}
|
|
1246
|
-
|
|
1798
|
+
/**
|
|
1799
|
+
* Clear all locks
|
|
1800
|
+
*/ clear() {
|
|
1247
1801
|
this.lockData = {
|
|
1248
1802
|
lockfileVersion: LOCKFILE_VERSION,
|
|
1249
1803
|
skills: {}
|
|
1250
1804
|
};
|
|
1251
1805
|
this.save();
|
|
1252
1806
|
}
|
|
1253
|
-
|
|
1807
|
+
/**
|
|
1808
|
+
* Delete lock file
|
|
1809
|
+
*/ delete() {
|
|
1254
1810
|
if (this.exists()) {
|
|
1255
1811
|
const fs = __webpack_require__("node:fs");
|
|
1256
1812
|
fs.unlinkSync(this.lockPath);
|
|
@@ -1258,7 +1814,16 @@ class LockManager {
|
|
|
1258
1814
|
this.lockData = null;
|
|
1259
1815
|
}
|
|
1260
1816
|
}
|
|
1261
|
-
|
|
1817
|
+
/**
|
|
1818
|
+
* SkillManager - Core Skill management class
|
|
1819
|
+
*
|
|
1820
|
+
* Integrates GitResolver, CacheManager, ConfigLoader, LockManager
|
|
1821
|
+
* Provides complete skill installation, update, and uninstall functionality
|
|
1822
|
+
*
|
|
1823
|
+
* Installation directories:
|
|
1824
|
+
* - Project mode (default): .skills/ or directory configured in skills.json
|
|
1825
|
+
* - Global mode (-g): ~/.claude/skills/
|
|
1826
|
+
*/ class SkillManager {
|
|
1262
1827
|
projectRoot;
|
|
1263
1828
|
resolver;
|
|
1264
1829
|
cache;
|
|
@@ -1273,40 +1838,70 @@ class SkillManager {
|
|
|
1273
1838
|
this.cache = new CacheManager();
|
|
1274
1839
|
this.resolver = new GitResolver();
|
|
1275
1840
|
}
|
|
1276
|
-
|
|
1841
|
+
/**
|
|
1842
|
+
* Check if in global mode
|
|
1843
|
+
*/ isGlobalMode() {
|
|
1277
1844
|
return this.isGlobal;
|
|
1278
1845
|
}
|
|
1279
|
-
|
|
1846
|
+
/**
|
|
1847
|
+
* Get project root directory
|
|
1848
|
+
*/ getProjectRoot() {
|
|
1280
1849
|
return this.projectRoot;
|
|
1281
1850
|
}
|
|
1282
|
-
|
|
1851
|
+
/**
|
|
1852
|
+
* Get legacy installation directory (for backward compatibility)
|
|
1853
|
+
*
|
|
1854
|
+
* - Global mode: ~/.claude/skills/
|
|
1855
|
+
* - Project mode: .skills/ or directory configured in skills.json
|
|
1856
|
+
*/ getInstallDir() {
|
|
1283
1857
|
if (this.isGlobal) return getGlobalSkillsDir();
|
|
1284
1858
|
return this.config.getInstallDir();
|
|
1285
1859
|
}
|
|
1286
|
-
|
|
1860
|
+
/**
|
|
1861
|
+
* Get canonical skills directory
|
|
1862
|
+
*
|
|
1863
|
+
* This is the primary storage location used by installToAgents().
|
|
1864
|
+
* - Project mode: .agents/skills/
|
|
1865
|
+
* - Global mode: ~/.agents/skills/
|
|
1866
|
+
*/ getCanonicalSkillsDir() {
|
|
1287
1867
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
1288
1868
|
const baseDir = this.isGlobal ? home : this.projectRoot;
|
|
1289
1869
|
return __WEBPACK_EXTERNAL_MODULE_node_path__.join(baseDir, '.agents', 'skills');
|
|
1290
1870
|
}
|
|
1291
|
-
|
|
1871
|
+
/**
|
|
1872
|
+
* Get skill installation path
|
|
1873
|
+
*
|
|
1874
|
+
* Checks canonical location first, then falls back to configured installDir.
|
|
1875
|
+
*/ getSkillPath(name) {
|
|
1876
|
+
// Check canonical location first (.agents/skills/)
|
|
1292
1877
|
const canonicalPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.getCanonicalSkillsDir(), name);
|
|
1293
1878
|
if (exists(canonicalPath)) return canonicalPath;
|
|
1879
|
+
// Check configured installation directory (.skills/ or custom)
|
|
1294
1880
|
const installDir = this.getInstallDir();
|
|
1295
1881
|
const installPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(installDir, name);
|
|
1296
1882
|
if (exists(installPath)) return installPath;
|
|
1883
|
+
// Default to configured installation directory for new installations
|
|
1884
|
+
// if it's not the default .skills, otherwise use canonical location.
|
|
1885
|
+
// This respects "installDir" in skills.json.
|
|
1297
1886
|
const defaults = this.config.getDefaults();
|
|
1298
1887
|
if ('.skills' !== defaults.installDir && !this.isGlobal) return installPath;
|
|
1888
|
+
// Default to canonical location for new installations
|
|
1299
1889
|
return canonicalPath;
|
|
1300
1890
|
}
|
|
1301
|
-
|
|
1891
|
+
/**
|
|
1892
|
+
* Install skill
|
|
1893
|
+
*/ async install(ref, options = {}) {
|
|
1302
1894
|
const { force = false, save = true } = options;
|
|
1895
|
+
// Parse reference
|
|
1303
1896
|
const resolved = await this.resolver.resolve(ref);
|
|
1304
1897
|
const { parsed, repoUrl } = resolved;
|
|
1305
|
-
const gitRef = resolved.ref;
|
|
1898
|
+
const gitRef = resolved.ref; // Git reference (tag, branch, commit)
|
|
1306
1899
|
const skillName = parsed.subPath ? __WEBPACK_EXTERNAL_MODULE_node_path__.basename(parsed.subPath) : parsed.repo;
|
|
1307
1900
|
const skillPath = this.getSkillPath(skillName);
|
|
1901
|
+
// Check if already installed
|
|
1308
1902
|
if (exists(skillPath) && !force) {
|
|
1309
1903
|
const locked = this.lockManager.get(skillName);
|
|
1904
|
+
// Compare ref if available, fallback to version for backward compatibility
|
|
1310
1905
|
const lockedRef = locked?.ref || locked?.version;
|
|
1311
1906
|
if (locked && lockedRef === gitRef) {
|
|
1312
1907
|
logger_logger.info(`${skillName}@${gitRef} is already installed`);
|
|
@@ -1320,21 +1915,27 @@ class SkillManager {
|
|
|
1320
1915
|
}
|
|
1321
1916
|
}
|
|
1322
1917
|
logger_logger["package"](`Installing ${skillName}@${gitRef}...`);
|
|
1918
|
+
// Check cache
|
|
1323
1919
|
let cacheResult = await this.cache.get(parsed, gitRef);
|
|
1324
1920
|
if (cacheResult) logger_logger.debug(`Using cached ${skillName}@${gitRef}`);
|
|
1325
1921
|
else {
|
|
1326
1922
|
logger_logger.debug(`Caching ${skillName}@${gitRef} from ${repoUrl}`);
|
|
1327
1923
|
cacheResult = await this.cache.cache(repoUrl, parsed, gitRef, gitRef);
|
|
1328
1924
|
}
|
|
1925
|
+
// Copy to installation directory
|
|
1329
1926
|
ensureDir(this.getInstallDir());
|
|
1330
1927
|
if (exists(skillPath)) remove(skillPath);
|
|
1331
1928
|
await this.cache.copyTo(parsed, gitRef, skillPath);
|
|
1332
|
-
|
|
1929
|
+
// Read semantic version from skill.json
|
|
1930
|
+
let semanticVersion = gitRef; // fallback to gitRef if no skill.json
|
|
1333
1931
|
const skillJsonPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(skillPath, 'skill.json');
|
|
1334
1932
|
if (exists(skillJsonPath)) try {
|
|
1335
1933
|
const skillJson = readJson(skillJsonPath);
|
|
1336
1934
|
if (skillJson.version) semanticVersion = skillJson.version;
|
|
1337
|
-
} catch {
|
|
1935
|
+
} catch {
|
|
1936
|
+
// Ignore parse errors, use gitRef as fallback
|
|
1937
|
+
}
|
|
1938
|
+
// Update lock file (project mode only)
|
|
1338
1939
|
if (!this.isGlobal) this.lockManager.lockSkill(skillName, {
|
|
1339
1940
|
source: `${parsed.registry}:${parsed.owner}/${parsed.repo}${parsed.subPath ? `/${parsed.subPath}` : ''}`,
|
|
1340
1941
|
version: semanticVersion,
|
|
@@ -1342,6 +1943,7 @@ class SkillManager {
|
|
|
1342
1943
|
resolved: repoUrl,
|
|
1343
1944
|
commit: cacheResult.commit
|
|
1344
1945
|
});
|
|
1946
|
+
// Update skills.json (project mode only)
|
|
1345
1947
|
if (!this.isGlobal && save) {
|
|
1346
1948
|
this.config.ensureExists();
|
|
1347
1949
|
this.config.addSkill(skillName, ref);
|
|
@@ -1353,7 +1955,9 @@ class SkillManager {
|
|
|
1353
1955
|
if (!installed) throw new Error(`Failed to get installed skill info for ${skillName}`);
|
|
1354
1956
|
return installed;
|
|
1355
1957
|
}
|
|
1356
|
-
|
|
1958
|
+
/**
|
|
1959
|
+
* Install all skills from skills.json
|
|
1960
|
+
*/ async installAll(options = {}) {
|
|
1357
1961
|
const skills = this.config.getSkills();
|
|
1358
1962
|
const installed = [];
|
|
1359
1963
|
for (const [name, ref] of Object.entries(skills))try {
|
|
@@ -1367,33 +1971,50 @@ class SkillManager {
|
|
|
1367
1971
|
}
|
|
1368
1972
|
return installed;
|
|
1369
1973
|
}
|
|
1370
|
-
|
|
1974
|
+
/**
|
|
1975
|
+
* Uninstall skill
|
|
1976
|
+
*/ uninstall(name) {
|
|
1371
1977
|
const skillPath = this.getSkillPath(name);
|
|
1372
1978
|
if (!exists(skillPath)) {
|
|
1373
1979
|
const location = this.isGlobal ? '(global)' : '';
|
|
1374
1980
|
logger_logger.warn(`Skill ${name} is not installed ${location}`.trim());
|
|
1375
1981
|
return false;
|
|
1376
1982
|
}
|
|
1983
|
+
// Remove installation directory
|
|
1377
1984
|
remove(skillPath);
|
|
1985
|
+
// Remove from lock file (project mode only)
|
|
1378
1986
|
if (!this.isGlobal) this.lockManager.remove(name);
|
|
1987
|
+
// Remove from skills.json (project mode only)
|
|
1379
1988
|
if (!this.isGlobal && this.config.exists()) this.config.removeSkill(name);
|
|
1380
1989
|
const locationHint = this.isGlobal ? '(global)' : '';
|
|
1381
1990
|
logger_logger.success(`Uninstalled ${name} ${locationHint}`.trim());
|
|
1382
1991
|
return true;
|
|
1383
1992
|
}
|
|
1384
|
-
|
|
1993
|
+
/**
|
|
1994
|
+
* Check if a skill needs to be updated by comparing local and remote commits
|
|
1995
|
+
*
|
|
1996
|
+
* @param name - Skill name
|
|
1997
|
+
* @param remoteCommit - Remote commit hash to compare against
|
|
1998
|
+
* @returns true if update is needed, false if already up to date
|
|
1999
|
+
*/ checkNeedsUpdate(name, remoteCommit) {
|
|
1385
2000
|
const locked = this.lockManager.get(name);
|
|
2001
|
+
// No lock info or no commit hash means we need to update
|
|
1386
2002
|
if (!locked?.commit) return true;
|
|
2003
|
+
// Compare commits
|
|
1387
2004
|
return locked.commit !== remoteCommit;
|
|
1388
2005
|
}
|
|
1389
|
-
|
|
2006
|
+
/**
|
|
2007
|
+
* Update skill
|
|
2008
|
+
*/ async update(name) {
|
|
1390
2009
|
const updated = [];
|
|
1391
2010
|
if (name) {
|
|
2011
|
+
// Update single skill
|
|
1392
2012
|
const ref = this.config.getSkillRef(name);
|
|
1393
2013
|
if (!ref) {
|
|
1394
2014
|
logger_logger.error(`Skill ${name} not found in skills.json`);
|
|
1395
2015
|
return [];
|
|
1396
2016
|
}
|
|
2017
|
+
// Check if update is needed by getting remote commit first
|
|
1397
2018
|
const resolved = await this.resolver.resolve(ref);
|
|
1398
2019
|
const remoteCommit = await this.cache.getRemoteCommit(resolved.repoUrl, resolved.ref);
|
|
1399
2020
|
if (!this.checkNeedsUpdate(name, remoteCommit)) {
|
|
@@ -1406,8 +2027,10 @@ class SkillManager {
|
|
|
1406
2027
|
});
|
|
1407
2028
|
updated.push(skill);
|
|
1408
2029
|
} else {
|
|
2030
|
+
// Update all
|
|
1409
2031
|
const skills = this.config.getSkills();
|
|
1410
2032
|
for (const [skillName, ref] of Object.entries(skills))try {
|
|
2033
|
+
// Check if update is needed
|
|
1411
2034
|
const resolved = await this.resolver.resolve(ref);
|
|
1412
2035
|
const remoteCommit = await this.cache.getRemoteCommit(resolved.repoUrl, resolved.ref);
|
|
1413
2036
|
if (!this.checkNeedsUpdate(skillName, remoteCommit)) {
|
|
@@ -1425,9 +2048,14 @@ class SkillManager {
|
|
|
1425
2048
|
}
|
|
1426
2049
|
return updated;
|
|
1427
2050
|
}
|
|
1428
|
-
|
|
2051
|
+
/**
|
|
2052
|
+
* List installed skills
|
|
2053
|
+
*
|
|
2054
|
+
* Checks both canonical (.agents/skills/) and legacy (.skills/) locations.
|
|
2055
|
+
*/ list() {
|
|
1429
2056
|
const skills = [];
|
|
1430
2057
|
const seenNames = new Set();
|
|
2058
|
+
// Check canonical location first (.agents/skills/)
|
|
1431
2059
|
const canonicalDir = this.getCanonicalSkillsDir();
|
|
1432
2060
|
if (exists(canonicalDir)) for (const name of listDir(canonicalDir)){
|
|
1433
2061
|
const skillPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(canonicalDir, name);
|
|
@@ -1438,15 +2066,20 @@ class SkillManager {
|
|
|
1438
2066
|
seenNames.add(name);
|
|
1439
2067
|
}
|
|
1440
2068
|
}
|
|
2069
|
+
// Check legacy location (.skills/)
|
|
1441
2070
|
const legacyDir = this.getInstallDir();
|
|
1442
2071
|
if (exists(legacyDir) && legacyDir !== canonicalDir) for (const name of listDir(legacyDir)){
|
|
2072
|
+
// Skip if already found in canonical location
|
|
1443
2073
|
if (seenNames.has(name)) continue;
|
|
1444
2074
|
const skillPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(legacyDir, name);
|
|
1445
2075
|
if (!isDirectory(skillPath)) continue;
|
|
2076
|
+
// Skip symlinks pointing to canonical location (avoid duplicates)
|
|
1446
2077
|
if (isSymlink(skillPath)) try {
|
|
1447
2078
|
const realPath = getRealPath(skillPath);
|
|
1448
2079
|
if (realPath.includes(__WEBPACK_EXTERNAL_MODULE_node_path__.join('.agents', 'skills'))) continue;
|
|
1449
|
-
} catch {
|
|
2080
|
+
} catch {
|
|
2081
|
+
// If we can't resolve the symlink, include it anyway
|
|
2082
|
+
}
|
|
1450
2083
|
const skill = this.getInstalledSkillFromPath(name, skillPath);
|
|
1451
2084
|
if (skill) {
|
|
1452
2085
|
skills.push(skill);
|
|
@@ -1455,7 +2088,9 @@ class SkillManager {
|
|
|
1455
2088
|
}
|
|
1456
2089
|
return skills;
|
|
1457
2090
|
}
|
|
1458
|
-
|
|
2091
|
+
/**
|
|
2092
|
+
* Get installed skill information from a specific path
|
|
2093
|
+
*/ getInstalledSkillFromPath(name, skillPath) {
|
|
1459
2094
|
if (!exists(skillPath)) return null;
|
|
1460
2095
|
const isLinked = isSymlink(skillPath);
|
|
1461
2096
|
const locked = this.lockManager.get(name);
|
|
@@ -1463,7 +2098,9 @@ class SkillManager {
|
|
|
1463
2098
|
const skillJsonPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(skillPath, 'skill.json');
|
|
1464
2099
|
if (exists(skillJsonPath)) try {
|
|
1465
2100
|
metadata = readJson(skillJsonPath);
|
|
1466
|
-
} catch {
|
|
2101
|
+
} catch {
|
|
2102
|
+
// Ignore parse errors
|
|
2103
|
+
}
|
|
1467
2104
|
return {
|
|
1468
2105
|
name,
|
|
1469
2106
|
path: skillPath,
|
|
@@ -1473,35 +2110,49 @@ class SkillManager {
|
|
|
1473
2110
|
isLinked
|
|
1474
2111
|
};
|
|
1475
2112
|
}
|
|
1476
|
-
|
|
2113
|
+
/**
|
|
2114
|
+
* Get installed skill information
|
|
2115
|
+
*
|
|
2116
|
+
* Checks canonical location first, then legacy location.
|
|
2117
|
+
*/ getInstalledSkill(name) {
|
|
2118
|
+
// Check canonical location first (.agents/skills/)
|
|
1477
2119
|
const canonicalPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.getCanonicalSkillsDir(), name);
|
|
1478
2120
|
if (exists(canonicalPath)) return this.getInstalledSkillFromPath(name, canonicalPath);
|
|
2121
|
+
// Check legacy location (.skills/)
|
|
1479
2122
|
const legacyPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(this.getInstallDir(), name);
|
|
1480
2123
|
if (exists(legacyPath)) return this.getInstalledSkillFromPath(name, legacyPath);
|
|
1481
2124
|
return null;
|
|
1482
2125
|
}
|
|
1483
|
-
|
|
2126
|
+
/**
|
|
2127
|
+
* Get skill details
|
|
2128
|
+
*/ getInfo(name) {
|
|
1484
2129
|
return {
|
|
1485
2130
|
installed: this.getInstalledSkill(name),
|
|
1486
2131
|
locked: this.lockManager.get(name),
|
|
1487
2132
|
config: this.config.getSkillRef(name)
|
|
1488
2133
|
};
|
|
1489
2134
|
}
|
|
1490
|
-
|
|
2135
|
+
/**
|
|
2136
|
+
* Check for outdated skills
|
|
2137
|
+
*/ async checkOutdated() {
|
|
1491
2138
|
const results = [];
|
|
1492
2139
|
const skills = this.config.getSkills();
|
|
1493
2140
|
for (const [name, ref] of Object.entries(skills))try {
|
|
1494
2141
|
const locked = this.lockManager.get(name);
|
|
2142
|
+
// Use ref for comparison (git tag/branch/commit), fallback to version for backward compatibility
|
|
1495
2143
|
const currentRef = locked?.ref || locked?.version || 'unknown';
|
|
1496
2144
|
const currentVersion = locked?.version || 'unknown';
|
|
2145
|
+
// Parse latest version
|
|
1497
2146
|
const parsed = this.resolver.parseRef(ref);
|
|
1498
2147
|
const repoUrl = this.resolver.buildRepoUrl(parsed);
|
|
2148
|
+
// Force get latest
|
|
1499
2149
|
const latestResolved = await this.resolver.resolveVersion(repoUrl, {
|
|
1500
2150
|
type: 'latest',
|
|
1501
2151
|
value: 'latest',
|
|
1502
2152
|
raw: 'latest'
|
|
1503
2153
|
});
|
|
1504
2154
|
const latest = latestResolved.ref;
|
|
2155
|
+
// Compare using git refs, not semantic versions
|
|
1505
2156
|
const updateAvailable = currentRef !== latest && 'unknown' !== currentRef;
|
|
1506
2157
|
results.push({
|
|
1507
2158
|
name,
|
|
@@ -1520,35 +2171,53 @@ class SkillManager {
|
|
|
1520
2171
|
}
|
|
1521
2172
|
return results;
|
|
1522
2173
|
}
|
|
1523
|
-
|
|
2174
|
+
// ============================================================================
|
|
2175
|
+
// Multi-Agent installation methods
|
|
2176
|
+
// ============================================================================
|
|
2177
|
+
/**
|
|
2178
|
+
* Install skill to multiple agents
|
|
2179
|
+
*
|
|
2180
|
+
* @param ref - Skill reference (e.g., github:user/repo@v1.0.0)
|
|
2181
|
+
* @param targetAgents - Target agents list
|
|
2182
|
+
* @param options - Installation options
|
|
2183
|
+
*/ async installToAgents(ref, targetAgents, options = {}) {
|
|
1524
2184
|
const { save = true, mode = 'symlink' } = options;
|
|
2185
|
+
// Parse reference
|
|
1525
2186
|
const resolved = await this.resolver.resolve(ref);
|
|
1526
2187
|
const { parsed, repoUrl } = resolved;
|
|
1527
|
-
const gitRef = resolved.ref;
|
|
2188
|
+
const gitRef = resolved.ref; // Git reference (tag, branch, commit)
|
|
1528
2189
|
const skillName = parsed.subPath ? __WEBPACK_EXTERNAL_MODULE_node_path__.basename(parsed.subPath) : parsed.repo;
|
|
1529
2190
|
logger_logger["package"](`Installing ${skillName}@${gitRef} to ${targetAgents.length} agent(s)...`);
|
|
2191
|
+
// Check cache
|
|
1530
2192
|
let cacheResult = await this.cache.get(parsed, gitRef);
|
|
1531
2193
|
if (cacheResult) logger_logger.debug(`Using cached ${skillName}@${gitRef}`);
|
|
1532
2194
|
else {
|
|
1533
2195
|
logger_logger.debug(`Caching ${skillName}@${gitRef} from ${repoUrl}`);
|
|
1534
2196
|
cacheResult = await this.cache.cache(repoUrl, parsed, gitRef, gitRef);
|
|
1535
2197
|
}
|
|
2198
|
+
// Get cache path as source
|
|
1536
2199
|
const sourcePath = this.cache.getCachePath(parsed, gitRef);
|
|
1537
|
-
|
|
2200
|
+
// Read semantic version from skill.json
|
|
2201
|
+
let semanticVersion = gitRef; // fallback to gitRef if no skill.json
|
|
1538
2202
|
const skillJsonPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(sourcePath, 'skill.json');
|
|
1539
2203
|
if (exists(skillJsonPath)) try {
|
|
1540
2204
|
const skillJson = readJson(skillJsonPath);
|
|
1541
2205
|
if (skillJson.version) semanticVersion = skillJson.version;
|
|
1542
|
-
} catch {
|
|
2206
|
+
} catch {
|
|
2207
|
+
// Ignore parse errors, use gitRef as fallback
|
|
2208
|
+
}
|
|
2209
|
+
// Create Installer with custom installDir from config
|
|
1543
2210
|
const defaults = this.config.getDefaults();
|
|
1544
2211
|
const installer = new Installer({
|
|
1545
2212
|
cwd: this.projectRoot,
|
|
1546
2213
|
global: this.isGlobal,
|
|
1547
2214
|
installDir: defaults.installDir
|
|
1548
2215
|
});
|
|
2216
|
+
// Install to all target agents
|
|
1549
2217
|
const results = await installer.installToAgents(sourcePath, skillName, targetAgents, {
|
|
1550
2218
|
mode: mode
|
|
1551
2219
|
});
|
|
2220
|
+
// Update lock file (project mode only)
|
|
1552
2221
|
if (!this.isGlobal) this.lockManager.lockSkill(skillName, {
|
|
1553
2222
|
source: `${parsed.registry}:${parsed.owner}/${parsed.repo}${parsed.subPath ? `/${parsed.subPath}` : ''}`,
|
|
1554
2223
|
version: semanticVersion,
|
|
@@ -1556,15 +2225,18 @@ class SkillManager {
|
|
|
1556
2225
|
resolved: repoUrl,
|
|
1557
2226
|
commit: cacheResult.commit
|
|
1558
2227
|
});
|
|
2228
|
+
// Update skills.json (project mode only)
|
|
1559
2229
|
if (!this.isGlobal && save) {
|
|
1560
2230
|
this.config.ensureExists();
|
|
1561
2231
|
this.config.addSkill(skillName, ref);
|
|
1562
2232
|
}
|
|
2233
|
+
// Count results
|
|
1563
2234
|
const successCount = Array.from(results.values()).filter((r)=>r.success).length;
|
|
1564
2235
|
const failCount = results.size - successCount;
|
|
1565
2236
|
const displayVersion = semanticVersion !== gitRef ? `${semanticVersion} (${gitRef})` : gitRef;
|
|
1566
2237
|
if (0 === failCount) logger_logger.success(`Installed ${skillName}@${displayVersion} to ${successCount} agent(s)`);
|
|
1567
2238
|
else logger_logger.warn(`Installed ${skillName}@${displayVersion} to ${successCount} agent(s), ${failCount} failed`);
|
|
2239
|
+
// Build the InstalledSkill to return
|
|
1568
2240
|
const skill = {
|
|
1569
2241
|
name: skillName,
|
|
1570
2242
|
path: sourcePath,
|
|
@@ -1576,17 +2248,30 @@ class SkillManager {
|
|
|
1576
2248
|
results
|
|
1577
2249
|
};
|
|
1578
2250
|
}
|
|
1579
|
-
|
|
2251
|
+
/**
|
|
2252
|
+
* Get default target agents
|
|
2253
|
+
*
|
|
2254
|
+
* Priority:
|
|
2255
|
+
* 1. defaults.targetAgents in skills.json
|
|
2256
|
+
* 2. Auto-detect installed agents
|
|
2257
|
+
* 3. Return empty array
|
|
2258
|
+
*/ async getDefaultTargetAgents() {
|
|
2259
|
+
// Read from configuration
|
|
1580
2260
|
const defaults = this.config.getDefaults();
|
|
1581
2261
|
if (defaults.targetAgents && defaults.targetAgents.length > 0) return defaults.targetAgents.filter(isValidAgentType);
|
|
2262
|
+
// Auto-detect
|
|
1582
2263
|
return detectInstalledAgents();
|
|
1583
2264
|
}
|
|
1584
|
-
|
|
2265
|
+
/**
|
|
2266
|
+
* Get default installation mode
|
|
2267
|
+
*/ getDefaultInstallMode() {
|
|
1585
2268
|
const defaults = this.config.getDefaults();
|
|
1586
2269
|
if ('copy' === defaults.installMode || 'symlink' === defaults.installMode) return defaults.installMode;
|
|
1587
2270
|
return 'symlink';
|
|
1588
2271
|
}
|
|
1589
|
-
|
|
2272
|
+
/**
|
|
2273
|
+
* Validate agent type list
|
|
2274
|
+
*/ validateAgentTypes(agentNames) {
|
|
1590
2275
|
const valid = [];
|
|
1591
2276
|
const invalid = [];
|
|
1592
2277
|
for (const name of agentNames)if (isValidAgentType(name)) valid.push(name);
|
|
@@ -1596,10 +2281,14 @@ class SkillManager {
|
|
|
1596
2281
|
invalid
|
|
1597
2282
|
};
|
|
1598
2283
|
}
|
|
1599
|
-
|
|
2284
|
+
/**
|
|
2285
|
+
* Get all available agent types
|
|
2286
|
+
*/ getAllAgentTypes() {
|
|
1600
2287
|
return Object.keys(agents);
|
|
1601
2288
|
}
|
|
1602
|
-
|
|
2289
|
+
/**
|
|
2290
|
+
* Uninstall skill from specified agents
|
|
2291
|
+
*/ uninstallFromAgents(name, targetAgents) {
|
|
1603
2292
|
const defaults = this.config.getDefaults();
|
|
1604
2293
|
const installer = new Installer({
|
|
1605
2294
|
cwd: this.projectRoot,
|
|
@@ -1607,14 +2296,18 @@ class SkillManager {
|
|
|
1607
2296
|
installDir: defaults.installDir
|
|
1608
2297
|
});
|
|
1609
2298
|
const results = installer.uninstallFromAgents(name, targetAgents);
|
|
2299
|
+
// Remove from lock file (project mode only)
|
|
1610
2300
|
if (!this.isGlobal) this.lockManager.remove(name);
|
|
2301
|
+
// Remove from skills.json (project mode only)
|
|
1611
2302
|
if (!this.isGlobal && this.config.exists()) this.config.removeSkill(name);
|
|
1612
2303
|
const successCount = Array.from(results.values()).filter((r)=>r).length;
|
|
1613
2304
|
logger_logger.success(`Uninstalled ${name} from ${successCount} agent(s)`);
|
|
1614
2305
|
return results;
|
|
1615
2306
|
}
|
|
1616
2307
|
}
|
|
1617
|
-
|
|
2308
|
+
/**
|
|
2309
|
+
* All available subcommands for reskill
|
|
2310
|
+
*/ const SUBCOMMANDS = [
|
|
1618
2311
|
{
|
|
1619
2312
|
name: 'install',
|
|
1620
2313
|
description: 'Install a skill or all skills from skills.json'
|
|
@@ -1648,15 +2341,21 @@ const SUBCOMMANDS = [
|
|
|
1648
2341
|
description: 'Setup shell completion'
|
|
1649
2342
|
}
|
|
1650
2343
|
];
|
|
1651
|
-
|
|
2344
|
+
/**
|
|
2345
|
+
* Commands that need skill name completion
|
|
2346
|
+
*/ const SKILL_COMPLETION_COMMANDS = [
|
|
1652
2347
|
'info',
|
|
1653
2348
|
'uninstall',
|
|
1654
2349
|
'update'
|
|
1655
2350
|
];
|
|
1656
|
-
|
|
2351
|
+
/**
|
|
2352
|
+
* Get all agent type names for completion
|
|
2353
|
+
*/ function getAgentNames() {
|
|
1657
2354
|
return Object.keys(agents);
|
|
1658
2355
|
}
|
|
1659
|
-
|
|
2356
|
+
/**
|
|
2357
|
+
* Get installed skill names for completion
|
|
2358
|
+
*/ function getInstalledSkillNames() {
|
|
1660
2359
|
try {
|
|
1661
2360
|
const skillManager = new SkillManager();
|
|
1662
2361
|
const skills = skillManager.list();
|
|
@@ -1665,12 +2364,23 @@ function getInstalledSkillNames() {
|
|
|
1665
2364
|
return [];
|
|
1666
2365
|
}
|
|
1667
2366
|
}
|
|
1668
|
-
|
|
2367
|
+
/**
|
|
2368
|
+
* Handle tab completion
|
|
2369
|
+
*
|
|
2370
|
+
* This function is called when the shell requests completion.
|
|
2371
|
+
* It parses the current command line and returns appropriate completions.
|
|
2372
|
+
*/ function handleCompletion() {
|
|
1669
2373
|
const env = __WEBPACK_EXTERNAL_MODULE_tabtab__["default"].parseEnv(process.env);
|
|
2374
|
+
// Not in completion mode
|
|
1670
2375
|
if (!env.complete) return;
|
|
1671
2376
|
const { prev, line } = env;
|
|
2377
|
+
// Parse the command line to understand context
|
|
1672
2378
|
const parts = line.trim().split(/\s+/);
|
|
1673
|
-
const command = parts[1];
|
|
2379
|
+
const command = parts[1]; // First word after 'reskill'
|
|
2380
|
+
// Completing the subcommand (reskill <TAB>)
|
|
2381
|
+
// Only show subcommands if:
|
|
2382
|
+
// 1. Only 'reskill' is typed (parts.length === 1)
|
|
2383
|
+
// 2. Or typing the subcommand (parts.length === 2 and not ending with space)
|
|
1674
2384
|
if (1 === parts.length || 2 === parts.length && !line.endsWith(' ')) {
|
|
1675
2385
|
const completions = SUBCOMMANDS.map((c)=>({
|
|
1676
2386
|
name: c.name,
|
|
@@ -1679,12 +2389,18 @@ function handleCompletion() {
|
|
|
1679
2389
|
__WEBPACK_EXTERNAL_MODULE_tabtab__["default"].log(completions);
|
|
1680
2390
|
return;
|
|
1681
2391
|
}
|
|
2392
|
+
// Completing skill name for info/uninstall commands (only accept ONE skill argument)
|
|
2393
|
+
// Only complete if we're at the first argument position (words === 2 means completing 2nd word)
|
|
1682
2394
|
if (SKILL_COMPLETION_COMMANDS.includes(command)) {
|
|
2395
|
+
// parts: ['reskill', 'info', ...args]
|
|
2396
|
+
// If already has a skill argument, don't complete more
|
|
1683
2397
|
if (parts.length > 2 && line.endsWith(' ')) {
|
|
2398
|
+
// Already have an argument and user pressed space, no more completions
|
|
1684
2399
|
__WEBPACK_EXTERNAL_MODULE_tabtab__["default"].log([]);
|
|
1685
2400
|
return;
|
|
1686
2401
|
}
|
|
1687
2402
|
if (parts.length > 3) {
|
|
2403
|
+
// Already have more than one argument
|
|
1688
2404
|
__WEBPACK_EXTERNAL_MODULE_tabtab__["default"].log([]);
|
|
1689
2405
|
return;
|
|
1690
2406
|
}
|
|
@@ -1692,13 +2408,18 @@ function handleCompletion() {
|
|
|
1692
2408
|
__WEBPACK_EXTERNAL_MODULE_tabtab__["default"].log(skills);
|
|
1693
2409
|
return;
|
|
1694
2410
|
}
|
|
2411
|
+
// Completing agent names for install -a/--agent
|
|
1695
2412
|
if ('install' === command && ('-a' === prev || '--agent' === prev)) {
|
|
1696
2413
|
const agentNames = getAgentNames();
|
|
1697
2414
|
__WEBPACK_EXTERNAL_MODULE_tabtab__["default"].log(agentNames);
|
|
1698
2415
|
return;
|
|
1699
2416
|
}
|
|
2417
|
+
// Completing options for install command
|
|
2418
|
+
// Only show options when user is typing an option (starts with -)
|
|
2419
|
+
// Don't auto-complete '-' when user just typed 'install '
|
|
1700
2420
|
if ('install' === command) {
|
|
1701
2421
|
const { last } = env;
|
|
2422
|
+
// Only complete if user has started typing an option (e.g., '-' or '--')
|
|
1702
2423
|
if (last.startsWith('-')) {
|
|
1703
2424
|
const options = [
|
|
1704
2425
|
{
|
|
@@ -1741,18 +2462,25 @@ function handleCompletion() {
|
|
|
1741
2462
|
__WEBPACK_EXTERNAL_MODULE_tabtab__["default"].log(options);
|
|
1742
2463
|
return;
|
|
1743
2464
|
}
|
|
2465
|
+
// After 'install ' with no input, don't suggest anything
|
|
2466
|
+
// (user might want to type a skill reference)
|
|
1744
2467
|
__WEBPACK_EXTERNAL_MODULE_tabtab__["default"].log([]);
|
|
1745
2468
|
return;
|
|
1746
2469
|
}
|
|
2470
|
+
// Default: no completions
|
|
1747
2471
|
__WEBPACK_EXTERNAL_MODULE_tabtab__["default"].log([]);
|
|
1748
2472
|
}
|
|
1749
|
-
|
|
2473
|
+
/**
|
|
2474
|
+
* completion command - Setup shell completion
|
|
2475
|
+
*/ const completionCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('completion').description('Setup shell completion for reskill').argument('[action]', 'Action: install, uninstall, or shell name (bash, zsh, fish)').action(async (action)=>{
|
|
2476
|
+
// Check if we're being called for completion
|
|
1750
2477
|
const env = __WEBPACK_EXTERNAL_MODULE_tabtab__["default"].parseEnv(process.env);
|
|
1751
2478
|
if (env.complete) {
|
|
1752
2479
|
handleCompletion();
|
|
1753
2480
|
return;
|
|
1754
2481
|
}
|
|
1755
2482
|
if (!action) {
|
|
2483
|
+
// Show help
|
|
1756
2484
|
logger_logger.log('Shell completion for reskill');
|
|
1757
2485
|
logger_logger.newline();
|
|
1758
2486
|
logger_logger.log('Usage:');
|
|
@@ -1792,11 +2520,16 @@ const completionCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('com
|
|
|
1792
2520
|
}
|
|
1793
2521
|
return;
|
|
1794
2522
|
}
|
|
2523
|
+
// Unknown action
|
|
1795
2524
|
logger_logger.error(`Unknown action: ${action}`);
|
|
1796
2525
|
logger_logger.log('Use "reskill completion install" or "reskill completion uninstall"');
|
|
1797
2526
|
process.exit(1);
|
|
1798
2527
|
});
|
|
1799
|
-
|
|
2528
|
+
/**
|
|
2529
|
+
* Check if completion is being requested and handle it
|
|
2530
|
+
*
|
|
2531
|
+
* This should be called at the start of CLI execution
|
|
2532
|
+
*/ function maybeHandleCompletion() {
|
|
1800
2533
|
const env = __WEBPACK_EXTERNAL_MODULE_tabtab__["default"].parseEnv(process.env);
|
|
1801
2534
|
if (env.complete) {
|
|
1802
2535
|
handleCompletion();
|
|
@@ -1804,7 +2537,9 @@ function maybeHandleCompletion() {
|
|
|
1804
2537
|
}
|
|
1805
2538
|
return false;
|
|
1806
2539
|
}
|
|
1807
|
-
|
|
2540
|
+
/**
|
|
2541
|
+
* Get status icon
|
|
2542
|
+
*/ function getStatusIcon(status) {
|
|
1808
2543
|
switch(status){
|
|
1809
2544
|
case 'ok':
|
|
1810
2545
|
return __WEBPACK_EXTERNAL_MODULE_chalk__["default"].green('✓');
|
|
@@ -1814,7 +2549,13 @@ function getStatusIcon(status) {
|
|
|
1814
2549
|
return __WEBPACK_EXTERNAL_MODULE_chalk__["default"].red('✗');
|
|
1815
2550
|
}
|
|
1816
2551
|
}
|
|
1817
|
-
|
|
2552
|
+
/**
|
|
2553
|
+
* Execute command and return output
|
|
2554
|
+
*
|
|
2555
|
+
* @warning This function executes arbitrary shell commands. It is exported for
|
|
2556
|
+
* testing purposes only. Do not use with untrusted input. Internal usage is
|
|
2557
|
+
* limited to hardcoded commands (e.g., 'git --version').
|
|
2558
|
+
*/ function execCommand(command) {
|
|
1818
2559
|
try {
|
|
1819
2560
|
return (0, __WEBPACK_EXTERNAL_MODULE_node_child_process__.execSync)(command, {
|
|
1820
2561
|
encoding: 'utf-8',
|
|
@@ -1828,7 +2569,9 @@ function execCommand(command) {
|
|
|
1828
2569
|
return null;
|
|
1829
2570
|
}
|
|
1830
2571
|
}
|
|
1831
|
-
|
|
2572
|
+
/**
|
|
2573
|
+
* Get directory size in bytes
|
|
2574
|
+
*/ function getDirSize(dirPath) {
|
|
1832
2575
|
if (!(0, external_node_fs_.existsSync)(dirPath)) return 0;
|
|
1833
2576
|
let size = 0;
|
|
1834
2577
|
try {
|
|
@@ -1840,10 +2583,14 @@ function getDirSize(dirPath) {
|
|
|
1840
2583
|
if (item.isDirectory()) size += getDirSize(itemPath);
|
|
1841
2584
|
else if (item.isFile()) size += (0, external_node_fs_.statSync)(itemPath).size;
|
|
1842
2585
|
}
|
|
1843
|
-
} catch {
|
|
2586
|
+
} catch {
|
|
2587
|
+
// Ignore permission errors
|
|
2588
|
+
}
|
|
1844
2589
|
return size;
|
|
1845
2590
|
}
|
|
1846
|
-
|
|
2591
|
+
/**
|
|
2592
|
+
* Format bytes to human readable
|
|
2593
|
+
*/ function formatBytes(bytes) {
|
|
1847
2594
|
if (0 === bytes) return '0 B';
|
|
1848
2595
|
const k = 1024;
|
|
1849
2596
|
const sizes = [
|
|
@@ -1855,10 +2602,13 @@ function formatBytes(bytes) {
|
|
|
1855
2602
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1856
2603
|
return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`;
|
|
1857
2604
|
}
|
|
1858
|
-
|
|
2605
|
+
/**
|
|
2606
|
+
* Count cached skills
|
|
2607
|
+
*/ function countCachedSkills(cacheDir) {
|
|
1859
2608
|
if (!(0, external_node_fs_.existsSync)(cacheDir)) return 0;
|
|
1860
2609
|
let count = 0;
|
|
1861
2610
|
try {
|
|
2611
|
+
// Cache structure: ~/.reskill-cache/<registry>/<owner>/<repo>/<version>/
|
|
1862
2612
|
const registries = (0, external_node_fs_.readdirSync)(cacheDir, {
|
|
1863
2613
|
withFileTypes: true
|
|
1864
2614
|
});
|
|
@@ -1884,10 +2634,14 @@ function countCachedSkills(cacheDir) {
|
|
|
1884
2634
|
}
|
|
1885
2635
|
}
|
|
1886
2636
|
}
|
|
1887
|
-
} catch {
|
|
2637
|
+
} catch {
|
|
2638
|
+
// Ignore errors
|
|
2639
|
+
}
|
|
1888
2640
|
return count;
|
|
1889
2641
|
}
|
|
1890
|
-
|
|
2642
|
+
/**
|
|
2643
|
+
* Check reskill version
|
|
2644
|
+
*/ async function checkReskillVersion(currentVersion, packageName) {
|
|
1891
2645
|
try {
|
|
1892
2646
|
const result = await checkForUpdate(packageName, currentVersion);
|
|
1893
2647
|
if (result?.hasUpdate) return {
|
|
@@ -1909,7 +2663,9 @@ async function checkReskillVersion(currentVersion, packageName) {
|
|
|
1909
2663
|
};
|
|
1910
2664
|
}
|
|
1911
2665
|
}
|
|
1912
|
-
|
|
2666
|
+
/**
|
|
2667
|
+
* Check Node.js version
|
|
2668
|
+
*/ function checkNodeVersion() {
|
|
1913
2669
|
const version = process.version;
|
|
1914
2670
|
const major = Number.parseInt(version.slice(1).split('.')[0], 10);
|
|
1915
2671
|
const required = 18;
|
|
@@ -1925,7 +2681,9 @@ function checkNodeVersion() {
|
|
|
1925
2681
|
message: `${version} (>=${required}.0.0 required)`
|
|
1926
2682
|
};
|
|
1927
2683
|
}
|
|
1928
|
-
|
|
2684
|
+
/**
|
|
2685
|
+
* Check Git version
|
|
2686
|
+
*/ function checkGitVersion() {
|
|
1929
2687
|
const version = execCommand('git --version');
|
|
1930
2688
|
if (!version) return {
|
|
1931
2689
|
name: 'Git',
|
|
@@ -1933,6 +2691,7 @@ function checkGitVersion() {
|
|
|
1933
2691
|
message: 'not found',
|
|
1934
2692
|
hint: 'Please install Git: https://git-scm.com/downloads'
|
|
1935
2693
|
};
|
|
2694
|
+
// Extract version number from "git version X.Y.Z"
|
|
1936
2695
|
const match = version.match(/git version (\d+\.\d+\.\d+)/);
|
|
1937
2696
|
const versionNum = match ? match[1] : version;
|
|
1938
2697
|
return {
|
|
@@ -1941,9 +2700,12 @@ function checkGitVersion() {
|
|
|
1941
2700
|
message: versionNum
|
|
1942
2701
|
};
|
|
1943
2702
|
}
|
|
1944
|
-
|
|
2703
|
+
/**
|
|
2704
|
+
* Check Git authentication
|
|
2705
|
+
*/ function checkGitAuth() {
|
|
1945
2706
|
const home = (0, __WEBPACK_EXTERNAL_MODULE_node_os__.homedir)();
|
|
1946
2707
|
const sshDir = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(home, '.ssh');
|
|
2708
|
+
// Check for SSH keys
|
|
1947
2709
|
const sshKeyFiles = [
|
|
1948
2710
|
'id_rsa',
|
|
1949
2711
|
'id_ed25519',
|
|
@@ -1957,6 +2719,7 @@ function checkGitAuth() {
|
|
|
1957
2719
|
break;
|
|
1958
2720
|
}
|
|
1959
2721
|
}
|
|
2722
|
+
// Check for git credential helper
|
|
1960
2723
|
const credentialHelper = execCommand('git config --global credential.helper');
|
|
1961
2724
|
const hasCredentialHelper = !!credentialHelper;
|
|
1962
2725
|
if (hasSSHKey && hasCredentialHelper) return {
|
|
@@ -1981,7 +2744,9 @@ function checkGitAuth() {
|
|
|
1981
2744
|
hint: 'For private repos, add SSH key: ssh-keygen -t ed25519'
|
|
1982
2745
|
};
|
|
1983
2746
|
}
|
|
1984
|
-
|
|
2747
|
+
/**
|
|
2748
|
+
* Check cache directory
|
|
2749
|
+
*/ function checkCacheDir() {
|
|
1985
2750
|
const cacheDir = process.env.RESKILL_CACHE_DIR || (0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)((0, __WEBPACK_EXTERNAL_MODULE_node_os__.homedir)(), '.reskill-cache');
|
|
1986
2751
|
if (!(0, external_node_fs_.existsSync)(cacheDir)) return {
|
|
1987
2752
|
name: 'Cache directory',
|
|
@@ -1996,7 +2761,9 @@ function checkCacheDir() {
|
|
|
1996
2761
|
message: `${cacheDir} (${formatBytes(size)}, ${count} skill${1 !== count ? 's' : ''} cached)`
|
|
1997
2762
|
};
|
|
1998
2763
|
}
|
|
1999
|
-
|
|
2764
|
+
/**
|
|
2765
|
+
* Check skills.json
|
|
2766
|
+
*/ function checkSkillsJson(cwd) {
|
|
2000
2767
|
const configLoader = new ConfigLoader(cwd);
|
|
2001
2768
|
if (!configLoader.exists()) return {
|
|
2002
2769
|
name: 'skills.json',
|
|
@@ -2021,11 +2788,15 @@ function checkSkillsJson(cwd) {
|
|
|
2021
2788
|
};
|
|
2022
2789
|
}
|
|
2023
2790
|
}
|
|
2024
|
-
|
|
2791
|
+
/**
|
|
2792
|
+
* Reserved registry names that should not be overridden
|
|
2793
|
+
*/ const RESERVED_REGISTRIES = [
|
|
2025
2794
|
'github',
|
|
2026
2795
|
'gitlab'
|
|
2027
2796
|
];
|
|
2028
|
-
|
|
2797
|
+
/**
|
|
2798
|
+
* Dangerous paths that should not be used as installDir
|
|
2799
|
+
*/ const DANGEROUS_INSTALL_DIRS = [
|
|
2029
2800
|
'src',
|
|
2030
2801
|
'lib',
|
|
2031
2802
|
'dist',
|
|
@@ -2038,7 +2809,9 @@ const DANGEROUS_INSTALL_DIRS = [
|
|
|
2038
2809
|
'.vscode',
|
|
2039
2810
|
'.idea'
|
|
2040
2811
|
];
|
|
2041
|
-
|
|
2812
|
+
/**
|
|
2813
|
+
* Dangerous skill name patterns
|
|
2814
|
+
*/ const DANGEROUS_SKILL_NAMES = [
|
|
2042
2815
|
'.git',
|
|
2043
2816
|
'..',
|
|
2044
2817
|
'.',
|
|
@@ -2046,9 +2819,16 @@ const DANGEROUS_SKILL_NAMES = [
|
|
|
2046
2819
|
'__proto__',
|
|
2047
2820
|
'constructor'
|
|
2048
2821
|
];
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2822
|
+
/**
|
|
2823
|
+
* Network connectivity check timeout in milliseconds
|
|
2824
|
+
*/ const NETWORK_CHECK_TIMEOUT_MS = 5000;
|
|
2825
|
+
/**
|
|
2826
|
+
* Padding width for check result names in output.
|
|
2827
|
+
* Accounts for nested items with " └─ " prefix (5 chars).
|
|
2828
|
+
*/ const CHECK_NAME_PAD_WIDTH = 26;
|
|
2829
|
+
/**
|
|
2830
|
+
* Check for registry name conflicts
|
|
2831
|
+
*/ function checkRegistryConflicts(cwd) {
|
|
2052
2832
|
const results = [];
|
|
2053
2833
|
const configLoader = new ConfigLoader(cwd);
|
|
2054
2834
|
if (!configLoader.exists()) return results;
|
|
@@ -2061,16 +2841,21 @@ function checkRegistryConflicts(cwd) {
|
|
|
2061
2841
|
message: `"${name}" overrides built-in registry`,
|
|
2062
2842
|
hint: 'Consider using a different name for custom registries'
|
|
2063
2843
|
});
|
|
2064
|
-
} catch {
|
|
2844
|
+
} catch {
|
|
2845
|
+
// Ignore parse errors, handled by checkSkillsJson
|
|
2846
|
+
}
|
|
2065
2847
|
return results;
|
|
2066
2848
|
}
|
|
2067
|
-
|
|
2849
|
+
/**
|
|
2850
|
+
* Check for dangerous installDir configuration
|
|
2851
|
+
*/ function checkInstallDir(cwd) {
|
|
2068
2852
|
const configLoader = new ConfigLoader(cwd);
|
|
2069
2853
|
if (!configLoader.exists()) return null;
|
|
2070
2854
|
try {
|
|
2071
2855
|
const defaults = configLoader.getDefaults();
|
|
2072
2856
|
const installDir = defaults.installDir;
|
|
2073
2857
|
if (!installDir) return null;
|
|
2858
|
+
// Check for dangerous paths
|
|
2074
2859
|
const normalizedDir = installDir.replace(/^\.\//, '').replace(/\/$/, '');
|
|
2075
2860
|
if (DANGEROUS_INSTALL_DIRS.includes(normalizedDir.toLowerCase())) return {
|
|
2076
2861
|
name: 'Dangerous installDir',
|
|
@@ -2078,16 +2863,21 @@ function checkInstallDir(cwd) {
|
|
|
2078
2863
|
message: `"${installDir}" may conflict with important directories`,
|
|
2079
2864
|
hint: 'Use a dedicated directory like ".skills" or ".agents/skills"'
|
|
2080
2865
|
};
|
|
2866
|
+
// Check for path traversal
|
|
2081
2867
|
if (installDir.includes('..')) return {
|
|
2082
2868
|
name: 'Dangerous installDir',
|
|
2083
2869
|
status: 'error',
|
|
2084
2870
|
message: `"${installDir}" contains path traversal`,
|
|
2085
2871
|
hint: 'Use a simple directory name without ".."'
|
|
2086
2872
|
};
|
|
2087
|
-
} catch {
|
|
2873
|
+
} catch {
|
|
2874
|
+
// Ignore parse errors
|
|
2875
|
+
}
|
|
2088
2876
|
return null;
|
|
2089
2877
|
}
|
|
2090
|
-
|
|
2878
|
+
/**
|
|
2879
|
+
* Check for invalid targetAgents configuration
|
|
2880
|
+
*/ function checkTargetAgents(cwd) {
|
|
2091
2881
|
const results = [];
|
|
2092
2882
|
const configLoader = new ConfigLoader(cwd);
|
|
2093
2883
|
if (!configLoader.exists()) return results;
|
|
@@ -2100,10 +2890,14 @@ function checkTargetAgents(cwd) {
|
|
|
2100
2890
|
message: `Unknown agent type: "${agent}"`,
|
|
2101
2891
|
hint: 'Run: reskill install --help to see valid agent types'
|
|
2102
2892
|
});
|
|
2103
|
-
} catch {
|
|
2893
|
+
} catch {
|
|
2894
|
+
// Ignore parse errors
|
|
2895
|
+
}
|
|
2104
2896
|
return results;
|
|
2105
2897
|
}
|
|
2106
|
-
|
|
2898
|
+
/**
|
|
2899
|
+
* Check for skill reference format errors
|
|
2900
|
+
*/ function checkSkillRefs(cwd) {
|
|
2107
2901
|
const results = [];
|
|
2108
2902
|
const configLoader = new ConfigLoader(cwd);
|
|
2109
2903
|
if (!configLoader.exists()) return results;
|
|
@@ -2111,6 +2905,7 @@ function checkSkillRefs(cwd) {
|
|
|
2111
2905
|
const skills = configLoader.getSkills();
|
|
2112
2906
|
const resolver = new GitResolver();
|
|
2113
2907
|
for (const [name, ref] of Object.entries(skills)){
|
|
2908
|
+
// Check for dangerous skill names
|
|
2114
2909
|
if (DANGEROUS_SKILL_NAMES.includes(name) || name.includes('/') || name.includes('\\')) {
|
|
2115
2910
|
results.push({
|
|
2116
2911
|
name: 'Dangerous skill name',
|
|
@@ -2120,6 +2915,7 @@ function checkSkillRefs(cwd) {
|
|
|
2120
2915
|
});
|
|
2121
2916
|
continue;
|
|
2122
2917
|
}
|
|
2918
|
+
// Try to parse the reference
|
|
2123
2919
|
try {
|
|
2124
2920
|
resolver.parseRef(ref);
|
|
2125
2921
|
} catch (error) {
|
|
@@ -2131,10 +2927,14 @@ function checkSkillRefs(cwd) {
|
|
|
2131
2927
|
});
|
|
2132
2928
|
}
|
|
2133
2929
|
}
|
|
2134
|
-
} catch {
|
|
2930
|
+
} catch {
|
|
2931
|
+
// Ignore parse errors
|
|
2932
|
+
}
|
|
2135
2933
|
return results;
|
|
2136
2934
|
}
|
|
2137
|
-
|
|
2935
|
+
/**
|
|
2936
|
+
* Check for version conflicts in monorepo skills
|
|
2937
|
+
*/ function checkMonorepoVersions(cwd) {
|
|
2138
2938
|
const results = [];
|
|
2139
2939
|
const configLoader = new ConfigLoader(cwd);
|
|
2140
2940
|
if (!configLoader.exists()) return results;
|
|
@@ -2144,6 +2944,7 @@ function checkMonorepoVersions(cwd) {
|
|
|
2144
2944
|
const repoVersions = new Map();
|
|
2145
2945
|
for (const [name, ref] of Object.entries(skills))try {
|
|
2146
2946
|
const parsed = resolver.parseRef(ref);
|
|
2947
|
+
// Only check skills with subPath (monorepo)
|
|
2147
2948
|
if (!parsed.subPath) continue;
|
|
2148
2949
|
const repoKey = `${parsed.registry}:${parsed.owner}/${parsed.repo}`;
|
|
2149
2950
|
const version = parsed.version || 'default';
|
|
@@ -2158,7 +2959,10 @@ function checkMonorepoVersions(cwd) {
|
|
|
2158
2959
|
versions.set(version, skillList);
|
|
2159
2960
|
}
|
|
2160
2961
|
skillList.push(name);
|
|
2161
|
-
} catch {
|
|
2962
|
+
} catch {
|
|
2963
|
+
// Skip invalid refs, handled by checkSkillRefs
|
|
2964
|
+
}
|
|
2965
|
+
// Check for multiple versions from same repo
|
|
2162
2966
|
for (const [repo, versions] of repoVersions)if (versions.size > 1) {
|
|
2163
2967
|
const versionList = [
|
|
2164
2968
|
...versions.entries()
|
|
@@ -2170,10 +2974,14 @@ function checkMonorepoVersions(cwd) {
|
|
|
2170
2974
|
hint: 'Consider using the same version for all skills from this repo'
|
|
2171
2975
|
});
|
|
2172
2976
|
}
|
|
2173
|
-
} catch {
|
|
2977
|
+
} catch {
|
|
2978
|
+
// Ignore parse errors
|
|
2979
|
+
}
|
|
2174
2980
|
return results;
|
|
2175
2981
|
}
|
|
2176
|
-
|
|
2982
|
+
/**
|
|
2983
|
+
* Check skills.lock sync status
|
|
2984
|
+
*/ function checkSkillsLock(cwd) {
|
|
2177
2985
|
const configLoader = new ConfigLoader(cwd);
|
|
2178
2986
|
const lockManager = new LockManager(cwd);
|
|
2179
2987
|
if (!configLoader.exists()) return {
|
|
@@ -2195,6 +3003,7 @@ function checkSkillsLock(cwd) {
|
|
|
2195
3003
|
hint: 'Run: reskill install'
|
|
2196
3004
|
};
|
|
2197
3005
|
}
|
|
3006
|
+
// Check if lock is in sync with config
|
|
2198
3007
|
const configSkills = configLoader.getSkills();
|
|
2199
3008
|
const lockedSkills = lockManager.getAll();
|
|
2200
3009
|
const configNames = new Set(Object.keys(configSkills));
|
|
@@ -2229,20 +3038,25 @@ function checkSkillsLock(cwd) {
|
|
|
2229
3038
|
message: `in sync (${lockedNames.size} skill${1 !== lockedNames.size ? 's' : ''} locked)`
|
|
2230
3039
|
};
|
|
2231
3040
|
}
|
|
2232
|
-
|
|
3041
|
+
/**
|
|
3042
|
+
* Check if a file is a valid symlink
|
|
3043
|
+
*/ function isValidSymlink(path) {
|
|
2233
3044
|
try {
|
|
2234
3045
|
const stat = (0, external_node_fs_.lstatSync)(path);
|
|
2235
|
-
if (!stat.isSymbolicLink()) return true;
|
|
3046
|
+
if (!stat.isSymbolicLink()) return true; // Not a symlink, so not a broken symlink
|
|
3047
|
+
// Try to resolve the symlink target
|
|
2236
3048
|
(0, external_node_fs_.realpathSync)(path);
|
|
2237
3049
|
return true;
|
|
2238
3050
|
} catch {
|
|
2239
3051
|
return false;
|
|
2240
3052
|
}
|
|
2241
3053
|
}
|
|
2242
|
-
|
|
3054
|
+
/**
|
|
3055
|
+
* Check if skill.json is valid JSON with required fields
|
|
3056
|
+
*/ function validateSkillJson(skillJsonPath) {
|
|
2243
3057
|
if (!(0, external_node_fs_.existsSync)(skillJsonPath)) return {
|
|
2244
3058
|
valid: true
|
|
2245
|
-
};
|
|
3059
|
+
}; // Not present is OK if SKILL.md exists
|
|
2246
3060
|
try {
|
|
2247
3061
|
const content = (0, external_node_fs_.readFileSync)(skillJsonPath, 'utf-8');
|
|
2248
3062
|
const json = JSON.parse(content);
|
|
@@ -2264,7 +3078,9 @@ function validateSkillJson(skillJsonPath) {
|
|
|
2264
3078
|
};
|
|
2265
3079
|
}
|
|
2266
3080
|
}
|
|
2267
|
-
|
|
3081
|
+
/**
|
|
3082
|
+
* Check installed skills with detailed diagnostics
|
|
3083
|
+
*/ function checkInstalledSkills(cwd) {
|
|
2268
3084
|
const results = [];
|
|
2269
3085
|
const skillManager = new SkillManager(cwd);
|
|
2270
3086
|
const installed = skillManager.list();
|
|
@@ -2275,10 +3091,12 @@ function checkInstalledSkills(cwd) {
|
|
|
2275
3091
|
message: 'none'
|
|
2276
3092
|
}
|
|
2277
3093
|
];
|
|
3094
|
+
// Check each installed skill for issues
|
|
2278
3095
|
const issues = [];
|
|
2279
3096
|
for (const skill of installed){
|
|
2280
3097
|
const skillJsonPath = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(skill.path, 'skill.json');
|
|
2281
3098
|
const skillMdPath = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(skill.path, 'SKILL.md');
|
|
3099
|
+
// Check for broken symlink
|
|
2282
3100
|
if (skill.isLinked && !isValidSymlink(skill.path)) {
|
|
2283
3101
|
issues.push({
|
|
2284
3102
|
name: skill.name,
|
|
@@ -2287,6 +3105,7 @@ function checkInstalledSkills(cwd) {
|
|
|
2287
3105
|
});
|
|
2288
3106
|
continue;
|
|
2289
3107
|
}
|
|
3108
|
+
// Check for missing both files
|
|
2290
3109
|
const hasSkillJson = (0, external_node_fs_.existsSync)(skillJsonPath);
|
|
2291
3110
|
const hasSkillMd = (0, external_node_fs_.existsSync)(skillMdPath);
|
|
2292
3111
|
if (!hasSkillJson && !hasSkillMd) {
|
|
@@ -2297,6 +3116,7 @@ function checkInstalledSkills(cwd) {
|
|
|
2297
3116
|
});
|
|
2298
3117
|
continue;
|
|
2299
3118
|
}
|
|
3119
|
+
// Validate skill.json if present
|
|
2300
3120
|
if (hasSkillJson) {
|
|
2301
3121
|
const validation = validateSkillJson(skillJsonPath);
|
|
2302
3122
|
if (!validation.valid) issues.push({
|
|
@@ -2306,6 +3126,7 @@ function checkInstalledSkills(cwd) {
|
|
|
2306
3126
|
});
|
|
2307
3127
|
}
|
|
2308
3128
|
}
|
|
3129
|
+
// Build summary result
|
|
2309
3130
|
const errorCount = issues.filter((i)=>'error' === i.severity).length;
|
|
2310
3131
|
const warnCount = issues.filter((i)=>'warn' === i.severity).length;
|
|
2311
3132
|
if (0 === issues.length) results.push({
|
|
@@ -2322,6 +3143,7 @@ function checkInstalledSkills(cwd) {
|
|
|
2322
3143
|
message: `${installed.length} installed, ${issueText}`,
|
|
2323
3144
|
hint: 'Run: reskill install --force to fix'
|
|
2324
3145
|
});
|
|
3146
|
+
// Add detailed issues
|
|
2325
3147
|
for (const issue of issues)results.push({
|
|
2326
3148
|
name: ` └─ ${issue.name}`,
|
|
2327
3149
|
status: 'error' === issue.severity ? 'error' : 'warn',
|
|
@@ -2330,7 +3152,9 @@ function checkInstalledSkills(cwd) {
|
|
|
2330
3152
|
}
|
|
2331
3153
|
return results;
|
|
2332
3154
|
}
|
|
2333
|
-
|
|
3155
|
+
/**
|
|
3156
|
+
* Check network connectivity
|
|
3157
|
+
*/ async function checkNetwork(host) {
|
|
2334
3158
|
const displayName = host.replace('https://', '').replace('http://', '');
|
|
2335
3159
|
try {
|
|
2336
3160
|
const controller = new AbortController();
|
|
@@ -2360,40 +3184,57 @@ async function checkNetwork(host) {
|
|
|
2360
3184
|
};
|
|
2361
3185
|
}
|
|
2362
3186
|
}
|
|
2363
|
-
|
|
3187
|
+
/**
|
|
3188
|
+
* Print check result
|
|
3189
|
+
*/ function printResult(result) {
|
|
2364
3190
|
const icon = getStatusIcon(result.status);
|
|
2365
3191
|
const name = result.name.padEnd(CHECK_NAME_PAD_WIDTH);
|
|
2366
3192
|
const message = 'ok' === result.status ? result.message : __WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim(result.message);
|
|
2367
3193
|
logger_logger.log(`${icon} ${name} ${message}`);
|
|
2368
3194
|
if (result.hint) logger_logger.log(` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim('→')} ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim(result.hint)}`);
|
|
2369
3195
|
}
|
|
2370
|
-
|
|
3196
|
+
/**
|
|
3197
|
+
* Run all checks
|
|
3198
|
+
*/ async function runDoctorChecks(options) {
|
|
2371
3199
|
const { cwd, packageName, packageVersion, skipNetwork, skipConfigChecks } = options;
|
|
2372
3200
|
const results = [];
|
|
3201
|
+
// Version checks
|
|
2373
3202
|
results.push(await checkReskillVersion(packageVersion, packageName));
|
|
2374
3203
|
results.push(checkNodeVersion());
|
|
2375
3204
|
results.push(checkGitVersion());
|
|
2376
3205
|
results.push(checkGitAuth());
|
|
3206
|
+
// Directory checks
|
|
2377
3207
|
results.push(checkCacheDir());
|
|
2378
3208
|
results.push(checkSkillsJson(cwd));
|
|
2379
3209
|
results.push(checkSkillsLock(cwd));
|
|
2380
3210
|
results.push(...checkInstalledSkills(cwd));
|
|
3211
|
+
// Deep config checks (can be skipped for faster checks)
|
|
2381
3212
|
if (!skipConfigChecks) {
|
|
3213
|
+
// Registry conflicts
|
|
2382
3214
|
results.push(...checkRegistryConflicts(cwd));
|
|
3215
|
+
// installDir validation
|
|
2383
3216
|
const installDirCheck = checkInstallDir(cwd);
|
|
2384
3217
|
if (installDirCheck) results.push(installDirCheck);
|
|
3218
|
+
// targetAgents validation
|
|
2385
3219
|
results.push(...checkTargetAgents(cwd));
|
|
3220
|
+
// Skill reference format validation
|
|
2386
3221
|
results.push(...checkSkillRefs(cwd));
|
|
3222
|
+
// Monorepo version consistency
|
|
2387
3223
|
results.push(...checkMonorepoVersions(cwd));
|
|
2388
3224
|
}
|
|
3225
|
+
// Network checks
|
|
2389
3226
|
if (!skipNetwork) {
|
|
2390
3227
|
results.push(await checkNetwork('https://github.com'));
|
|
2391
3228
|
results.push(await checkNetwork('https://gitlab.com'));
|
|
2392
3229
|
}
|
|
2393
3230
|
return results;
|
|
2394
3231
|
}
|
|
2395
|
-
|
|
3232
|
+
/**
|
|
3233
|
+
* doctor command - Diagnose reskill environment
|
|
3234
|
+
*/ const doctorCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('doctor').description('Diagnose reskill environment and check for issues').option('--json', 'Output as JSON').option('--skip-network', 'Skip network connectivity checks').action(async (options)=>{
|
|
3235
|
+
// Get package info
|
|
2396
3236
|
const __dirname = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.dirname)((0, __WEBPACK_EXTERNAL_MODULE_node_url__.fileURLToPath)(import.meta.url));
|
|
3237
|
+
// In bundled output, __dirname is dist/cli/, so ../../package.json
|
|
2397
3238
|
const packageJson = JSON.parse((0, external_node_fs_.readFileSync)((0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(__dirname, '../../package.json'), 'utf-8'));
|
|
2398
3239
|
if (!options.json) logger_logger.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold('\n🩺 Checking reskill environment...\n'));
|
|
2399
3240
|
const results = await runDoctorChecks({
|
|
@@ -2406,7 +3247,9 @@ const doctorCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('doctor'
|
|
|
2406
3247
|
console.log(JSON.stringify(results, null, 2));
|
|
2407
3248
|
return;
|
|
2408
3249
|
}
|
|
3250
|
+
// Print results
|
|
2409
3251
|
for (const result of results)printResult(result);
|
|
3252
|
+
// Summary
|
|
2410
3253
|
const errors = results.filter((r)=>'error' === r.status).length;
|
|
2411
3254
|
const warnings = results.filter((r)=>'warn' === r.status).length;
|
|
2412
3255
|
logger_logger.newline();
|
|
@@ -2417,7 +3260,9 @@ const doctorCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('doctor'
|
|
|
2417
3260
|
if (warnings > 0) logger_logger.warn(`Found ${warnings} warning${1 !== warnings ? 's' : ''}, but reskill should work`);
|
|
2418
3261
|
else logger_logger.success('All checks passed! reskill is ready to use.');
|
|
2419
3262
|
});
|
|
2420
|
-
|
|
3263
|
+
/**
|
|
3264
|
+
* info command - Show skill details
|
|
3265
|
+
*/ const infoCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('info').description('Show skill details').argument('<skill>', 'Skill name').option('-j, --json', 'Output as JSON').action((skillName, options)=>{
|
|
2421
3266
|
const skillManager = new SkillManager();
|
|
2422
3267
|
const info = skillManager.getInfo(skillName);
|
|
2423
3268
|
if (options.json) {
|
|
@@ -2456,8 +3301,16 @@ const infoCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('info').de
|
|
|
2456
3301
|
}
|
|
2457
3302
|
} else logger_logger.warn(`Skill ${skillName} is not installed`);
|
|
2458
3303
|
});
|
|
3304
|
+
// ============================================================================
|
|
3305
|
+
// Constants
|
|
3306
|
+
// ============================================================================
|
|
2459
3307
|
const DEFAULT_INSTALL_DIR = '.skills';
|
|
2460
|
-
|
|
3308
|
+
// ============================================================================
|
|
3309
|
+
// Helper Functions
|
|
3310
|
+
// ============================================================================
|
|
3311
|
+
/**
|
|
3312
|
+
* Display configuration summary after initialization
|
|
3313
|
+
*/ function displayConfigSummary(installDir) {
|
|
2461
3314
|
logger_logger.success('Created skills.json');
|
|
2462
3315
|
logger_logger.newline();
|
|
2463
3316
|
logger_logger.log('Configuration:');
|
|
@@ -2467,38 +3320,74 @@ function displayConfigSummary(installDir) {
|
|
|
2467
3320
|
logger_logger.log(' reskill install <skill> Install a skill');
|
|
2468
3321
|
logger_logger.log(' reskill list List installed skills');
|
|
2469
3322
|
}
|
|
2470
|
-
|
|
3323
|
+
// ============================================================================
|
|
3324
|
+
// Command Definition
|
|
3325
|
+
// ============================================================================
|
|
3326
|
+
/**
|
|
3327
|
+
* init command - Initialize skills.json configuration
|
|
3328
|
+
*
|
|
3329
|
+
* Creates a new skills.json file in the current directory with default settings.
|
|
3330
|
+
* Will not overwrite an existing skills.json file.
|
|
3331
|
+
*
|
|
3332
|
+
* @example
|
|
3333
|
+
* ```bash
|
|
3334
|
+
* # Initialize with defaults
|
|
3335
|
+
* reskill init
|
|
3336
|
+
*
|
|
3337
|
+
* # Initialize with custom install directory
|
|
3338
|
+
* reskill init -d my-skills
|
|
3339
|
+
*
|
|
3340
|
+
* # Skip prompts (for CI/scripts)
|
|
3341
|
+
* reskill init -y
|
|
3342
|
+
* ```
|
|
3343
|
+
*/ const initCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('init').description('Initialize a new skills.json configuration').option('-d, --install-dir <dir>', 'Skills installation directory', DEFAULT_INSTALL_DIR).option('-y, --yes', 'Skip prompts and use defaults').action((options)=>{
|
|
2471
3344
|
const configLoader = new ConfigLoader();
|
|
3345
|
+
// Check if configuration already exists
|
|
2472
3346
|
if (configLoader.exists()) {
|
|
2473
3347
|
logger_logger.warn('skills.json already exists');
|
|
2474
3348
|
return;
|
|
2475
3349
|
}
|
|
3350
|
+
// Create new configuration
|
|
2476
3351
|
configLoader.create({
|
|
2477
3352
|
defaults: {
|
|
2478
3353
|
installDir: options.installDir
|
|
2479
3354
|
}
|
|
2480
3355
|
});
|
|
3356
|
+
// Display summary (use options.installDir directly since we just set it)
|
|
2481
3357
|
displayConfigSummary(options.installDir);
|
|
2482
3358
|
});
|
|
2483
|
-
|
|
3359
|
+
// ============================================================================
|
|
3360
|
+
// Utility Functions
|
|
3361
|
+
// ============================================================================
|
|
3362
|
+
/**
|
|
3363
|
+
* Format agent names list for display
|
|
3364
|
+
* Truncates long lists with "+N more" suffix
|
|
3365
|
+
*/ function formatAgentNames(agentTypes, maxShow = 5) {
|
|
2484
3366
|
const names = agentTypes.map((a)=>agents[a].displayName);
|
|
2485
3367
|
if (names.length <= maxShow) return names.join(', ');
|
|
2486
3368
|
const shown = names.slice(0, maxShow);
|
|
2487
3369
|
const remaining = names.length - maxShow;
|
|
2488
3370
|
return `${shown.join(', ')} +${remaining} more`;
|
|
2489
3371
|
}
|
|
2490
|
-
|
|
3372
|
+
/**
|
|
3373
|
+
* Format agent names with chalk coloring
|
|
3374
|
+
*/ function formatColoredAgentNames(agentTypes) {
|
|
2491
3375
|
return agentTypes.map((a)=>__WEBPACK_EXTERNAL_MODULE_chalk__["default"].cyan(agents[a].displayName)).join(', ');
|
|
2492
3376
|
}
|
|
2493
|
-
|
|
3377
|
+
/**
|
|
3378
|
+
* Filter valid agent types from stored configuration
|
|
3379
|
+
*/ function filterValidAgents(storedAgents, validAgents) {
|
|
2494
3380
|
if (!storedAgents || 0 === storedAgents.length) return;
|
|
2495
3381
|
const filtered = storedAgents.filter((a)=>validAgents.includes(a));
|
|
2496
3382
|
return filtered.length > 0 ? filtered : void 0;
|
|
2497
3383
|
}
|
|
2498
|
-
|
|
3384
|
+
/**
|
|
3385
|
+
* Create install context from command arguments and options
|
|
3386
|
+
*/ function createInstallContext(skills, options) {
|
|
2499
3387
|
const configLoader = new ConfigLoader();
|
|
2500
3388
|
const allAgentTypes = Object.keys(agents);
|
|
2501
3389
|
const hasSkillsJson = configLoader.exists();
|
|
3390
|
+
// Load stored defaults from skills.json
|
|
2502
3391
|
const storedDefaults = hasSkillsJson ? configLoader.getDefaults() : null;
|
|
2503
3392
|
const storedAgents = filterValidAgents(storedDefaults?.targetAgents, allAgentTypes);
|
|
2504
3393
|
const storedMode = storedDefaults?.installMode;
|
|
@@ -2516,20 +3405,31 @@ function createInstallContext(skills, options) {
|
|
|
2516
3405
|
skipConfirm: options.yes ?? false
|
|
2517
3406
|
};
|
|
2518
3407
|
}
|
|
2519
|
-
|
|
3408
|
+
// ============================================================================
|
|
3409
|
+
// Agent Selection Logic
|
|
3410
|
+
// ============================================================================
|
|
3411
|
+
/**
|
|
3412
|
+
* Resolve target agents based on options and context
|
|
3413
|
+
*/ async function resolveTargetAgents(ctx, spinner) {
|
|
2520
3414
|
const { options, allAgentTypes, storedAgents, hasStoredAgents, isReinstallAll } = ctx;
|
|
3415
|
+
// Priority 1: --all flag
|
|
2521
3416
|
if (options.all) {
|
|
2522
3417
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.info(`Installing to all ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].cyan(allAgentTypes.length)} agents`);
|
|
2523
3418
|
return allAgentTypes;
|
|
2524
3419
|
}
|
|
3420
|
+
// Priority 2: -a/--agent flag
|
|
2525
3421
|
if (options.agent && options.agent.length > 0) return resolveAgentsFromCLI(options.agent, allAgentTypes);
|
|
3422
|
+
// Priority 3: Reinstall all with stored agents
|
|
2526
3423
|
if (isReinstallAll && hasStoredAgents && storedAgents) {
|
|
2527
3424
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.info(`Using saved agents: ${formatColoredAgentNames(storedAgents)}`);
|
|
2528
3425
|
return storedAgents;
|
|
2529
3426
|
}
|
|
3427
|
+
// Priority 4: Auto-detect and/or prompt
|
|
2530
3428
|
return await detectAndPromptAgents(ctx, spinner);
|
|
2531
3429
|
}
|
|
2532
|
-
|
|
3430
|
+
/**
|
|
3431
|
+
* Resolve agents from CLI -a option
|
|
3432
|
+
*/ function resolveAgentsFromCLI(agentArgs, validAgents) {
|
|
2533
3433
|
const invalidAgents = agentArgs.filter((a)=>!validAgents.includes(a));
|
|
2534
3434
|
if (invalidAgents.length > 0) {
|
|
2535
3435
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.error(`Invalid agents: ${invalidAgents.join(', ')}`);
|
|
@@ -2540,11 +3440,14 @@ function resolveAgentsFromCLI(agentArgs, validAgents) {
|
|
|
2540
3440
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.info(`Installing to: ${formatAgentNames(targetAgents)}`);
|
|
2541
3441
|
return targetAgents;
|
|
2542
3442
|
}
|
|
2543
|
-
|
|
3443
|
+
/**
|
|
3444
|
+
* Auto-detect agents and optionally prompt user
|
|
3445
|
+
*/ async function detectAndPromptAgents(ctx, spinner) {
|
|
2544
3446
|
const { allAgentTypes, storedAgents, hasStoredAgents, skipConfirm } = ctx;
|
|
2545
3447
|
spinner.start('Detecting installed agents...');
|
|
2546
3448
|
const installedAgents = await detectInstalledAgents();
|
|
2547
3449
|
spinner.stop(`Detected ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].green(installedAgents.length)} agent${1 !== installedAgents.length ? 's' : ''}`);
|
|
3450
|
+
// No agents detected
|
|
2548
3451
|
if (0 === installedAgents.length) {
|
|
2549
3452
|
if (skipConfirm) {
|
|
2550
3453
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.info('Installing to all agents (none detected)');
|
|
@@ -2552,15 +3455,19 @@ async function detectAndPromptAgents(ctx, spinner) {
|
|
|
2552
3455
|
}
|
|
2553
3456
|
return await promptAgentSelection(allAgentTypes, hasStoredAgents ? storedAgents : allAgentTypes);
|
|
2554
3457
|
}
|
|
3458
|
+
// Single agent or skip confirmation
|
|
2555
3459
|
if (1 === installedAgents.length || skipConfirm) {
|
|
2556
3460
|
const displayNames = formatColoredAgentNames(installedAgents);
|
|
2557
3461
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.info(`Installing to: ${displayNames}`);
|
|
2558
3462
|
return installedAgents;
|
|
2559
3463
|
}
|
|
3464
|
+
// Multiple agents: let user select
|
|
2560
3465
|
const initialAgents = hasStoredAgents ? storedAgents : installedAgents;
|
|
2561
3466
|
return await promptAgentSelection(installedAgents, initialAgents, true);
|
|
2562
3467
|
}
|
|
2563
|
-
|
|
3468
|
+
/**
|
|
3469
|
+
* Prompt user to select agents
|
|
3470
|
+
*/ async function promptAgentSelection(availableAgents, initialValues, showHint = false) {
|
|
2564
3471
|
if (0 === availableAgents.length) __WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.warn('No coding agents detected. You can still install skills.');
|
|
2565
3472
|
const agentChoices = availableAgents.map((a)=>({
|
|
2566
3473
|
value: a,
|
|
@@ -2584,11 +3491,20 @@ async function promptAgentSelection(availableAgents, initialValues, showHint = f
|
|
|
2584
3491
|
}
|
|
2585
3492
|
return selected;
|
|
2586
3493
|
}
|
|
2587
|
-
|
|
3494
|
+
// ============================================================================
|
|
3495
|
+
// Installation Scope Logic
|
|
3496
|
+
// ============================================================================
|
|
3497
|
+
/**
|
|
3498
|
+
* Resolve installation scope (global vs project)
|
|
3499
|
+
*/ async function resolveInstallScope(ctx) {
|
|
2588
3500
|
const { options, isReinstallAll, skipConfirm } = ctx;
|
|
3501
|
+
// Explicit --global flag
|
|
2589
3502
|
if (void 0 !== options.global) return options.global;
|
|
3503
|
+
// Skip prompt for reinstall-all (always project scope)
|
|
2590
3504
|
if (isReinstallAll) return false;
|
|
3505
|
+
// Skip prompt if --yes
|
|
2591
3506
|
if (skipConfirm) return false;
|
|
3507
|
+
// Prompt user
|
|
2592
3508
|
const scope = await __WEBPACK_EXTERNAL_MODULE__clack_prompts__.select({
|
|
2593
3509
|
message: 'Installation scope',
|
|
2594
3510
|
options: [
|
|
@@ -2610,14 +3526,23 @@ async function resolveInstallScope(ctx) {
|
|
|
2610
3526
|
}
|
|
2611
3527
|
return scope;
|
|
2612
3528
|
}
|
|
2613
|
-
|
|
3529
|
+
// ============================================================================
|
|
3530
|
+
// Installation Mode Logic
|
|
3531
|
+
// ============================================================================
|
|
3532
|
+
/**
|
|
3533
|
+
* Resolve installation mode (symlink vs copy)
|
|
3534
|
+
*/ async function resolveInstallMode(ctx) {
|
|
2614
3535
|
const { options, storedMode, isReinstallAll, skipConfirm } = ctx;
|
|
3536
|
+
// Priority 1: CLI --mode option
|
|
2615
3537
|
if (options.mode) return options.mode;
|
|
3538
|
+
// Priority 2: Reinstall all with stored mode
|
|
2616
3539
|
if (isReinstallAll && storedMode) {
|
|
2617
3540
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.info(`Using saved install mode: ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].cyan(storedMode)}`);
|
|
2618
3541
|
return storedMode;
|
|
2619
3542
|
}
|
|
3543
|
+
// Priority 3: Skip confirmation
|
|
2620
3544
|
if (skipConfirm) return storedMode ?? 'symlink';
|
|
3545
|
+
// Priority 4: Prompt user
|
|
2621
3546
|
const modeChoice = await __WEBPACK_EXTERNAL_MODULE__clack_prompts__.select({
|
|
2622
3547
|
message: 'Installation method',
|
|
2623
3548
|
initialValue: storedMode ?? 'symlink',
|
|
@@ -2640,7 +3565,12 @@ async function resolveInstallMode(ctx) {
|
|
|
2640
3565
|
}
|
|
2641
3566
|
return modeChoice;
|
|
2642
3567
|
}
|
|
2643
|
-
|
|
3568
|
+
// ============================================================================
|
|
3569
|
+
// Installation Execution
|
|
3570
|
+
// ============================================================================
|
|
3571
|
+
/**
|
|
3572
|
+
* Install all skills from skills.json
|
|
3573
|
+
*/ async function installAllSkills(ctx, targetAgents, installMode, spinner) {
|
|
2644
3574
|
const { configLoader, options } = ctx;
|
|
2645
3575
|
if (!configLoader.exists()) {
|
|
2646
3576
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.error("skills.json not found. Run 'reskill init' first.");
|
|
@@ -2652,12 +3582,14 @@ async function installAllSkills(ctx, targetAgents, installMode, spinner) {
|
|
|
2652
3582
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.outro('Done');
|
|
2653
3583
|
return;
|
|
2654
3584
|
}
|
|
3585
|
+
// Show installation summary
|
|
2655
3586
|
displayInstallSummary({
|
|
2656
3587
|
skillCount: Object.keys(skills).length,
|
|
2657
3588
|
agentCount: targetAgents.length,
|
|
2658
3589
|
scope: 'Project (./) ',
|
|
2659
3590
|
mode: installMode
|
|
2660
3591
|
});
|
|
3592
|
+
// Execute installation (no confirmation for reinstall all)
|
|
2661
3593
|
spinner.start('Installing skills...');
|
|
2662
3594
|
const skillManager = new SkillManager(void 0, {
|
|
2663
3595
|
global: false
|
|
@@ -2678,22 +3610,28 @@ async function installAllSkills(ctx, targetAgents, installMode, spinner) {
|
|
|
2678
3610
|
totalFailed += targetAgents.length;
|
|
2679
3611
|
}
|
|
2680
3612
|
spinner.stop('Installation complete');
|
|
3613
|
+
// Show results
|
|
2681
3614
|
displayInstallResults(Object.keys(skills).length, targetAgents.length, totalInstalled, totalFailed);
|
|
3615
|
+
// Save installation defaults
|
|
2682
3616
|
if (totalInstalled > 0) configLoader.updateDefaults({
|
|
2683
3617
|
targetAgents,
|
|
2684
3618
|
installMode
|
|
2685
3619
|
});
|
|
2686
3620
|
}
|
|
2687
|
-
|
|
3621
|
+
/**
|
|
3622
|
+
* Install a single skill
|
|
3623
|
+
*/ async function installSingleSkill(ctx, targetAgents, installGlobally, installMode, spinner) {
|
|
2688
3624
|
const { skills, options, configLoader, skipConfirm } = ctx;
|
|
2689
3625
|
const skill = skills[0];
|
|
2690
3626
|
const cwd = process.cwd();
|
|
3627
|
+
// Show installation summary
|
|
2691
3628
|
const summaryLines = [
|
|
2692
3629
|
__WEBPACK_EXTERNAL_MODULE_chalk__["default"].cyan(skill),
|
|
2693
3630
|
` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim('→')} ${formatAgentNames(targetAgents)}`,
|
|
2694
3631
|
` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim('Scope:')} ${installGlobally ? 'Global' : 'Project'}${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim(', Mode:')} ${installMode}`
|
|
2695
3632
|
];
|
|
2696
3633
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.note(summaryLines.join('\n'), 'Installation Summary');
|
|
3634
|
+
// Confirm installation
|
|
2697
3635
|
if (!skipConfirm) {
|
|
2698
3636
|
const confirmed = await __WEBPACK_EXTERNAL_MODULE__clack_prompts__.confirm({
|
|
2699
3637
|
message: 'Proceed with installation?'
|
|
@@ -2703,6 +3641,7 @@ async function installSingleSkill(ctx, targetAgents, installGlobally, installMod
|
|
|
2703
3641
|
process.exit(0);
|
|
2704
3642
|
}
|
|
2705
3643
|
}
|
|
3644
|
+
// Execute installation
|
|
2706
3645
|
spinner.start(`Installing ${skill}...`);
|
|
2707
3646
|
const skillManager = new SkillManager(void 0, {
|
|
2708
3647
|
global: installGlobally
|
|
@@ -2713,19 +3652,24 @@ async function installSingleSkill(ctx, targetAgents, installGlobally, installMod
|
|
|
2713
3652
|
mode: installMode
|
|
2714
3653
|
});
|
|
2715
3654
|
spinner.stop('Installation complete');
|
|
3655
|
+
// Process and display results
|
|
2716
3656
|
const successful = Array.from(results.entries()).filter(([, r])=>r.success);
|
|
2717
3657
|
const failed = Array.from(results.entries()).filter(([, r])=>!r.success);
|
|
2718
3658
|
displaySingleSkillResults(installed, successful, failed, cwd);
|
|
3659
|
+
// Save installation defaults (only for project installs with success)
|
|
2719
3660
|
if (!installGlobally && successful.length > 0 && configLoader.exists()) {
|
|
2720
|
-
configLoader.reload();
|
|
3661
|
+
configLoader.reload(); // Sync with SkillManager's changes
|
|
2721
3662
|
configLoader.updateDefaults({
|
|
2722
3663
|
targetAgents,
|
|
2723
3664
|
installMode
|
|
2724
3665
|
});
|
|
2725
3666
|
}
|
|
2726
3667
|
}
|
|
2727
|
-
|
|
3668
|
+
/**
|
|
3669
|
+
* Install multiple skills in batch
|
|
3670
|
+
*/ async function installMultipleSkills(ctx, targetAgents, installGlobally, installMode, spinner) {
|
|
2728
3671
|
const { skills, options, configLoader, skipConfirm } = ctx;
|
|
3672
|
+
// Show installation summary
|
|
2729
3673
|
const summaryLines = [
|
|
2730
3674
|
`${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].cyan(skills.length)} skills:`,
|
|
2731
3675
|
...skills.map((s)=>` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim('•')} ${s}`),
|
|
@@ -2734,6 +3678,7 @@ async function installMultipleSkills(ctx, targetAgents, installGlobally, install
|
|
|
2734
3678
|
` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim('Scope:')} ${installGlobally ? 'Global' : 'Project'}${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim(', Mode:')} ${installMode}`
|
|
2735
3679
|
];
|
|
2736
3680
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.note(summaryLines.join('\n'), 'Installation Summary');
|
|
3681
|
+
// Confirm installation
|
|
2737
3682
|
if (!skipConfirm) {
|
|
2738
3683
|
const confirmed = await __WEBPACK_EXTERNAL_MODULE__clack_prompts__.confirm({
|
|
2739
3684
|
message: 'Proceed with installation?'
|
|
@@ -2743,12 +3688,14 @@ async function installMultipleSkills(ctx, targetAgents, installGlobally, install
|
|
|
2743
3688
|
process.exit(0);
|
|
2744
3689
|
}
|
|
2745
3690
|
}
|
|
3691
|
+
// Execute installation for all skills in parallel
|
|
2746
3692
|
const skillManager = new SkillManager(void 0, {
|
|
2747
3693
|
global: installGlobally
|
|
2748
3694
|
});
|
|
2749
3695
|
const successfulSkills = [];
|
|
2750
3696
|
const failedSkills = [];
|
|
2751
3697
|
spinner.start(`Installing ${skills.length} skills in parallel...`);
|
|
3698
|
+
// Create install promises for all skills
|
|
2752
3699
|
const installPromises = skills.map(async (skillRef)=>{
|
|
2753
3700
|
try {
|
|
2754
3701
|
const { skill: installed, results } = await skillManager.installToAgents(skillRef, targetAgents, {
|
|
@@ -2776,8 +3723,10 @@ async function installMultipleSkills(ctx, targetAgents, installGlobally, install
|
|
|
2776
3723
|
};
|
|
2777
3724
|
}
|
|
2778
3725
|
});
|
|
3726
|
+
// Wait for all installations to complete
|
|
2779
3727
|
const results = await Promise.all(installPromises);
|
|
2780
3728
|
spinner.stop(`Processed ${skills.length} skills`);
|
|
3729
|
+
// Aggregate results
|
|
2781
3730
|
for (const result of results)if (result.success) {
|
|
2782
3731
|
successfulSkills.push(result.skill);
|
|
2783
3732
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.success(`${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].green('✓')} ${result.skill.name}@${result.skill.version}`);
|
|
@@ -2788,29 +3737,38 @@ async function installMultipleSkills(ctx, targetAgents, installGlobally, install
|
|
|
2788
3737
|
});
|
|
2789
3738
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.error(`${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].red('✗')} ${result.skillRef}`);
|
|
2790
3739
|
}
|
|
3740
|
+
// Display batch results
|
|
2791
3741
|
console.log();
|
|
2792
3742
|
displayBatchInstallResults(successfulSkills, failedSkills, targetAgents.length);
|
|
3743
|
+
// Save installation defaults (only for project installs with success)
|
|
2793
3744
|
if (!installGlobally && successfulSkills.length > 0 && configLoader.exists()) {
|
|
2794
|
-
configLoader.reload();
|
|
3745
|
+
configLoader.reload(); // Sync with SkillManager's changes
|
|
2795
3746
|
configLoader.updateDefaults({
|
|
2796
3747
|
targetAgents,
|
|
2797
3748
|
installMode
|
|
2798
3749
|
});
|
|
2799
3750
|
}
|
|
3751
|
+
// Exit with error if any skills failed
|
|
2800
3752
|
if (failedSkills.length > 0) process.exit(1);
|
|
2801
3753
|
}
|
|
2802
|
-
|
|
3754
|
+
/**
|
|
3755
|
+
* Display installation summary note
|
|
3756
|
+
*/ function displayInstallSummary(info) {
|
|
2803
3757
|
const summaryLines = [
|
|
2804
3758
|
`${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].cyan(info.skillCount)} skill(s) → ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].cyan(info.agentCount)} agent(s)`,
|
|
2805
3759
|
`${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim('Scope:')} ${info.scope}${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim(', Mode:')} ${info.mode}`
|
|
2806
3760
|
];
|
|
2807
3761
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.note(summaryLines.join('\n'), 'Installation Summary');
|
|
2808
3762
|
}
|
|
2809
|
-
|
|
3763
|
+
/**
|
|
3764
|
+
* Display installation results for batch install
|
|
3765
|
+
*/ function displayInstallResults(skillCount, agentCount, totalInstalled, totalFailed) {
|
|
2810
3766
|
if (0 === totalFailed) __WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.success(`Installed ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].green(skillCount)} skill(s) to ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].green(agentCount)} agent(s)`);
|
|
2811
3767
|
else __WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.warn(`Installed ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].green(totalInstalled)} successfully, ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].red(totalFailed)} failed`);
|
|
2812
3768
|
}
|
|
2813
|
-
|
|
3769
|
+
/**
|
|
3770
|
+
* Display results for batch skill installation
|
|
3771
|
+
*/ function displayBatchInstallResults(successfulSkills, failedSkills, agentCount) {
|
|
2814
3772
|
if (successfulSkills.length > 0) {
|
|
2815
3773
|
const resultLines = successfulSkills.map((s)=>` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].green('✓')} ${s.name}@${s.version}`);
|
|
2816
3774
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.note(resultLines.join('\n'), __WEBPACK_EXTERNAL_MODULE_chalk__["default"].green(`Installed ${successfulSkills.length} skill(s) to ${agentCount} agent(s)`));
|
|
@@ -2820,7 +3778,9 @@ function displayBatchInstallResults(successfulSkills, failedSkills, agentCount)
|
|
|
2820
3778
|
for (const { ref, error } of failedSkills)__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.message(` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].red('✗')} ${ref}: ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim(error)}`);
|
|
2821
3779
|
}
|
|
2822
3780
|
}
|
|
2823
|
-
|
|
3781
|
+
/**
|
|
3782
|
+
* Display results for single skill installation
|
|
3783
|
+
*/ function displaySingleSkillResults(installed, successful, failed, cwd) {
|
|
2824
3784
|
if (successful.length > 0) {
|
|
2825
3785
|
const resultLines = [];
|
|
2826
3786
|
const firstResult = successful[0][1];
|
|
@@ -2828,6 +3788,7 @@ function displaySingleSkillResults(installed, successful, failed, cwd) {
|
|
|
2828
3788
|
resultLines.push(`${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].green('✓')} ${installed.name}@${installed.version} ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim('(copied)')}`);
|
|
2829
3789
|
for (const [, result] of successful)resultLines.push(` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim('→')} ${shortenPath(result.path, cwd)}`);
|
|
2830
3790
|
} else {
|
|
3791
|
+
// Symlink mode
|
|
2831
3792
|
const displayPath = firstResult.canonicalPath ? shortenPath(firstResult.canonicalPath, cwd) : `${installed.name}@${installed.version}`;
|
|
2832
3793
|
resultLines.push(`${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].green('✓')} ${displayPath}`);
|
|
2833
3794
|
const symlinked = successful.filter(([, r])=>!r.symlinkFailed).map(([a])=>agents[a].displayName);
|
|
@@ -2836,6 +3797,7 @@ function displaySingleSkillResults(installed, successful, failed, cwd) {
|
|
|
2836
3797
|
if (copied.length > 0) resultLines.push(` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].yellow('copied →')} ${copied.join(', ')}`);
|
|
2837
3798
|
}
|
|
2838
3799
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.note(resultLines.join('\n'), __WEBPACK_EXTERNAL_MODULE_chalk__["default"].green(`Installed 1 skill to ${successful.length} agent${1 !== successful.length ? 's' : ''}`));
|
|
3800
|
+
// Symlink failure warning
|
|
2839
3801
|
const symlinkFailed = successful.filter(([, r])=>'symlink' === r.mode && r.symlinkFailed);
|
|
2840
3802
|
if (symlinkFailed.length > 0) {
|
|
2841
3803
|
const copiedAgentNames = symlinkFailed.map(([a])=>agents[a].displayName);
|
|
@@ -2843,31 +3805,57 @@ function displaySingleSkillResults(installed, successful, failed, cwd) {
|
|
|
2843
3805
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.message(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim(' Files were copied instead. On Windows, enable Developer Mode for symlink support.'));
|
|
2844
3806
|
}
|
|
2845
3807
|
}
|
|
3808
|
+
// Show failure message
|
|
2846
3809
|
if (failed.length > 0) {
|
|
2847
3810
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.error(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].red(`Failed to install to ${failed.length} agent(s)`));
|
|
2848
3811
|
for (const [agent, result] of failed)__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.message(` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].red('✗')} ${agents[agent].displayName}: ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim(result.error)}`);
|
|
2849
3812
|
}
|
|
2850
3813
|
}
|
|
2851
|
-
|
|
3814
|
+
// ============================================================================
|
|
3815
|
+
// Command Definition
|
|
3816
|
+
// ============================================================================
|
|
3817
|
+
/**
|
|
3818
|
+
* install command - Install a skill or all skills from skills.json
|
|
3819
|
+
*
|
|
3820
|
+
* Installation Flow:
|
|
3821
|
+
* 1. Resolve target agents (CLI > stored > detected > prompt)
|
|
3822
|
+
* 2. Resolve installation scope (global vs project)
|
|
3823
|
+
* 3. Resolve installation mode (symlink vs copy)
|
|
3824
|
+
* 4. Execute installation
|
|
3825
|
+
* 5. Save defaults for future installs
|
|
3826
|
+
*
|
|
3827
|
+
* Behavior:
|
|
3828
|
+
* - Single skill install: Prompts for agents/mode (stored config as defaults)
|
|
3829
|
+
* - Reinstall all (no args): Uses stored config directly, no confirmation
|
|
3830
|
+
*/ const installCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('install').alias('i').description('Install one or more skills, or all skills from skills.json').argument('[skills...]', 'Skill references (e.g., github:user/skill@v1.0.0 or git@github.com:user/repo.git)').option('-f, --force', 'Force reinstall even if already installed').option('-g, --global', 'Install globally to user home directory').option('--no-save', 'Do not save to skills.json').option('-a, --agent <agents...>', 'Specify target agents (e.g., cursor, claude-code)').option('--mode <mode>', 'Installation mode: symlink or copy').option('-y, --yes', 'Skip confirmation prompts').option('--all', 'Install to all agents (implies -y -g)').action(async (skills, options)=>{
|
|
3831
|
+
// Handle --all flag implications
|
|
2852
3832
|
if (options.all) {
|
|
2853
3833
|
options.yes = true;
|
|
2854
3834
|
options.global = true;
|
|
2855
3835
|
}
|
|
3836
|
+
// Create execution context
|
|
2856
3837
|
const ctx = createInstallContext(skills, options);
|
|
3838
|
+
// Print banner
|
|
2857
3839
|
console.log();
|
|
2858
3840
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.intro(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bgCyan.black(' reskill '));
|
|
2859
3841
|
try {
|
|
2860
3842
|
const spinner = __WEBPACK_EXTERNAL_MODULE__clack_prompts__.spinner();
|
|
3843
|
+
// Step 1: Resolve target agents
|
|
2861
3844
|
const targetAgents = await resolveTargetAgents(ctx, spinner);
|
|
3845
|
+
// Step 2: Resolve installation scope
|
|
2862
3846
|
const installGlobally = await resolveInstallScope(ctx);
|
|
3847
|
+
// Validate: Cannot install all skills globally
|
|
2863
3848
|
if (ctx.isReinstallAll && installGlobally) {
|
|
2864
3849
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.error('Cannot install all skills globally. Please specify a skill to install.');
|
|
2865
3850
|
process.exit(1);
|
|
2866
3851
|
}
|
|
3852
|
+
// Step 3: Resolve installation mode
|
|
2867
3853
|
const installMode = await resolveInstallMode(ctx);
|
|
3854
|
+
// Step 4: Execute installation
|
|
2868
3855
|
if (ctx.isReinstallAll) await installAllSkills(ctx, targetAgents, installMode, spinner);
|
|
2869
3856
|
else if (ctx.isBatchInstall) await installMultipleSkills(ctx, targetAgents, installGlobally, installMode, spinner);
|
|
2870
3857
|
else await installSingleSkill(ctx, targetAgents, installGlobally, installMode, spinner);
|
|
3858
|
+
// Done
|
|
2871
3859
|
console.log();
|
|
2872
3860
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.outro(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].green('Done!'));
|
|
2873
3861
|
} catch (error) {
|
|
@@ -2876,7 +3864,9 @@ const installCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('instal
|
|
|
2876
3864
|
process.exit(1);
|
|
2877
3865
|
}
|
|
2878
3866
|
});
|
|
2879
|
-
|
|
3867
|
+
/**
|
|
3868
|
+
* list command - List installed skills
|
|
3869
|
+
*/ const listCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('list').alias('ls').description('List installed skills').option('-j, --json', 'Output as JSON').option('-g, --global', 'List globally installed skills').action((options)=>{
|
|
2880
3870
|
const isGlobal = options.global || false;
|
|
2881
3871
|
const skillManager = new SkillManager(void 0, {
|
|
2882
3872
|
global: isGlobal
|
|
@@ -2908,7 +3898,9 @@ const listCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('list').al
|
|
|
2908
3898
|
logger_logger.newline();
|
|
2909
3899
|
logger_logger.log(`Total: ${skills.length} skill(s)`);
|
|
2910
3900
|
});
|
|
2911
|
-
|
|
3901
|
+
/**
|
|
3902
|
+
* outdated command - Check for outdated skills
|
|
3903
|
+
*/ const outdatedCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('outdated').description('Check for outdated skills').option('-j, --json', 'Output as JSON').action(async (options)=>{
|
|
2912
3904
|
const configLoader = new ConfigLoader();
|
|
2913
3905
|
if (!configLoader.exists()) {
|
|
2914
3906
|
logger_logger.error("skills.json not found. Run 'reskill init' first.");
|
|
@@ -2959,7 +3951,9 @@ const outdatedCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('outda
|
|
|
2959
3951
|
process.exit(1);
|
|
2960
3952
|
}
|
|
2961
3953
|
});
|
|
2962
|
-
|
|
3954
|
+
/**
|
|
3955
|
+
* uninstall command - Uninstall a skill
|
|
3956
|
+
*/ const uninstallCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('uninstall').alias('un').alias('remove').alias('rm').description('Uninstall a skill').argument('<skill>', 'Skill name to uninstall').option('-g, --global', 'Uninstall from global installation (~/.claude/skills)').option('-y, --yes', 'Skip confirmation prompts').action(async (skillName, options)=>{
|
|
2963
3957
|
const isGlobal = options.global || false;
|
|
2964
3958
|
const skipConfirm = options.yes || false;
|
|
2965
3959
|
const skillManager = new SkillManager(void 0, {
|
|
@@ -2967,6 +3961,7 @@ const uninstallCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('unin
|
|
|
2967
3961
|
});
|
|
2968
3962
|
console.log();
|
|
2969
3963
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.intro(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bgCyan.black(' reskill '));
|
|
3964
|
+
// Check which agents have this skill installed
|
|
2970
3965
|
const installer = new Installer({
|
|
2971
3966
|
cwd: process.cwd(),
|
|
2972
3967
|
global: isGlobal
|
|
@@ -2979,6 +3974,7 @@ const uninstallCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('unin
|
|
|
2979
3974
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.outro('Done');
|
|
2980
3975
|
process.exit(0);
|
|
2981
3976
|
}
|
|
3977
|
+
// Show uninstallation summary
|
|
2982
3978
|
const summaryLines = [];
|
|
2983
3979
|
summaryLines.push(`${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].cyan(skillName)}`);
|
|
2984
3980
|
summaryLines.push(` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim('→')} ${installedAgents.map((a)=>agents[a].displayName).join(', ')}`);
|
|
@@ -2993,6 +3989,7 @@ const uninstallCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('unin
|
|
|
2993
3989
|
process.exit(0);
|
|
2994
3990
|
}
|
|
2995
3991
|
}
|
|
3992
|
+
// Uninstall from all detected agents
|
|
2996
3993
|
const results = skillManager.uninstallFromAgents(skillName, installedAgents);
|
|
2997
3994
|
const successCount = Array.from(results.values()).filter((r)=>r).length;
|
|
2998
3995
|
if (successCount > 0) __WEBPACK_EXTERNAL_MODULE__clack_prompts__.log.success(`Uninstalled ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].cyan(skillName)} from ${successCount} agent(s)`);
|
|
@@ -3003,7 +4000,9 @@ const uninstallCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('unin
|
|
|
3003
4000
|
console.log();
|
|
3004
4001
|
__WEBPACK_EXTERNAL_MODULE__clack_prompts__.outro(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].green('Done!'));
|
|
3005
4002
|
});
|
|
3006
|
-
|
|
4003
|
+
/**
|
|
4004
|
+
* update command - Update installed skills
|
|
4005
|
+
*/ const updateCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('update').alias('up').description('Update installed skills').argument('[skill]', 'Skill name to update (updates all if not specified)').action(async (skill)=>{
|
|
3007
4006
|
const configLoader = new ConfigLoader();
|
|
3008
4007
|
if (!configLoader.exists()) {
|
|
3009
4008
|
logger_logger.error("skills.json not found. Run 'reskill init' first.");
|
|
@@ -3026,11 +4025,14 @@ const updateCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('update'
|
|
|
3026
4025
|
process.exit(1);
|
|
3027
4026
|
}
|
|
3028
4027
|
});
|
|
4028
|
+
// Handle tab completion early (before commander parsing)
|
|
4029
|
+
// This is needed because tabtab expects to intercept the process early
|
|
3029
4030
|
if (maybeHandleCompletion()) process.exit(0);
|
|
3030
4031
|
const cli_rslib_entry_dirname = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.dirname)((0, __WEBPACK_EXTERNAL_MODULE_node_url__.fileURLToPath)(import.meta.url));
|
|
3031
4032
|
const cli_rslib_entry_packageJson = JSON.parse((0, external_node_fs_.readFileSync)((0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(cli_rslib_entry_dirname, '../../package.json'), 'utf-8'));
|
|
3032
4033
|
const program = new __WEBPACK_EXTERNAL_MODULE_commander__.Command();
|
|
3033
4034
|
program.name('reskill').description('AI Skills Package Manager - Git-based skills management for AI agents').version(cli_rslib_entry_packageJson.version);
|
|
4035
|
+
// Register all commands
|
|
3034
4036
|
program.addCommand(initCommand);
|
|
3035
4037
|
program.addCommand(installCommand);
|
|
3036
4038
|
program.addCommand(listCommand);
|
|
@@ -3040,8 +4042,11 @@ program.addCommand(outdatedCommand);
|
|
|
3040
4042
|
program.addCommand(uninstallCommand);
|
|
3041
4043
|
program.addCommand(completionCommand);
|
|
3042
4044
|
program.addCommand(doctorCommand);
|
|
4045
|
+
// Start update check in background (non-blocking)
|
|
3043
4046
|
const updateCheckPromise = checkForUpdate(cli_rslib_entry_packageJson.name, cli_rslib_entry_packageJson.version);
|
|
4047
|
+
// Parse arguments and wait for async commands to complete
|
|
3044
4048
|
program.parseAsync().then(async ()=>{
|
|
4049
|
+
// After command execution, show update notification if available
|
|
3045
4050
|
const result = await updateCheckPromise;
|
|
3046
4051
|
if (result?.hasUpdate) logger_logger.log(formatUpdateMessage(result));
|
|
3047
4052
|
});
|