File size: 3,295 Bytes
a5d1c1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67978ff
a5d1c1b
 
 
67978ff
a5d1c1b
 
 
 
 
 
 
 
 
 
61b1da1
a5d1c1b
 
 
 
 
67978ff
 
 
 
 
a5d1c1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67978ff
a5d1c1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { cacheService } from './cache-service.js';

export interface MaterializeResult {
  skillId: string;
  targetAgent: string;
  destinationPath: string;
  filesCopied: number;
}

export class MaterializeService {
  materialize(
    workspaceRoot: string,
    skillId: string,
    options?: {
      target?: 'agents' | 'gemini' | 'cursor' | 'claude' | 'auto';
      global?: boolean;
    }
  ): MaterializeResult {
    const targetAgent = options?.target && options.target !== 'auto' ? options.target : 'agents';
    const isGlobal = !!options?.global;

    // Locate skill in cache
    const skillDirRel = `skills/${skillId}`;
    const allCached = cacheService.listCachedFiles(workspaceRoot);
    const skillFiles = allCached.filter(
      (f) => f === `${skillDirRel}/SKILL.md` || f === `${skillDirRel}.md` || f.startsWith(`${skillDirRel}/`)
    );

    if (skillFiles.length === 0) {
      throw new Error(`Skill '${skillId}' not found in local cache. Run 'evb sync' to update.`);
    }

    let destDir = '';
    let primaryFile = '';

    if (targetAgent === 'agents') {
      const base = isGlobal ? path.join(os.homedir(), '.agents', 'skills') : path.join(workspaceRoot, '.agents', 'skills');
      destDir = path.join(base, skillId);
      primaryFile = path.join(destDir, 'SKILL.md');
    } else if (targetAgent === 'gemini') {
      const base = isGlobal ? path.join(os.homedir(), '.gemini', 'skills') : path.join(workspaceRoot, '.gemini', 'skills');
      destDir = path.join(base, skillId);
      primaryFile = path.join(destDir, 'SKILL.md');
    } else if (targetAgent === 'cursor') {
      const base = isGlobal ? path.join(os.homedir(), '.cursor', 'rules') : path.join(workspaceRoot, '.cursor', 'rules');
      destDir = base;
      primaryFile = path.join(destDir, `${skillId}.mdc`);
    } else if (targetAgent === 'claude') {
      const base = isGlobal ? path.join(os.homedir(), '.claude', 'skills') : path.join(workspaceRoot, '.claude', 'skills');
      destDir = path.join(base, skillId);
      primaryFile = path.join(destDir, 'SKILL.md');
    }

    fs.mkdirSync(destDir, { recursive: true });

    let filesCopied = 0;

    for (const file of skillFiles) {
      const content = cacheService.readCachedFile(workspaceRoot, file);
      if (content === null) continue;

      if (targetAgent === 'cursor') {
        // For Cursor, write primary mdc rule
        if (file.endsWith('SKILL.md') || file === `${skillDirRel}.md`) {
          fs.writeFileSync(primaryFile, content, 'utf8');
          filesCopied++;
        }
      } else {
        // For universal .agents, Gemini & Claude, copy directory structure
        const subPath = file.startsWith(`${skillDirRel}/`)
          ? file.slice(`${skillDirRel}/`.length)
          : 'SKILL.md';

        const targetFilePath = path.join(destDir, subPath);
        fs.mkdirSync(path.dirname(targetFilePath), { recursive: true });
        fs.writeFileSync(targetFilePath, content, 'utf8');
        filesCopied++;
      }
    }

    return {
      skillId,
      targetAgent,
      destinationPath: targetAgent === 'cursor' ? primaryFile : destDir,
      filesCopied,
    };
  }
}

export const materializeService = new MaterializeService();