#!/usr/bin/env bun /** * Skill Packager - Creates a distributable zip file of a skill folder * * Usage: * bun package_skill.ts [output-directory] * * Example: * bun package_skill.ts skills/public/my-skill * bun package_skill.ts skills/public/my-skill ./dist */ import { existsSync, readdirSync, statSync } from 'node:fs'; import { mkdir } from 'node:fs/promises'; import { basename, join, relative, resolve } from 'node:path'; import { validateSkill } from './quick_validate'; async function packageSkill(skillPath: string, outputDir?: string): Promise { const skillPathResolved = resolve(skillPath); // Validate skill folder exists if (!existsSync(skillPathResolved)) { console.log(`āŒ Error: Skill folder not found: ${skillPathResolved}`); return null; } if (!statSync(skillPathResolved).isDirectory()) { console.log(`āŒ Error: Path is not a directory: ${skillPathResolved}`); return null; } // Validate SKILL.md exists const skillMd = join(skillPathResolved, 'SKILL.md'); if (!existsSync(skillMd)) { console.log(`āŒ Error: SKILL.md not found in ${skillPathResolved}`); return null; } // Run validation before packaging console.log('šŸ” Validating skill...'); const { valid, message } = await validateSkill(skillPathResolved); if (!valid) { console.log(`āŒ Validation failed: ${message}`); console.log(' Please fix the validation errors before packaging.'); return null; } console.log(`āœ… ${message}\n`); // Determine output location const skillName = basename(skillPathResolved); let outputPath: string; if (outputDir) { outputPath = resolve(outputDir); if (!existsSync(outputPath)) { await mkdir(outputPath, { recursive: true }); } } else { outputPath = process.cwd(); } const zipFilename = join(outputPath, `${skillName}.zip`); // Create the zip file using system zip command try { // Remove existing zip file if it exists if (existsSync(zipFilename)) { const { unlinkSync } = require('node:fs'); unlinkSync(zipFilename); } // Collect all files for display const files: { path: string; arcname: string }[] = []; function walkDirectory(dir: string, baseDir: string) { const entries = readdirSync(dir); for (const entry of entries) { const fullPath = join(dir, entry); const stat = statSync(fullPath); if (stat.isDirectory()) { walkDirectory(fullPath, baseDir); } else if (stat.isFile()) { const arcname = relative(baseDir, fullPath); files.push({ path: fullPath, arcname }); } } } const parentDir = resolve(skillPathResolved, '..'); walkDirectory(skillPathResolved, parentDir); // Create zip using system command const { $ } = await import('bun'); const cwd = parentDir; const skillDirName = basename(skillPathResolved); await $`cd ${cwd} && zip -r ${zipFilename} ${skillDirName}`.quiet(); // List files that were added for (const { arcname } of files) { console.log(` Added: ${arcname}`); } console.log(`\nāœ… Successfully packaged skill to: ${zipFilename}`); return zipFilename; } catch (e) { console.log(`āŒ Error creating zip file: ${e}`); return null; } } async function main() { const args = process.argv.slice(2); if (args.length < 1) { console.log('Usage: bun package_skill.ts [output-directory]'); console.log('\nExample:'); console.log(' bun package_skill.ts skills/public/my-skill'); console.log(' bun package_skill.ts skills/public/my-skill ./dist'); process.exit(1); } const skillPath = args[0]; const outputDir = args[1]; console.log(`šŸ“¦ Packaging skill: ${skillPath}`); if (outputDir) { console.log(` Output directory: ${outputDir}`); } console.log(); const result = await packageSkill(skillPath, outputDir); if (result) { process.exit(0); } else { process.exit(1); } } main();