shared.js

 1import path from 'path';
 2import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js';
 3
 4/**
 5 * Shared transformer logic for all providers.
 6 *
 7 * @param {Object} config - Provider-specific configuration
 8 * @param {string} config.provider - Provider key for placeholders (e.g., 'claude-code')
 9 * @param {string} config.displayName - Display name for logging (e.g., 'Claude Code')
10 * @param {string} config.configDir - Dot-directory name (e.g., '.claude')
11 * @param {Function} config.buildFrontmatter - (skill, skillName) => frontmatter object
12 * @param {Function} [config.transformBody] - Optional (body, skill) => transformed body
13 * @param {Array} skills - All skills
14 * @param {string} distDir - Distribution output directory
15 * @param {Object} options - Optional settings (prefix, outputSuffix)
16 */
17export function transformProvider(config, skills, distDir, options = {}) {
18  const { provider, displayName, configDir, buildFrontmatter, transformBody } = config;
19  const { prefix = '', outputSuffix = '' } = options;
20  const providerDir = path.join(distDir, `${provider}${outputSuffix}`);
21  const skillsDir = path.join(providerDir, `${configDir}/skills`);
22
23  cleanDir(providerDir);
24  ensureDir(skillsDir);
25
26  const allSkillNames = skills.map(s => s.name);
27  const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
28  let refCount = 0;
29  let scriptCount = 0;
30
31  for (const skill of skills) {
32    const skillName = `${prefix}${skill.name}`;
33    const skillDir = path.join(skillsDir, skillName);
34
35    const frontmatterObj = buildFrontmatter(skill, skillName);
36    const frontmatter = generateYamlFrontmatter(frontmatterObj);
37
38    let skillBody = replacePlaceholders(skill.body, provider, commandNames);
39
40    // Replace {{scripts_path}} with provider-aware path to skill's scripts directory
41    const scriptsPath = provider === 'claude-code'
42      ? '${CLAUDE_PLUGIN_ROOT}/scripts'
43      : `${configDir}/skills/${skillName}/scripts`;
44    skillBody = skillBody.replace(/\{\{scripts_path\}\}/g, scriptsPath);
45
46    if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);
47    if (transformBody) skillBody = transformBody(skillBody, skill);
48
49    const content = `${frontmatter}\n\n${skillBody}`;
50    writeFile(path.join(skillDir, 'SKILL.md'), content);
51
52    // Copy reference files if they exist
53    if (skill.references && skill.references.length > 0) {
54      const refDir = path.join(skillDir, 'reference');
55      ensureDir(refDir);
56      for (const ref of skill.references) {
57        writeFile(
58          path.join(refDir, `${ref.name}.md`),
59          replacePlaceholders(ref.content, provider)
60        );
61        refCount++;
62      }
63    }
64
65    // Copy script files if they exist
66    if (skill.scripts && skill.scripts.length > 0) {
67      const scriptsOutDir = path.join(skillDir, 'scripts');
68      ensureDir(scriptsOutDir);
69      for (const script of skill.scripts) {
70        writeFile(path.join(scriptsOutDir, script.name), script.content);
71        scriptCount++;
72      }
73    }
74  }
75
76  const userInvokableCount = skills.filter(s => s.userInvokable).length;
77  const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
78  const scriptInfo = scriptCount > 0 ? ` (${scriptCount} script files)` : '';
79  const prefixInfo = prefix ? ` [${prefix}prefixed]` : '';
80  console.log(`${displayName}${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invokable)${refInfo}${scriptInfo}`);
81}