This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

vit / src / lib / skill-install.js
2.9 kB 103 lines
1// SPDX-License-Identifier: MIT 2// Copyright (c) 2026 sol pbc 3 4import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs'; 5import { homedir } from 'node:os'; 6import { dirname, join } from 'node:path'; 7import { fileURLToPath } from 'node:url'; 8 9const DEFAULT_SKILL_NAME = 'using-vit'; 10const SOURCE_MISSING_ERROR = 'source skill files missing or empty'; 11 12function defaultSourceDir() { 13 return join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'skills', 'vit'); 14} 15 16function errorMessage(err) { 17 if (err instanceof Error) return err.message || String(err); 18 return String(err); 19} 20 21function readSkillName(sourceDir) { 22 try { 23 const content = readFileSync(join(sourceDir, 'SKILL.md'), 'utf-8'); 24 const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); 25 if (!match) return DEFAULT_SKILL_NAME; 26 const nameMatch = match[1].match(/^name:\s*(.+)$/m); 27 return nameMatch ? nameMatch[1].trim() : DEFAULT_SKILL_NAME; 28 } catch { 29 return DEFAULT_SKILL_NAME; 30 } 31} 32 33function sourceExists(sourceDir) { 34 try { 35 return existsSync(sourceDir) && readdirSync(sourceDir).length > 0; 36 } catch { 37 return false; 38 } 39} 40 41export function skillInstallReason(skillResult) { 42 if (skillResult.results.some(result => result.status === 'source-missing')) { 43 return 'source skill files missing'; 44 } 45 46 const failed = skillResult.results.filter(result => result.status === 'failed'); 47 if (failed.length > 0) { 48 return failed 49 .map(result => `${result.label} failed: ${result.error || 'unknown error'}`) 50 .join('; '); 51 } 52 53 return 'skill install failed'; 54} 55 56// sourceDir exists only as a test seam; production callers pass nothing. 57export function ensureSkill({ sourceDir = defaultSourceDir() } = {}) { 58 const name = readSkillName(sourceDir); 59 const home = homedir(); 60 const targets = [ 61 { label: 'claude', path: join(home, '.claude', 'skills', name) }, 62 { label: 'agents', path: join(home, '.agents', 'skills', name) }, 63 ]; 64 65 if (!sourceExists(sourceDir)) { 66 return { 67 name, 68 source: null, 69 ok: false, 70 results: targets.map(target => ({ 71 ...target, 72 status: 'source-missing', 73 error: SOURCE_MISSING_ERROR, 74 })), 75 }; 76 } 77 78 const results = targets.map(target => { 79 try { 80 const existed = existsSync(join(target.path, 'SKILL.md')); 81 mkdirSync(target.path, { recursive: true }); 82 cpSync(sourceDir, target.path, { recursive: true, force: true }); 83 return { 84 ...target, 85 status: existed ? 'already-present' : 'created', 86 error: null, 87 }; 88 } catch (err) { 89 return { 90 ...target, 91 status: 'failed', 92 error: errorMessage(err), 93 }; 94 } 95 }); 96 97 return { 98 name, 99 source: sourceDir, 100 ok: results.every(result => result.status === 'created' || result.status === 'already-present'), 101 results, 102 }; 103}