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 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
- const logger_logger = {
31
- info (message) {
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
- success (message) {
45
+ /**
46
+ * Success message (green)
47
+ */ success (message) {
35
48
  console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].green('✅'), message);
36
49
  },
37
- warn (message) {
50
+ /**
51
+ * Warning message (yellow)
52
+ */ warn (message) {
38
53
  console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].yellow('⚠️'), message);
39
54
  },
40
- error (message) {
55
+ /**
56
+ * Error message (red)
57
+ */ error (message) {
41
58
  console.error(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].red('❌'), message);
42
59
  },
43
- debug (message) {
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
- package (message) {
65
+ /**
66
+ * Package/skill message (package emoji)
67
+ */ package (message) {
47
68
  console.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].cyan('📦'), message);
48
69
  },
49
- log (message) {
70
+ /**
71
+ * Plain message without icon
72
+ */ log (message) {
50
73
  console.log(message);
51
74
  },
52
- newline () {
75
+ /**
76
+ * Newline
77
+ */ newline () {
53
78
  console.log();
54
79
  },
55
- table (headers, rows) {
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
- async function checkForUpdate(packageName, currentVersion, options = {}) {
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
- function formatUpdateMessage(result) {
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
- const agent_registry_home = (0, __WEBPACK_EXTERNAL_MODULE_node_os__.homedir)();
106
- const agents = {
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
- async function detectInstalledAgents() {
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
- function getAgentConfig(type) {
290
+ /**
291
+ * Get Agent configuration
292
+ */ function getAgentConfig(type) {
233
293
  return agents[type];
234
294
  }
235
- function isValidAgentType(type) {
295
+ /**
296
+ * Validate if Agent type is valid
297
+ */ function isValidAgentType(type) {
236
298
  return type in agents;
237
299
  }
238
- function exists(filePath) {
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
- function readJson(filePath) {
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
- function writeJson(filePath, data, indent = 2) {
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
- function ensureDir(dirPath) {
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
- function remove(targetPath) {
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
- function copyDir(src, dest, options) {
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
- function listDir(dirPath) {
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
- function isDirectory(targetPath) {
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
- function isSymlink(targetPath) {
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
- function getRealPath(linkPath) {
378
+ /**
379
+ * Get real path of symbolic link
380
+ */ function getRealPath(linkPath) {
291
381
  return external_node_fs_.realpathSync(linkPath);
292
382
  }
293
- function getSkillsJsonPath(projectRoot) {
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
- function getSkillsLockPath(projectRoot) {
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
- function getCacheDir() {
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
- function getHomeDir() {
401
+ /**
402
+ * Get home directory
403
+ */ function getHomeDir() {
306
404
  return process.env.HOME || process.env.USERPROFILE || '';
307
405
  }
308
- function getGlobalSkillsDir() {
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
- function shortenPath(fullPath, cwd) {
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
- class GitCloneError extends Error {
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
- static detectUrlType(url) {
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
- static isAuthenticationError(message) {
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
- async function git(args, cwd) {
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
- async function getRemoteTags(repoUrl) {
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
- async function getLatestTag(repoUrl) {
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
- async function clone(repoUrl, destPath, options) {
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
- async function getCurrentCommit(cwd) {
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
- async function getDefaultBranch(repoUrl) {
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
- function compareVersions(a, b) {
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
- function buildRepoUrl(registry, ownerRepo) {
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
- function isGitUrl(source) {
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
- function parseGitUrl(url) {
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
- const installer_AGENTS_DIR = '.agents';
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
- const DEFAULT_EXCLUDE_FILES = [
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
- const EXCLUDE_PREFIX = '_';
509
- function installer_sanitizeName(name) {
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
- function installer_isPathSafe(basePath, targetPath) {
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
- function installer_getCanonicalSkillsDir(isGlobal, cwd, installDir) {
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
- function installer_ensureDir(dirPath) {
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
- function installer_remove(targetPath) {
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
- function copyDirectory(src, dest, options) {
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
- async function installer_createSymlink(target, linkPath) {
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
- class Installer {
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
- getCanonicalPath(skillName) {
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
- getAgentSkillPath(skillName, agentType) {
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
- async installForAgent(sourcePath, skillName, agentType, options = {}) {
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
- async installToAgents(sourcePath, skillName, targetAgents, options = {}) {
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
- isInstalled(skillName, agentType) {
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
- uninstallFromAgent(skillName, agentType) {
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
- uninstallFromAgents(skillName, targetAgents) {
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
- listInstalledSkills(agentType) {
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
- class CacheManager {
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
- getCacheDir() {
986
+ /**
987
+ * Get cache directory
988
+ */ getCacheDir() {
706
989
  return this.cacheDir;
707
990
  }
708
- getSkillCachePath(parsed, version) {
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
- getCachePath(parsed, version) {
1001
+ /**
1002
+ * Get cache path (alias for getSkillCachePath)
1003
+ */ getCachePath(parsed, version) {
712
1004
  return this.getSkillCachePath(parsed, version);
713
1005
  }
714
- isCached(parsed, version) {
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
- async get(parsed, version) {
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
- async cache(repoUrl, parsed, ref, version) {
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
- async copyTo(parsed, version, destPath) {
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
- clearSkill(parsed, version) {
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
- clearAll() {
1101
+ /**
1102
+ * Clear all cache
1103
+ */ clearAll() {
785
1104
  remove(this.cacheDir);
786
1105
  }
787
- getStats() {
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
- async getRemoteCommit(repoUrl, ref) {
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
- const DEFAULT_SKILLS_JSON = {
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
- const DEFAULT_VALUES = {
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
- const DEFAULT_REGISTRIES = {
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
- class ConfigLoader {
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
- getProjectRoot() {
1219
+ // ==========================================================================
1220
+ // Path Accessors
1221
+ // ==========================================================================
1222
+ /**
1223
+ * Get project root directory
1224
+ */ getProjectRoot() {
858
1225
  return this.projectRoot;
859
1226
  }
860
- getConfigPath() {
1227
+ /**
1228
+ * Get configuration file path
1229
+ */ getConfigPath() {
861
1230
  return this.configPath;
862
1231
  }
863
- getInstallDir() {
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
- exists() {
1238
+ // ==========================================================================
1239
+ // File Operations
1240
+ // ==========================================================================
1241
+ /**
1242
+ * Check if configuration file exists
1243
+ */ exists() {
868
1244
  return exists(this.configPath);
869
1245
  }
870
- load() {
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
- reload() {
1260
+ /**
1261
+ * Reload configuration from file (ignores cache)
1262
+ */ reload() {
881
1263
  this.config = null;
882
1264
  return this.load();
883
1265
  }
884
- save(config) {
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
- ensureExists() {
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
- create(options) {
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
- getDefaults() {
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
- updateDefaults(updates) {
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
- getRegistryUrl(registryName) {
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
- addSkill(name, ref) {
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
- removeSkill(name) {
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
- getSkills() {
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
- hasSkill(name) {
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
- getSkillRef(name) {
1397
+ /**
1398
+ * Get skill reference by name
1399
+ */ getSkillRef(name) {
963
1400
  const skills = this.getSkills();
964
1401
  return skills[name];
965
1402
  }
966
- getConfigOrDefault() {
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
- ensureConfigLoaded() {
1413
+ /**
1414
+ * Ensure config is loaded into memory
1415
+ */ ensureConfigLoaded() {
972
1416
  if (!this.config) this.load();
973
1417
  }
974
1418
  }
975
- class GitResolver {
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
- parseRef(ref) {
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
- parseGitUrlRef(ref) {
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
- parseVersion(versionSpec) {
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
- buildRepoUrl(parsed) {
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
- async resolveVersion(repoUrl, versionSpec) {
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
- async resolve(ref) {
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
- const LOCKFILE_VERSION = 1;
1161
- class LockManager {
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
- getLockPath() {
1696
+ /**
1697
+ * Get lock file path
1698
+ */ getLockPath() {
1170
1699
  return this.lockPath;
1171
1700
  }
1172
- exists() {
1701
+ /**
1702
+ * Check if lock file exists
1703
+ */ exists() {
1173
1704
  return exists(this.lockPath);
1174
1705
  }
1175
- load() {
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
- reload() {
1725
+ /**
1726
+ * Reload lock file
1727
+ */ reload() {
1192
1728
  this.lockData = null;
1193
1729
  return this.load();
1194
1730
  }
1195
- save(lockToSave) {
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
- get(name) {
1739
+ /**
1740
+ * Get locked skill
1741
+ */ get(name) {
1202
1742
  const lock = this.load();
1203
1743
  return lock.skills[name];
1204
1744
  }
1205
- set(name, skill) {
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
- remove(name) {
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
- lockSkill(name, options) {
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
- getAll() {
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
- has(name) {
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
- isVersionMatch(name, version) {
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
- clear() {
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
- delete() {
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
- class SkillManager {
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
- isGlobalMode() {
1841
+ /**
1842
+ * Check if in global mode
1843
+ */ isGlobalMode() {
1277
1844
  return this.isGlobal;
1278
1845
  }
1279
- getProjectRoot() {
1846
+ /**
1847
+ * Get project root directory
1848
+ */ getProjectRoot() {
1280
1849
  return this.projectRoot;
1281
1850
  }
1282
- getInstallDir() {
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
- getCanonicalSkillsDir() {
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
- getSkillPath(name) {
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
- async install(ref, options = {}) {
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
- let semanticVersion = gitRef;
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
- async installAll(options = {}) {
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
- uninstall(name) {
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
- checkNeedsUpdate(name, remoteCommit) {
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
- async update(name) {
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
- list() {
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
- getInstalledSkillFromPath(name, skillPath) {
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
- getInstalledSkill(name) {
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
- getInfo(name) {
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
- async checkOutdated() {
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
- async installToAgents(ref, targetAgents, options = {}) {
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
- let semanticVersion = gitRef;
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
- async getDefaultTargetAgents() {
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
- getDefaultInstallMode() {
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
- validateAgentTypes(agentNames) {
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
- getAllAgentTypes() {
2284
+ /**
2285
+ * Get all available agent types
2286
+ */ getAllAgentTypes() {
1600
2287
  return Object.keys(agents);
1601
2288
  }
1602
- uninstallFromAgents(name, targetAgents) {
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
- const SUBCOMMANDS = [
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
- const SKILL_COMPLETION_COMMANDS = [
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
- function getAgentNames() {
2351
+ /**
2352
+ * Get all agent type names for completion
2353
+ */ function getAgentNames() {
1657
2354
  return Object.keys(agents);
1658
2355
  }
1659
- function getInstalledSkillNames() {
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
- function handleCompletion() {
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
- 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)=>{
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
- function maybeHandleCompletion() {
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
- function getStatusIcon(status) {
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
- function execCommand(command) {
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
- function getDirSize(dirPath) {
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
- function formatBytes(bytes) {
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
- function countCachedSkills(cacheDir) {
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
- async function checkReskillVersion(currentVersion, packageName) {
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
- function checkNodeVersion() {
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
- function checkGitVersion() {
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
- function checkGitAuth() {
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
- function checkCacheDir() {
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
- function checkSkillsJson(cwd) {
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
- const RESERVED_REGISTRIES = [
2791
+ /**
2792
+ * Reserved registry names that should not be overridden
2793
+ */ const RESERVED_REGISTRIES = [
2025
2794
  'github',
2026
2795
  'gitlab'
2027
2796
  ];
2028
- const DANGEROUS_INSTALL_DIRS = [
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
- const DANGEROUS_SKILL_NAMES = [
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
- const NETWORK_CHECK_TIMEOUT_MS = 5000;
2050
- const CHECK_NAME_PAD_WIDTH = 26;
2051
- function checkRegistryConflicts(cwd) {
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
- function checkInstallDir(cwd) {
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
- function checkTargetAgents(cwd) {
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
- function checkSkillRefs(cwd) {
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
- function checkMonorepoVersions(cwd) {
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
- function checkSkillsLock(cwd) {
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
- function isValidSymlink(path) {
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
- function validateSkillJson(skillJsonPath) {
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
- function checkInstalledSkills(cwd) {
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
- async function checkNetwork(host) {
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
- function printResult(result) {
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
- async function runDoctorChecks(options) {
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
- 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)=>{
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
- 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)=>{
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
- function displayConfigSummary(installDir) {
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
- 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)=>{
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
- function formatAgentNames(agentTypes, maxShow = 5) {
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
- function formatColoredAgentNames(agentTypes) {
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
- function filterValidAgents(storedAgents, validAgents) {
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
- function createInstallContext(skills, options) {
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
- async function resolveTargetAgents(ctx, spinner) {
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
- function resolveAgentsFromCLI(agentArgs, validAgents) {
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
- async function detectAndPromptAgents(ctx, spinner) {
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
- async function promptAgentSelection(availableAgents, initialValues, showHint = false) {
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
- async function resolveInstallScope(ctx) {
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
- async function resolveInstallMode(ctx) {
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
- async function installAllSkills(ctx, targetAgents, installMode, spinner) {
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
- async function installSingleSkill(ctx, targetAgents, installGlobally, installMode, spinner) {
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
- async function installMultipleSkills(ctx, targetAgents, installGlobally, installMode, spinner) {
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
- function displayInstallSummary(info) {
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
- function displayInstallResults(skillCount, agentCount, totalInstalled, totalFailed) {
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
- function displayBatchInstallResults(successfulSkills, failedSkills, agentCount) {
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
- function displaySingleSkillResults(installed, successful, failed, cwd) {
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
- 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)=>{
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
- 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)=>{
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
- const outdatedCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('outdated').description('Check for outdated skills').option('-j, --json', 'Output as JSON').action(async (options)=>{
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
- 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)=>{
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
- 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)=>{
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
  });