reskill 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3147,6 +3147,236 @@ class RegistryResolver {
3147
3147
  return destDir;
3148
3148
  }
3149
3149
  }
3150
+ /**
3151
+ * Skill Parser - SKILL.md parser
3152
+ *
3153
+ * Following agentskills.io specification: https://agentskills.io/specification
3154
+ *
3155
+ * SKILL.md format requirements:
3156
+ * - YAML frontmatter containing name and description (required)
3157
+ * - name: max 64 characters, lowercase letters, numbers, hyphens
3158
+ * - description: max 1024 characters
3159
+ * - Optional fields: license, compatibility, metadata, allowed-tools
3160
+ */ /**
3161
+ * Skill validation error
3162
+ */ class SkillValidationError extends Error {
3163
+ field;
3164
+ constructor(message, field){
3165
+ super(message), this.field = field;
3166
+ this.name = 'SkillValidationError';
3167
+ }
3168
+ }
3169
+ /**
3170
+ * Simple YAML frontmatter parser
3171
+ * Parses --- delimited YAML header
3172
+ *
3173
+ * Supports:
3174
+ * - Basic key: value pairs
3175
+ * - Multiline strings (| and >)
3176
+ * - Nested objects (one level deep, for metadata field)
3177
+ */ function parseFrontmatter(content) {
3178
+ const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
3179
+ const match = content.match(frontmatterRegex);
3180
+ if (!match) return {
3181
+ data: {},
3182
+ content
3183
+ };
3184
+ const yamlContent = match[1];
3185
+ const markdownContent = match[2];
3186
+ // Simple YAML parsing (supports basic key: value format and one level of nesting)
3187
+ const data = {};
3188
+ const lines = yamlContent.split('\n');
3189
+ let currentKey = '';
3190
+ let currentValue = '';
3191
+ let inMultiline = false;
3192
+ let inNestedObject = false;
3193
+ let nestedObject = {};
3194
+ for (const line of lines){
3195
+ const trimmedLine = line.trim();
3196
+ if (!trimmedLine || trimmedLine.startsWith('#')) continue;
3197
+ // Check if it's a nested key: value pair (indented with 2 spaces)
3198
+ const nestedMatch = line.match(/^ ([a-zA-Z_-]+):\s*(.*)$/);
3199
+ if (nestedMatch && inNestedObject) {
3200
+ const [, nestedKey, nestedValue] = nestedMatch;
3201
+ nestedObject[nestedKey] = parseYamlValue(nestedValue.trim());
3202
+ continue;
3203
+ }
3204
+ // Check if it's a new key: value pair (no indent)
3205
+ const keyValueMatch = line.match(/^([a-zA-Z_-]+):\s*(.*)$/);
3206
+ if (keyValueMatch && !inMultiline) {
3207
+ // Save previous nested object if any
3208
+ if (inNestedObject && currentKey) {
3209
+ data[currentKey] = nestedObject;
3210
+ nestedObject = {};
3211
+ inNestedObject = false;
3212
+ }
3213
+ // Save previous value
3214
+ if (currentKey && !inNestedObject) data[currentKey] = parseYamlValue(currentValue.trim());
3215
+ currentKey = keyValueMatch[1];
3216
+ currentValue = keyValueMatch[2];
3217
+ // Check if it's start of multiline string
3218
+ if ('|' === currentValue || '>' === currentValue) {
3219
+ inMultiline = true;
3220
+ currentValue = '';
3221
+ } else if ('' === currentValue) {
3222
+ // Empty value - might be start of nested object
3223
+ inNestedObject = true;
3224
+ nestedObject = {};
3225
+ }
3226
+ } else if (inMultiline && line.startsWith(' ')) // Multiline string continuation
3227
+ currentValue += (currentValue ? '\n' : '') + line.slice(2);
3228
+ else if (inMultiline && !line.startsWith(' ')) {
3229
+ // Multiline string end
3230
+ inMultiline = false;
3231
+ data[currentKey] = currentValue.trim();
3232
+ // Try to parse new line
3233
+ const newKeyMatch = line.match(/^([a-zA-Z_-]+):\s*(.*)$/);
3234
+ if (newKeyMatch) {
3235
+ currentKey = newKeyMatch[1];
3236
+ currentValue = newKeyMatch[2];
3237
+ }
3238
+ }
3239
+ }
3240
+ // Save last value
3241
+ if (inNestedObject && currentKey) data[currentKey] = nestedObject;
3242
+ else if (currentKey) data[currentKey] = parseYamlValue(currentValue.trim());
3243
+ return {
3244
+ data,
3245
+ content: markdownContent
3246
+ };
3247
+ }
3248
+ /**
3249
+ * Parse YAML value
3250
+ */ function parseYamlValue(value) {
3251
+ if (!value) return '';
3252
+ // Boolean value
3253
+ if ('true' === value) return true;
3254
+ if ('false' === value) return false;
3255
+ // Number
3256
+ if (/^-?\d+$/.test(value)) return parseInt(value, 10);
3257
+ if (/^-?\d+\.\d+$/.test(value)) return parseFloat(value);
3258
+ // Remove quotes
3259
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
3260
+ return value;
3261
+ }
3262
+ /**
3263
+ * Validate skill name format
3264
+ *
3265
+ * Specification requirements:
3266
+ * - Max 64 characters
3267
+ * - Only lowercase letters, numbers, hyphens allowed
3268
+ * - Cannot start or end with hyphen
3269
+ * - Cannot contain consecutive hyphens
3270
+ */ function validateSkillName(name) {
3271
+ if (!name) throw new SkillValidationError('Skill name is required', 'name');
3272
+ if (name.length > 64) throw new SkillValidationError('Skill name must be at most 64 characters', 'name');
3273
+ if (!/^[a-z0-9]/.test(name)) throw new SkillValidationError('Skill name must start with a lowercase letter or number', 'name');
3274
+ if (!/[a-z0-9]$/.test(name)) throw new SkillValidationError('Skill name must end with a lowercase letter or number', 'name');
3275
+ if (/--/.test(name)) throw new SkillValidationError('Skill name cannot contain consecutive hyphens', 'name');
3276
+ if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(name) && name.length > 1) throw new SkillValidationError('Skill name can only contain lowercase letters, numbers, and hyphens', 'name');
3277
+ // Single character name
3278
+ if (1 === name.length && !/^[a-z0-9]$/.test(name)) throw new SkillValidationError('Single character skill name must be a lowercase letter or number', 'name');
3279
+ }
3280
+ /**
3281
+ * Validate skill description
3282
+ *
3283
+ * Specification requirements:
3284
+ * - Max 1024 characters
3285
+ * - Angle brackets are allowed per agentskills.io spec
3286
+ */ function validateSkillDescription(description) {
3287
+ if (!description) throw new SkillValidationError('Skill description is required', 'description');
3288
+ if (description.length > 1024) throw new SkillValidationError('Skill description must be at most 1024 characters', 'description');
3289
+ // Note: angle brackets are allowed per agentskills.io spec
3290
+ }
3291
+ /**
3292
+ * Parse SKILL.md content
3293
+ *
3294
+ * @param content - SKILL.md file content
3295
+ * @param options - Parse options
3296
+ * @returns Parsed skill info, or null if format is invalid
3297
+ * @throws SkillValidationError if validation fails in strict mode
3298
+ */ function parseSkillMd(content, options = {}) {
3299
+ const { strict = false } = options;
3300
+ try {
3301
+ const { data, content: body } = parseFrontmatter(content);
3302
+ // Check required fields
3303
+ if (!data.name || !data.description) {
3304
+ if (strict) throw new SkillValidationError('SKILL.md must have name and description in frontmatter');
3305
+ return null;
3306
+ }
3307
+ const name = String(data.name);
3308
+ const description = String(data.description);
3309
+ // Validate field format
3310
+ if (strict) {
3311
+ validateSkillName(name);
3312
+ validateSkillDescription(description);
3313
+ }
3314
+ // Parse allowed-tools
3315
+ let allowedTools;
3316
+ if (data['allowed-tools']) {
3317
+ const toolsStr = String(data['allowed-tools']);
3318
+ allowedTools = toolsStr.split(/\s+/).filter(Boolean);
3319
+ }
3320
+ return {
3321
+ name,
3322
+ description,
3323
+ version: data.version ? String(data.version) : void 0,
3324
+ license: data.license ? String(data.license) : void 0,
3325
+ compatibility: data.compatibility ? String(data.compatibility) : void 0,
3326
+ metadata: data.metadata,
3327
+ allowedTools,
3328
+ content: body,
3329
+ rawContent: content
3330
+ };
3331
+ } catch (error) {
3332
+ if (error instanceof SkillValidationError) throw error;
3333
+ if (strict) throw new SkillValidationError(`Failed to parse SKILL.md: ${error}`);
3334
+ return null;
3335
+ }
3336
+ }
3337
+ /**
3338
+ * Parse SKILL.md from file path
3339
+ */ function skill_parser_parseSkillMdFile(filePath, options = {}) {
3340
+ if (!external_node_fs_.existsSync(filePath)) {
3341
+ if (options.strict) throw new SkillValidationError(`SKILL.md not found: ${filePath}`);
3342
+ return null;
3343
+ }
3344
+ const content = external_node_fs_.readFileSync(filePath, 'utf-8');
3345
+ return parseSkillMd(content, options);
3346
+ }
3347
+ /**
3348
+ * Parse SKILL.md from skill directory
3349
+ */ function parseSkillFromDir(dirPath, options = {}) {
3350
+ const skillMdPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(dirPath, 'SKILL.md');
3351
+ return skill_parser_parseSkillMdFile(skillMdPath, options);
3352
+ }
3353
+ /**
3354
+ * Check if directory contains valid SKILL.md
3355
+ */ function hasValidSkillMd(dirPath) {
3356
+ const skillMdPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(dirPath, 'SKILL.md');
3357
+ if (!external_node_fs_.existsSync(skillMdPath)) return false;
3358
+ try {
3359
+ const skill = skill_parser_parseSkillMdFile(skillMdPath);
3360
+ return null !== skill;
3361
+ } catch {
3362
+ return false;
3363
+ }
3364
+ }
3365
+ /**
3366
+ * Generate SKILL.md content
3367
+ */ function generateSkillMd(skill) {
3368
+ const frontmatter = [
3369
+ '---'
3370
+ ];
3371
+ frontmatter.push(`name: ${skill.name}`);
3372
+ frontmatter.push(`description: ${skill.description}`);
3373
+ if (skill.license) frontmatter.push(`license: ${skill.license}`);
3374
+ if (skill.compatibility) frontmatter.push(`compatibility: ${skill.compatibility}`);
3375
+ if (skill.allowedTools && skill.allowedTools.length > 0) frontmatter.push(`allowed-tools: ${skill.allowedTools.join(' ')}`);
3376
+ frontmatter.push('---');
3377
+ frontmatter.push('');
3378
+ return frontmatter.join('\n') + skill.content;
3379
+ }
3150
3380
  /**
3151
3381
  * SkillManager - Core Skill management class
3152
3382
  *
@@ -3237,6 +3467,24 @@ class RegistryResolver {
3237
3467
  return RegistryResolver.isRegistryRef(ref);
3238
3468
  }
3239
3469
  /**
3470
+ * Get skill metadata from SKILL.md in a directory
3471
+ *
3472
+ * @param dirPath - Path to the skill directory
3473
+ * @returns Skill metadata (name, version, description) or null if not found
3474
+ */ getSkillMetadataFromDir(dirPath) {
3475
+ try {
3476
+ const skill = parseSkillFromDir(dirPath);
3477
+ if (skill?.name) return {
3478
+ name: skill.name,
3479
+ version: skill.version,
3480
+ description: skill.description
3481
+ };
3482
+ } catch (error) {
3483
+ logger_logger.debug(`Failed to parse SKILL.md in ${dirPath}: ${error.message}`);
3484
+ }
3485
+ return null;
3486
+ }
3487
+ /**
3240
3488
  * Install skill
3241
3489
  */ async install(ref, options = {}) {
3242
3490
  // Detect source type and delegate to appropriate installer
@@ -3251,9 +3499,21 @@ class RegistryResolver {
3251
3499
  const resolved = await this.resolver.resolve(ref);
3252
3500
  const { parsed, repoUrl } = resolved;
3253
3501
  const gitRef = resolved.ref; // Git reference (tag, branch, commit)
3254
- const skillName = parsed.subPath ? __WEBPACK_EXTERNAL_MODULE_node_path__.basename(parsed.subPath) : parsed.repo;
3502
+ // Fallback name from path/repo (will be overridden by SKILL.md name if available)
3503
+ const fallbackName = parsed.subPath ? __WEBPACK_EXTERNAL_MODULE_node_path__.basename(parsed.subPath) : parsed.repo;
3504
+ // Cache first - we need to read SKILL.md to get the real name
3505
+ let cacheResult = await this.cache.get(parsed, gitRef);
3506
+ if (!cacheResult) {
3507
+ logger_logger.debug(`Caching from ${repoUrl}@${gitRef}`);
3508
+ cacheResult = await this.cache.cache(repoUrl, parsed, gitRef, gitRef);
3509
+ }
3510
+ // Get the real skill name from SKILL.md in cache
3511
+ const cachePath = this.cache.getCachePath(parsed, gitRef);
3512
+ const metadata = this.getSkillMetadataFromDir(cachePath);
3513
+ const skillName = metadata?.name ?? fallbackName;
3514
+ const semanticVersion = metadata?.version ?? gitRef;
3255
3515
  const skillPath = this.getSkillPath(skillName);
3256
- // Check if already installed
3516
+ // Check if already installed (using the real name from SKILL.md)
3257
3517
  if (exists(skillPath) && !force) {
3258
3518
  const locked = this.lockManager.get(skillName);
3259
3519
  // Compare ref if available, fallback to version for backward compatibility
@@ -3270,26 +3530,10 @@ class RegistryResolver {
3270
3530
  }
3271
3531
  }
3272
3532
  logger_logger["package"](`Installing ${skillName}@${gitRef}...`);
3273
- // Check cache
3274
- let cacheResult = await this.cache.get(parsed, gitRef);
3275
- if (cacheResult) logger_logger.debug(`Using cached ${skillName}@${gitRef}`);
3276
- else {
3277
- logger_logger.debug(`Caching ${skillName}@${gitRef} from ${repoUrl}`);
3278
- cacheResult = await this.cache.cache(repoUrl, parsed, gitRef, gitRef);
3279
- }
3280
3533
  // Copy to installation directory
3281
3534
  ensureDir(this.getInstallDir());
3282
3535
  if (exists(skillPath)) remove(skillPath);
3283
3536
  await this.cache.copyTo(parsed, gitRef, skillPath);
3284
- // Read semantic version from skill.json
3285
- let semanticVersion = gitRef; // fallback to gitRef if no skill.json
3286
- const skillJsonPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(skillPath, 'skill.json');
3287
- if (exists(skillJsonPath)) try {
3288
- const skillJson = readJson(skillJsonPath);
3289
- if (skillJson.version) semanticVersion = skillJson.version;
3290
- } catch {
3291
- // Ignore parse errors, use gitRef as fallback
3292
- }
3293
3537
  // Update lock file (project mode only)
3294
3538
  if (!this.isGlobal) this.lockManager.lockSkill(skillName, {
3295
3539
  source: `${parsed.registry}:${parsed.owner}/${parsed.repo}${parsed.subPath ? `/${parsed.subPath}` : ''}`,
@@ -3320,9 +3564,21 @@ class RegistryResolver {
3320
3564
  const resolved = await this.httpResolver.resolve(ref);
3321
3565
  const { parsed, repoUrl, httpInfo } = resolved;
3322
3566
  const version = resolved.ref || 'latest';
3323
- const skillName = httpInfo.skillName;
3567
+ // Fallback name from URL (will be overridden by SKILL.md name if available)
3568
+ const fallbackName = httpInfo.skillName;
3569
+ // Cache first - we need to read SKILL.md to get the real name
3570
+ let cacheResult = await this.cache.get(parsed, version);
3571
+ if (!cacheResult) {
3572
+ logger_logger.debug(`Downloading from ${repoUrl}`);
3573
+ cacheResult = await this.cache.cacheFromHttp(repoUrl, parsed, version);
3574
+ }
3575
+ // Get the real skill name from SKILL.md in cache
3576
+ const cachePath = this.cache.getCachePath(parsed, version);
3577
+ const metadata = this.getSkillMetadataFromDir(cachePath);
3578
+ const skillName = metadata?.name ?? fallbackName;
3579
+ const semanticVersion = metadata?.version ?? version;
3324
3580
  const skillPath = this.getSkillPath(skillName);
3325
- // Check if already installed
3581
+ // Check if already installed (using the real name from SKILL.md)
3326
3582
  if (exists(skillPath) && !force) {
3327
3583
  const locked = this.lockManager.get(skillName);
3328
3584
  const lockedRef = locked?.ref || locked?.version;
@@ -3338,26 +3594,10 @@ class RegistryResolver {
3338
3594
  }
3339
3595
  }
3340
3596
  logger_logger["package"](`Installing ${skillName}@${version} from ${httpInfo.host}...`);
3341
- // Check cache
3342
- let cacheResult = await this.cache.get(parsed, version);
3343
- if (cacheResult) logger_logger.debug(`Using cached ${skillName}@${version}`);
3344
- else {
3345
- logger_logger.debug(`Downloading ${skillName}@${version} from ${repoUrl}`);
3346
- cacheResult = await this.cache.cacheFromHttp(repoUrl, parsed, version);
3347
- }
3348
3597
  // Copy to installation directory
3349
3598
  ensureDir(this.getInstallDir());
3350
3599
  if (exists(skillPath)) remove(skillPath);
3351
3600
  await this.cache.copyTo(parsed, version, skillPath);
3352
- // Read semantic version from skill.json
3353
- let semanticVersion = version;
3354
- const skillJsonPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(skillPath, 'skill.json');
3355
- if (exists(skillJsonPath)) try {
3356
- const skillJson = readJson(skillJsonPath);
3357
- if (skillJson.version) semanticVersion = skillJson.version;
3358
- } catch {
3359
- // Ignore parse errors
3360
- }
3361
3601
  // Update lock file (project mode only)
3362
3602
  if (!this.isGlobal) this.lockManager.lockSkill(skillName, {
3363
3603
  source: `http:${httpInfo.host}/${skillName}`,
@@ -3524,19 +3764,15 @@ class RegistryResolver {
3524
3764
  if (!exists(skillPath)) return null;
3525
3765
  const isLinked = isSymlink(skillPath);
3526
3766
  const locked = this.lockManager.get(name);
3527
- let metadata;
3528
- const skillJsonPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(skillPath, 'skill.json');
3529
- if (exists(skillJsonPath)) try {
3530
- metadata = readJson(skillJsonPath);
3531
- } catch {
3532
- // Ignore parse errors
3533
- }
3767
+ // Read metadata from SKILL.md (sole source per agentskills.io spec)
3768
+ const skillMd = this.getSkillMetadataFromDir(skillPath);
3769
+ // Version priority: locked > SKILL.md > 'unknown'
3770
+ const version = isLinked ? 'local' : locked?.version || skillMd?.version || 'unknown';
3534
3771
  return {
3535
3772
  name,
3536
3773
  path: skillPath,
3537
- version: isLinked ? 'local' : locked?.version || metadata?.version || 'unknown',
3774
+ version,
3538
3775
  source: isLinked ? getRealPath(skillPath) : locked?.source || '',
3539
- metadata,
3540
3776
  isLinked
3541
3777
  };
3542
3778
  }
@@ -3635,26 +3871,21 @@ class RegistryResolver {
3635
3871
  const resolved = await this.resolver.resolve(ref);
3636
3872
  const { parsed, repoUrl } = resolved;
3637
3873
  const gitRef = resolved.ref; // Git reference (tag, branch, commit)
3638
- const skillName = parsed.subPath ? __WEBPACK_EXTERNAL_MODULE_node_path__.basename(parsed.subPath) : parsed.repo;
3639
- logger_logger["package"](`Installing ${skillName}@${gitRef} to ${targetAgents.length} agent(s)...`);
3640
- // Check cache
3874
+ // Fallback name from path/repo (will be overridden by SKILL.md name if available)
3875
+ const fallbackName = parsed.subPath ? __WEBPACK_EXTERNAL_MODULE_node_path__.basename(parsed.subPath) : parsed.repo;
3876
+ // Cache first - we need to read SKILL.md to get the real name
3641
3877
  let cacheResult = await this.cache.get(parsed, gitRef);
3642
- if (cacheResult) logger_logger.debug(`Using cached ${skillName}@${gitRef}`);
3643
- else {
3644
- logger_logger.debug(`Caching ${skillName}@${gitRef} from ${repoUrl}`);
3878
+ if (!cacheResult) {
3879
+ logger_logger.debug(`Caching from ${repoUrl}@${gitRef}`);
3645
3880
  cacheResult = await this.cache.cache(repoUrl, parsed, gitRef, gitRef);
3646
3881
  }
3647
3882
  // Get cache path as source
3648
3883
  const sourcePath = this.cache.getCachePath(parsed, gitRef);
3649
- // Read semantic version from skill.json
3650
- let semanticVersion = gitRef; // fallback to gitRef if no skill.json
3651
- const skillJsonPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(sourcePath, 'skill.json');
3652
- if (exists(skillJsonPath)) try {
3653
- const skillJson = readJson(skillJsonPath);
3654
- if (skillJson.version) semanticVersion = skillJson.version;
3655
- } catch {
3656
- // Ignore parse errors, use gitRef as fallback
3657
- }
3884
+ // Get the real skill name from SKILL.md in cache
3885
+ const metadata = this.getSkillMetadataFromDir(sourcePath);
3886
+ const skillName = metadata?.name ?? fallbackName;
3887
+ const semanticVersion = metadata?.version ?? gitRef;
3888
+ logger_logger["package"](`Installing ${skillName}@${gitRef} to ${targetAgents.length} agent(s)...`);
3658
3889
  // Create Installer with custom installDir from config
3659
3890
  const defaults = this.config.getDefaults();
3660
3891
  const installer = new Installer({
@@ -3707,26 +3938,21 @@ class RegistryResolver {
3707
3938
  const resolved = await this.httpResolver.resolve(ref);
3708
3939
  const { parsed, repoUrl, httpInfo } = resolved;
3709
3940
  const version = resolved.ref || 'latest';
3710
- const skillName = httpInfo.skillName;
3711
- logger_logger["package"](`Installing ${skillName}@${version} from ${httpInfo.host} to ${targetAgents.length} agent(s)...`);
3712
- // Check cache
3941
+ // Fallback name from URL (will be overridden by SKILL.md name if available)
3942
+ const fallbackName = httpInfo.skillName;
3943
+ // Cache first - we need to read SKILL.md to get the real name
3713
3944
  let cacheResult = await this.cache.get(parsed, version);
3714
- if (cacheResult) logger_logger.debug(`Using cached ${skillName}@${version}`);
3715
- else {
3716
- logger_logger.debug(`Downloading ${skillName}@${version} from ${repoUrl}`);
3945
+ if (!cacheResult) {
3946
+ logger_logger.debug(`Downloading from ${repoUrl}`);
3717
3947
  cacheResult = await this.cache.cacheFromHttp(repoUrl, parsed, version);
3718
3948
  }
3719
3949
  // Get cache path as source
3720
3950
  const sourcePath = this.cache.getCachePath(parsed, version);
3721
- // Read semantic version from skill.json
3722
- let semanticVersion = version;
3723
- const skillJsonPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(sourcePath, 'skill.json');
3724
- if (exists(skillJsonPath)) try {
3725
- const skillJson = readJson(skillJsonPath);
3726
- if (skillJson.version) semanticVersion = skillJson.version;
3727
- } catch {
3728
- // Ignore parse errors
3729
- }
3951
+ // Get the real skill name from SKILL.md in cache
3952
+ const metadata = this.getSkillMetadataFromDir(sourcePath);
3953
+ const skillName = metadata?.name ?? fallbackName;
3954
+ const semanticVersion = metadata?.version ?? version;
3955
+ logger_logger["package"](`Installing ${skillName}@${version} from ${httpInfo.host} to ${targetAgents.length} agent(s)...`);
3730
3956
  // Create Installer with custom installDir from config
3731
3957
  const defaults = this.config.getDefaults();
3732
3958
  const installer = new Installer({
@@ -3991,234 +4217,4 @@ class RegistryResolver {
3991
4217
  return results;
3992
4218
  }
3993
4219
  }
3994
- /**
3995
- * Skill Parser - SKILL.md parser
3996
- *
3997
- * Following agentskills.io specification: https://agentskills.io/specification
3998
- *
3999
- * SKILL.md format requirements:
4000
- * - YAML frontmatter containing name and description (required)
4001
- * - name: max 64 characters, lowercase letters, numbers, hyphens
4002
- * - description: max 1024 characters
4003
- * - Optional fields: license, compatibility, metadata, allowed-tools
4004
- */ /**
4005
- * Skill validation error
4006
- */ class SkillValidationError extends Error {
4007
- field;
4008
- constructor(message, field){
4009
- super(message), this.field = field;
4010
- this.name = 'SkillValidationError';
4011
- }
4012
- }
4013
- /**
4014
- * Simple YAML frontmatter parser
4015
- * Parses --- delimited YAML header
4016
- *
4017
- * Supports:
4018
- * - Basic key: value pairs
4019
- * - Multiline strings (| and >)
4020
- * - Nested objects (one level deep, for metadata field)
4021
- */ function parseFrontmatter(content) {
4022
- const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
4023
- const match = content.match(frontmatterRegex);
4024
- if (!match) return {
4025
- data: {},
4026
- content
4027
- };
4028
- const yamlContent = match[1];
4029
- const markdownContent = match[2];
4030
- // Simple YAML parsing (supports basic key: value format and one level of nesting)
4031
- const data = {};
4032
- const lines = yamlContent.split('\n');
4033
- let currentKey = '';
4034
- let currentValue = '';
4035
- let inMultiline = false;
4036
- let inNestedObject = false;
4037
- let nestedObject = {};
4038
- for (const line of lines){
4039
- const trimmedLine = line.trim();
4040
- if (!trimmedLine || trimmedLine.startsWith('#')) continue;
4041
- // Check if it's a nested key: value pair (indented with 2 spaces)
4042
- const nestedMatch = line.match(/^ ([a-zA-Z_-]+):\s*(.*)$/);
4043
- if (nestedMatch && inNestedObject) {
4044
- const [, nestedKey, nestedValue] = nestedMatch;
4045
- nestedObject[nestedKey] = parseYamlValue(nestedValue.trim());
4046
- continue;
4047
- }
4048
- // Check if it's a new key: value pair (no indent)
4049
- const keyValueMatch = line.match(/^([a-zA-Z_-]+):\s*(.*)$/);
4050
- if (keyValueMatch && !inMultiline) {
4051
- // Save previous nested object if any
4052
- if (inNestedObject && currentKey) {
4053
- data[currentKey] = nestedObject;
4054
- nestedObject = {};
4055
- inNestedObject = false;
4056
- }
4057
- // Save previous value
4058
- if (currentKey && !inNestedObject) data[currentKey] = parseYamlValue(currentValue.trim());
4059
- currentKey = keyValueMatch[1];
4060
- currentValue = keyValueMatch[2];
4061
- // Check if it's start of multiline string
4062
- if ('|' === currentValue || '>' === currentValue) {
4063
- inMultiline = true;
4064
- currentValue = '';
4065
- } else if ('' === currentValue) {
4066
- // Empty value - might be start of nested object
4067
- inNestedObject = true;
4068
- nestedObject = {};
4069
- }
4070
- } else if (inMultiline && line.startsWith(' ')) // Multiline string continuation
4071
- currentValue += (currentValue ? '\n' : '') + line.slice(2);
4072
- else if (inMultiline && !line.startsWith(' ')) {
4073
- // Multiline string end
4074
- inMultiline = false;
4075
- data[currentKey] = currentValue.trim();
4076
- // Try to parse new line
4077
- const newKeyMatch = line.match(/^([a-zA-Z_-]+):\s*(.*)$/);
4078
- if (newKeyMatch) {
4079
- currentKey = newKeyMatch[1];
4080
- currentValue = newKeyMatch[2];
4081
- }
4082
- }
4083
- }
4084
- // Save last value
4085
- if (inNestedObject && currentKey) data[currentKey] = nestedObject;
4086
- else if (currentKey) data[currentKey] = parseYamlValue(currentValue.trim());
4087
- return {
4088
- data,
4089
- content: markdownContent
4090
- };
4091
- }
4092
- /**
4093
- * Parse YAML value
4094
- */ function parseYamlValue(value) {
4095
- if (!value) return '';
4096
- // Boolean value
4097
- if ('true' === value) return true;
4098
- if ('false' === value) return false;
4099
- // Number
4100
- if (/^-?\d+$/.test(value)) return parseInt(value, 10);
4101
- if (/^-?\d+\.\d+$/.test(value)) return parseFloat(value);
4102
- // Remove quotes
4103
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
4104
- return value;
4105
- }
4106
- /**
4107
- * Validate skill name format
4108
- *
4109
- * Specification requirements:
4110
- * - Max 64 characters
4111
- * - Only lowercase letters, numbers, hyphens allowed
4112
- * - Cannot start or end with hyphen
4113
- * - Cannot contain consecutive hyphens
4114
- */ function validateSkillName(name) {
4115
- if (!name) throw new SkillValidationError('Skill name is required', 'name');
4116
- if (name.length > 64) throw new SkillValidationError('Skill name must be at most 64 characters', 'name');
4117
- if (!/^[a-z0-9]/.test(name)) throw new SkillValidationError('Skill name must start with a lowercase letter or number', 'name');
4118
- if (!/[a-z0-9]$/.test(name)) throw new SkillValidationError('Skill name must end with a lowercase letter or number', 'name');
4119
- if (/--/.test(name)) throw new SkillValidationError('Skill name cannot contain consecutive hyphens', 'name');
4120
- if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(name) && name.length > 1) throw new SkillValidationError('Skill name can only contain lowercase letters, numbers, and hyphens', 'name');
4121
- // Single character name
4122
- if (1 === name.length && !/^[a-z0-9]$/.test(name)) throw new SkillValidationError('Single character skill name must be a lowercase letter or number', 'name');
4123
- }
4124
- /**
4125
- * Validate skill description
4126
- *
4127
- * Specification requirements:
4128
- * - Max 1024 characters
4129
- * - Angle brackets are allowed per agentskills.io spec
4130
- */ function validateSkillDescription(description) {
4131
- if (!description) throw new SkillValidationError('Skill description is required', 'description');
4132
- if (description.length > 1024) throw new SkillValidationError('Skill description must be at most 1024 characters', 'description');
4133
- // Note: angle brackets are allowed per agentskills.io spec
4134
- }
4135
- /**
4136
- * Parse SKILL.md content
4137
- *
4138
- * @param content - SKILL.md file content
4139
- * @param options - Parse options
4140
- * @returns Parsed skill info, or null if format is invalid
4141
- * @throws SkillValidationError if validation fails in strict mode
4142
- */ function parseSkillMd(content, options = {}) {
4143
- const { strict = false } = options;
4144
- try {
4145
- const { data, content: body } = parseFrontmatter(content);
4146
- // Check required fields
4147
- if (!data.name || !data.description) {
4148
- if (strict) throw new SkillValidationError('SKILL.md must have name and description in frontmatter');
4149
- return null;
4150
- }
4151
- const name = String(data.name);
4152
- const description = String(data.description);
4153
- // Validate field format
4154
- if (strict) {
4155
- validateSkillName(name);
4156
- validateSkillDescription(description);
4157
- }
4158
- // Parse allowed-tools
4159
- let allowedTools;
4160
- if (data['allowed-tools']) {
4161
- const toolsStr = String(data['allowed-tools']);
4162
- allowedTools = toolsStr.split(/\s+/).filter(Boolean);
4163
- }
4164
- return {
4165
- name,
4166
- description,
4167
- version: data.version ? String(data.version) : void 0,
4168
- license: data.license ? String(data.license) : void 0,
4169
- compatibility: data.compatibility ? String(data.compatibility) : void 0,
4170
- metadata: data.metadata,
4171
- allowedTools,
4172
- content: body,
4173
- rawContent: content
4174
- };
4175
- } catch (error) {
4176
- if (error instanceof SkillValidationError) throw error;
4177
- if (strict) throw new SkillValidationError(`Failed to parse SKILL.md: ${error}`);
4178
- return null;
4179
- }
4180
- }
4181
- /**
4182
- * Parse SKILL.md from file path
4183
- */ function skill_parser_parseSkillMdFile(filePath, options = {}) {
4184
- if (!external_node_fs_.existsSync(filePath)) {
4185
- if (options.strict) throw new SkillValidationError(`SKILL.md not found: ${filePath}`);
4186
- return null;
4187
- }
4188
- const content = external_node_fs_.readFileSync(filePath, 'utf-8');
4189
- return parseSkillMd(content, options);
4190
- }
4191
- /**
4192
- * Parse SKILL.md from skill directory
4193
- */ function parseSkillFromDir(dirPath, options = {}) {
4194
- const skillMdPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(dirPath, 'SKILL.md');
4195
- return skill_parser_parseSkillMdFile(skillMdPath, options);
4196
- }
4197
- /**
4198
- * Check if directory contains valid SKILL.md
4199
- */ function hasValidSkillMd(dirPath) {
4200
- const skillMdPath = __WEBPACK_EXTERNAL_MODULE_node_path__.join(dirPath, 'SKILL.md');
4201
- if (!external_node_fs_.existsSync(skillMdPath)) return false;
4202
- try {
4203
- const skill = skill_parser_parseSkillMdFile(skillMdPath);
4204
- return null !== skill;
4205
- } catch {
4206
- return false;
4207
- }
4208
- }
4209
- /**
4210
- * Generate SKILL.md content
4211
- */ function generateSkillMd(skill) {
4212
- const frontmatter = [
4213
- '---'
4214
- ];
4215
- frontmatter.push(`name: ${skill.name}`);
4216
- frontmatter.push(`description: ${skill.description}`);
4217
- if (skill.license) frontmatter.push(`license: ${skill.license}`);
4218
- if (skill.compatibility) frontmatter.push(`compatibility: ${skill.compatibility}`);
4219
- if (skill.allowedTools && skill.allowedTools.length > 0) frontmatter.push(`allowed-tools: ${skill.allowedTools.join(' ')}`);
4220
- frontmatter.push('---');
4221
- frontmatter.push('');
4222
- return frontmatter.join('\n') + skill.content;
4223
- }
4224
4220
  export { CacheManager, config_loader_ConfigLoader as ConfigLoader, DEFAULT_REGISTRIES, GitResolver, HttpResolver, Installer, LockManager, SkillManager, SkillValidationError, agents, detectInstalledAgents, generateSkillMd, getAgentConfig, getAgentSkillsDir, getAllAgentTypes, getCanonicalSkillPath, getCanonicalSkillsDir, hasValidSkillMd, isPathSafe, isValidAgentType, logger_logger as logger, parseSkillFromDir, parseSkillMd, skill_parser_parseSkillMdFile as parseSkillMdFile, sanitizeName, shortenPath, validateSkillDescription, validateSkillName };