Files
gh-codethread-claude-code-p…/skills/claude-code-knowledge/scripts/skill-creator/package_skill.ts
2025-11-29 18:14:51 +08:00

149 lines
3.8 KiB
TypeScript
Executable File

#!/usr/bin/env bun
/**
* Skill Packager - Creates a distributable zip file of a skill folder
*
* Usage:
* bun package_skill.ts <path/to/skill-folder> [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<string | null> {
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 <path/to/skill-folder> [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();