skill-distill 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2020 @@
1
+ // src/utils/logger.ts
2
+ import chalk from "chalk";
3
+ var logger = {
4
+ info: (msg) => console.log(chalk.blue("\u2139"), msg),
5
+ success: (msg) => console.log(chalk.green("\u2713"), msg),
6
+ warn: (msg) => console.log(chalk.yellow("\u26A0"), msg),
7
+ error: (msg) => console.log(chalk.red("\u2717"), msg),
8
+ step: (msg) => console.log(chalk.cyan("\u2192"), msg)
9
+ };
10
+
11
+ // src/utils/fs.ts
12
+ import { promises as fs } from "fs";
13
+ import { homedir } from "os";
14
+ import { join } from "path";
15
+ function expandHome(path) {
16
+ if (path.startsWith("~")) {
17
+ return join(homedir(), path.slice(1));
18
+ }
19
+ return path;
20
+ }
21
+ async function ensureDir(path) {
22
+ await fs.mkdir(path, { recursive: true });
23
+ }
24
+ async function fileExists(path) {
25
+ try {
26
+ await fs.access(path);
27
+ return true;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+ async function readJson(path) {
33
+ const content = await fs.readFile(path, "utf-8");
34
+ return JSON.parse(content);
35
+ }
36
+ async function writeJson(path, data) {
37
+ await fs.writeFile(path, JSON.stringify(data, null, 2));
38
+ }
39
+
40
+ // src/utils/validation.ts
41
+ import { z } from "zod";
42
+ var SessionIdSchema = z.string().min(1, "Session ID is required");
43
+ var PlatformSchema = z.enum(["claude", "codex", "cursor"]);
44
+ var DistillOptionsSchema = z.object({
45
+ sessionId: z.string().optional(),
46
+ last: z.boolean().optional(),
47
+ prompts: z.array(z.string()).optional(),
48
+ format: z.enum(["claude", "codex", "cursor", "all"]).optional(),
49
+ outputDir: z.string().optional(),
50
+ install: z.boolean().optional(),
51
+ interactive: z.boolean().optional()
52
+ });
53
+ function validateSessionId(id) {
54
+ return SessionIdSchema.parse(id);
55
+ }
56
+ function validatePlatform(platform) {
57
+ return PlatformSchema.parse(platform);
58
+ }
59
+
60
+ // src/loaders/claude.ts
61
+ import { promises as fs2 } from "fs";
62
+ import { join as join2 } from "path";
63
+ import { homedir as homedir2 } from "os";
64
+
65
+ // src/loaders/base.ts
66
+ var SessionLoader = class {
67
+ basePath;
68
+ projectPath;
69
+ constructor(options = {}) {
70
+ this.basePath = options.basePath ?? this.getDefaultBasePath();
71
+ this.projectPath = options.projectPath;
72
+ }
73
+ async getLatestSession() {
74
+ const sessions = await this.listSessions();
75
+ if (sessions.length === 0) {
76
+ throw new Error("No sessions found");
77
+ }
78
+ const sorted = sessions.sort(
79
+ (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
80
+ );
81
+ return this.getSession(sorted[0].id);
82
+ }
83
+ async searchSessions(query) {
84
+ const sessions = await this.listSessions();
85
+ const lowerQuery = query.toLowerCase();
86
+ return sessions.filter(
87
+ (s) => s.summary?.toLowerCase().includes(lowerQuery) || s.projectPath.toLowerCase().includes(lowerQuery)
88
+ );
89
+ }
90
+ };
91
+
92
+ // src/loaders/claude.ts
93
+ var ClaudeSessionLoader = class extends SessionLoader {
94
+ constructor(options = {}) {
95
+ super(options);
96
+ }
97
+ getDefaultBasePath() {
98
+ return join2(homedir2(), ".claude", "projects");
99
+ }
100
+ async listSessions() {
101
+ const sessions = [];
102
+ try {
103
+ const projectDirs = await fs2.readdir(this.basePath);
104
+ for (const projectDir of projectDirs) {
105
+ const projectPath = join2(this.basePath, projectDir);
106
+ const stat = await fs2.stat(projectPath);
107
+ if (!stat.isDirectory()) continue;
108
+ const sessionsPath = join2(projectPath, "sessions");
109
+ try {
110
+ const sessionFiles = await fs2.readdir(sessionsPath);
111
+ for (const file of sessionFiles) {
112
+ if (!file.endsWith(".json")) continue;
113
+ const sessionPath = join2(sessionsPath, file);
114
+ const sessionData = await this.readSessionFile(sessionPath);
115
+ if (sessionData) {
116
+ sessions.push({
117
+ id: sessionData.id,
118
+ projectPath: projectDir,
119
+ startTime: sessionData.createdAt,
120
+ endTime: sessionData.updatedAt,
121
+ messageCount: sessionData.messages.length,
122
+ summary: this.extractSummary(sessionData.messages)
123
+ });
124
+ }
125
+ }
126
+ } catch {
127
+ continue;
128
+ }
129
+ }
130
+ } catch (error) {
131
+ throw new Error(`Failed to list sessions: ${error}`);
132
+ }
133
+ return sessions;
134
+ }
135
+ async getSession(id) {
136
+ const sessions = await this.listSessions();
137
+ const sessionInfo = sessions.find((s) => s.id === id);
138
+ if (!sessionInfo) {
139
+ throw new Error(`Session not found: ${id}`);
140
+ }
141
+ const sessionPath = join2(this.basePath, sessionInfo.projectPath, "sessions", `${id}.json`);
142
+ const data = await this.readSessionFile(sessionPath);
143
+ if (!data) {
144
+ throw new Error(`Failed to read session: ${id}`);
145
+ }
146
+ return this.transformSession(data, sessionInfo.projectPath);
147
+ }
148
+ async readSessionFile(path) {
149
+ try {
150
+ const content = await fs2.readFile(path, "utf-8");
151
+ return JSON.parse(content);
152
+ } catch {
153
+ return null;
154
+ }
155
+ }
156
+ transformSession(data, projectPath) {
157
+ return {
158
+ id: data.id,
159
+ projectPath,
160
+ startTime: data.createdAt,
161
+ endTime: data.updatedAt,
162
+ messages: data.messages.map((msg) => this.transformMessage(msg))
163
+ };
164
+ }
165
+ transformMessage(msg) {
166
+ return {
167
+ role: msg.role === "human" ? "user" : "assistant",
168
+ content: msg.content,
169
+ timestamp: msg.createdAt,
170
+ toolCalls: msg.toolUseBlocks?.map((t) => ({
171
+ name: t.name,
172
+ input: t.input
173
+ })),
174
+ toolResults: msg.toolResultBlocks?.map((t) => ({
175
+ name: t.toolUseId,
176
+ output: t.content,
177
+ success: !t.isError
178
+ }))
179
+ };
180
+ }
181
+ extractSummary(messages) {
182
+ const firstUserMessage = messages.find((m) => m.role === "human");
183
+ if (!firstUserMessage) return "";
184
+ const content = firstUserMessage.content;
185
+ return content.length > 100 ? content.slice(0, 100) + "..." : content;
186
+ }
187
+ };
188
+
189
+ // src/loaders/codex.ts
190
+ import { join as join3 } from "path";
191
+ import { homedir as homedir3 } from "os";
192
+ var CodexSessionLoader = class extends SessionLoader {
193
+ getDefaultBasePath() {
194
+ return join3(homedir3(), ".codex", "history");
195
+ }
196
+ async listSessions() {
197
+ return [];
198
+ }
199
+ async getSession(_id) {
200
+ throw new Error("Codex session loading not yet implemented");
201
+ }
202
+ };
203
+
204
+ // src/loaders/cursor.ts
205
+ import { join as join4 } from "path";
206
+ import { homedir as homedir4 } from "os";
207
+ var CursorSessionLoader = class extends SessionLoader {
208
+ getDefaultBasePath() {
209
+ return join4(homedir4(), ".cursor", "workspaceStorage");
210
+ }
211
+ async listSessions() {
212
+ return [];
213
+ }
214
+ async getSession(_id) {
215
+ throw new Error("Cursor session loading not yet implemented");
216
+ }
217
+ };
218
+
219
+ // src/loaders/index.ts
220
+ function createSessionLoader(platform, options) {
221
+ switch (platform) {
222
+ case "claude":
223
+ return new ClaudeSessionLoader(options);
224
+ case "codex":
225
+ return new CodexSessionLoader(options);
226
+ case "cursor":
227
+ return new CursorSessionLoader(options);
228
+ default:
229
+ throw new Error(`Unsupported platform: ${platform}`);
230
+ }
231
+ }
232
+
233
+ // src/core/llm-engine.ts
234
+ import Anthropic from "@anthropic-ai/sdk";
235
+ import { z as z2 } from "zod";
236
+
237
+ // src/prompts/index.ts
238
+ var SYSTEM_DEFAULT = `You are Skill Distill, an expert at analyzing AI agent conversations and extracting reusable patterns.`;
239
+ var SYSTEM_ANALYST = `You are an expert conversation analyst. Your task is to:
240
+ 1. Understand the user's original intent
241
+ 2. Identify key steps and patterns
242
+ 3. Filter out failed attempts and trial-and-error
243
+ 4. Extract reusable knowledge
244
+
245
+ Always respond in valid JSON format.`;
246
+ var SYSTEM_SKILL_WRITER = `You are an expert at writing Claude Code Skills. You follow these rules:
247
+ 1. Use third-person in descriptions ("This skill should be used when...")
248
+ 2. Use imperative/infinitive form in steps ("Parse the file...", not "You should parse...")
249
+ 3. Include specific trigger phrases
250
+ 4. Keep content focused and lean
251
+ 5. Follow Claude Code Skill format exactly`;
252
+ function intentExtraction(conversation) {
253
+ return `Analyze the following conversation between a user and an AI assistant.
254
+
255
+ <conversation>
256
+ ${conversation}
257
+ </conversation>
258
+
259
+ Extract the user's intent and provide a structured analysis.
260
+
261
+ Respond with a JSON object containing:
262
+ - goal: The user's core objective (what they wanted to achieve)
263
+ - problem: The problem they were trying to solve
264
+ - outcome: What was ultimately accomplished
265
+ - triggers: An array of 3-5 specific phrases that would trigger this type of task (e.g., "migrate to RSC", "add server components")
266
+ - category: The category of this task (e.g., "refactoring", "feature-development", "debugging", "configuration")
267
+
268
+ \`\`\`json
269
+ {
270
+ "goal": "...",
271
+ "problem": "...",
272
+ "outcome": "...",
273
+ "triggers": ["...", "..."],
274
+ "category": "..."
275
+ }
276
+ \`\`\``;
277
+ }
278
+ function stepDistillation(conversation, intent, userPromptAdditions) {
279
+ return `Based on the following conversation, extract the key steps that were taken to achieve the goal.
280
+
281
+ <goal>
282
+ ${intent.goal}
283
+ </goal>
284
+
285
+ <problem>
286
+ ${intent.problem}
287
+ </problem>
288
+
289
+ <conversation>
290
+ ${conversation}
291
+ </conversation>
292
+ ${userPromptAdditions}
293
+
294
+ Instructions:
295
+ 1. Identify the SUCCESSFUL steps that led to the outcome (filter out failed attempts)
296
+ 2. Generalize specific values into parameters where appropriate
297
+ 3. Identify prerequisites needed before starting
298
+ 4. Note any error handling patterns discovered
299
+
300
+ Respond with a JSON object:
301
+
302
+ \`\`\`json
303
+ {
304
+ "steps": [
305
+ {
306
+ "title": "Step title",
307
+ "description": "What to do in this step (use imperative form)",
308
+ "substeps": ["optional substep 1", "optional substep 2"],
309
+ "isKeyStep": true,
310
+ "toolsUsed": ["tool1", "tool2"]
311
+ }
312
+ ],
313
+ "parameters": [
314
+ {
315
+ "name": "parameter_name",
316
+ "type": "string",
317
+ "description": "What this parameter controls",
318
+ "defaultValue": "optional default"
319
+ }
320
+ ],
321
+ "prerequisites": [
322
+ "Prerequisite 1",
323
+ "Prerequisite 2"
324
+ ],
325
+ "errorHandling": {
326
+ "Error scenario 1": "How to handle it",
327
+ "Error scenario 2": "How to handle it"
328
+ }
329
+ }
330
+ \`\`\``;
331
+ }
332
+ function qualityEnhancement(skillJson) {
333
+ return `Review and enhance the following Skill definition for quality and completeness.
334
+
335
+ <skill>
336
+ ${skillJson}
337
+ </skill>
338
+
339
+ Improvements to make:
340
+ 1. Ensure all steps use imperative form ("Do X" not "You should do X")
341
+ 2. Add any missing error handling scenarios
342
+ 3. Improve trigger phrases to be more specific
343
+ 4. Add example usage phrases
344
+ 5. Ensure prerequisites are complete
345
+ 6. Add any missing notes or warnings
346
+
347
+ Return the enhanced Skill as a complete JSON object with the same structure but improved content.`;
348
+ }
349
+ function descriptionGeneration(intent) {
350
+ return `Generate a Claude Code Skill description based on the following:
351
+
352
+ Goal: ${intent.goal}
353
+ Trigger phrases: ${intent.triggers.join(", ")}
354
+
355
+ Requirements:
356
+ 1. Use third-person format: "This skill should be used when..."
357
+ 2. Include the specific trigger phrases in quotes
358
+ 3. Be concise but descriptive (1-2 sentences)
359
+
360
+ Example format:
361
+ "This skill should be used when the user asks to \\"migrate to RSC\\", \\"convert to Server Components\\", or mentions React Server Components migration. Provides step-by-step guidance for the migration process."
362
+
363
+ Generate only the description text, no additional formatting.`;
364
+ }
365
+
366
+ // src/core/llm-engine.ts
367
+ var IntentSchema = z2.object({
368
+ goal: z2.string().describe("The user core objective"),
369
+ problem: z2.string().describe("The problem to solve"),
370
+ outcome: z2.string().describe("The final result"),
371
+ triggers: z2.array(z2.string()).describe("Trigger phrases list"),
372
+ category: z2.string().describe("Task category")
373
+ });
374
+ var StepSchema = z2.object({
375
+ title: z2.string(),
376
+ description: z2.string(),
377
+ substeps: z2.array(z2.string()).optional(),
378
+ isKeyStep: z2.boolean(),
379
+ toolsUsed: z2.array(z2.string()).optional()
380
+ });
381
+ var StepsResultSchema = z2.object({
382
+ steps: z2.array(StepSchema),
383
+ parameters: z2.array(
384
+ z2.object({
385
+ name: z2.string(),
386
+ type: z2.enum(["string", "boolean", "number"]),
387
+ description: z2.string(),
388
+ defaultValue: z2.unknown().optional()
389
+ })
390
+ ),
391
+ prerequisites: z2.array(z2.string()),
392
+ errorHandling: z2.record(z2.string())
393
+ });
394
+ var LLMEngine = class {
395
+ client;
396
+ model;
397
+ maxTokens;
398
+ totalUsage = { inputTokens: 0, outputTokens: 0 };
399
+ constructor(options = {}) {
400
+ this.client = new Anthropic({
401
+ apiKey: options.apiKey ?? process.env.ANTHROPIC_API_KEY
402
+ });
403
+ this.model = options.model ?? "claude-sonnet-4-20250514";
404
+ this.maxTokens = options.maxTokens ?? 4096;
405
+ }
406
+ async extractIntent(session) {
407
+ const conversationText = this.formatConversation(session.messages);
408
+ const response = await this.chat(
409
+ intentExtraction(conversationText),
410
+ SYSTEM_ANALYST
411
+ );
412
+ return this.parseJson(response, IntentSchema);
413
+ }
414
+ async distillSteps(session, intent, userPrompts) {
415
+ const conversationText = this.formatConversation(session.messages);
416
+ const promptAdditions = userPrompts?.length ? `
417
+
418
+ User additional instructions:
419
+ ${userPrompts.map((p) => `- ${p}`).join("\n")}` : "";
420
+ const response = await this.chat(
421
+ stepDistillation(conversationText, intent, promptAdditions),
422
+ SYSTEM_ANALYST
423
+ );
424
+ return this.parseJson(response, StepsResultSchema);
425
+ }
426
+ async enhanceQuality(skill) {
427
+ const response = await this.chat(
428
+ qualityEnhancement(JSON.stringify(skill, null, 2)),
429
+ SYSTEM_SKILL_WRITER
430
+ );
431
+ return this.parseJson(response, z2.any());
432
+ }
433
+ async generateDescription(intent) {
434
+ const response = await this.chat(
435
+ descriptionGeneration(intent),
436
+ SYSTEM_SKILL_WRITER
437
+ );
438
+ return response.trim();
439
+ }
440
+ async chat(prompt, system) {
441
+ const response = await this.withRetry(async () => {
442
+ return this.client.messages.create({
443
+ model: this.model,
444
+ max_tokens: this.maxTokens,
445
+ system: system ?? SYSTEM_DEFAULT,
446
+ messages: [{ role: "user", content: prompt }]
447
+ });
448
+ });
449
+ this.totalUsage.inputTokens += response.usage.input_tokens;
450
+ this.totalUsage.outputTokens += response.usage.output_tokens;
451
+ const textBlock = response.content.find((b) => b.type === "text");
452
+ if (!textBlock || textBlock.type !== "text") {
453
+ throw new Error("No text response from LLM");
454
+ }
455
+ return textBlock.text;
456
+ }
457
+ getTokenUsage() {
458
+ return { ...this.totalUsage };
459
+ }
460
+ formatConversation(messages) {
461
+ return messages.map((m) => {
462
+ const role = m.role === "user" ? "User" : "Assistant";
463
+ let content = `[${role}]: ${m.content}`;
464
+ if (m.toolCalls?.length) {
465
+ content += `
466
+ [Tools Used]: ${m.toolCalls.map((t) => t.name).join(", ")}`;
467
+ }
468
+ return content;
469
+ }).join("\n\n");
470
+ }
471
+ parseJson(text, schema) {
472
+ const jsonMatch = text.match(/```json\n?([\s\S]*?)\n?```/) || text.match(/\{[\s\S]*\}/);
473
+ if (!jsonMatch) {
474
+ throw new Error("No JSON found in response");
475
+ }
476
+ const jsonStr = jsonMatch[1] || jsonMatch[0];
477
+ const parsed = JSON.parse(jsonStr);
478
+ return schema.parse(parsed);
479
+ }
480
+ async withRetry(fn, maxRetries = 3, delay = 1e3) {
481
+ let lastError;
482
+ for (let i = 0; i < maxRetries; i++) {
483
+ try {
484
+ return await fn();
485
+ } catch (error) {
486
+ lastError = error;
487
+ if (error instanceof Anthropic.RateLimitError) {
488
+ await this.sleep(delay * (i + 1) * 2);
489
+ } else if (error instanceof Anthropic.APIError) {
490
+ await this.sleep(delay * (i + 1));
491
+ } else {
492
+ throw error;
493
+ }
494
+ }
495
+ }
496
+ throw lastError;
497
+ }
498
+ sleep(ms) {
499
+ return new Promise((resolve) => setTimeout(resolve, ms));
500
+ }
501
+ };
502
+
503
+ // src/core/preprocessor.ts
504
+ var ConversationPreprocessor = class {
505
+ positiveSignals = [
506
+ "perfect",
507
+ "great",
508
+ "thanks",
509
+ "good",
510
+ "works",
511
+ "done",
512
+ "\u597D\u7684",
513
+ "\u53EF\u4EE5",
514
+ "\u5B8C\u6210",
515
+ "\u6210\u529F",
516
+ "\u5BF9\u7684"
517
+ ];
518
+ negativeSignals = [
519
+ "wrong",
520
+ "error",
521
+ "fail",
522
+ "doesn't work",
523
+ "not working",
524
+ "not right",
525
+ "undo",
526
+ "\u4E0D\u5BF9",
527
+ "\u9519\u8BEF",
528
+ "\u5931\u8D25",
529
+ "\u64A4\u9500",
530
+ "\u56DE\u6EDA"
531
+ ];
532
+ process(messages) {
533
+ let turnIndex = 0;
534
+ let toolCallCount = 0;
535
+ const processed = messages.map((msg) => {
536
+ if (msg.role === "user") {
537
+ turnIndex++;
538
+ }
539
+ const hasToolCalls = (msg.toolCalls?.length ?? 0) > 0;
540
+ if (hasToolCalls) {
541
+ toolCallCount += msg.toolCalls.length;
542
+ }
543
+ const codeBlocks = this.extractCodeBlocks(msg.content);
544
+ return {
545
+ ...msg,
546
+ turnIndex,
547
+ hasToolCalls,
548
+ hasCodeBlocks: codeBlocks.length > 0,
549
+ codeBlocks,
550
+ sentiment: this.analyzeSentiment(msg.content)
551
+ };
552
+ });
553
+ return {
554
+ messages: processed,
555
+ totalTurns: turnIndex,
556
+ toolCallCount
557
+ };
558
+ }
559
+ extractCodeBlocks(content) {
560
+ const regex = /```[\w]*\n([\s\S]*?)```/g;
561
+ const blocks = [];
562
+ let match;
563
+ while ((match = regex.exec(content)) !== null) {
564
+ blocks.push(match[1].trim());
565
+ }
566
+ return blocks;
567
+ }
568
+ analyzeSentiment(content) {
569
+ const lower = content.toLowerCase();
570
+ const hasPositive = this.positiveSignals.some((s) => lower.includes(s));
571
+ const hasNegative = this.negativeSignals.some((s) => lower.includes(s));
572
+ if (hasPositive && !hasNegative) return "positive";
573
+ if (hasNegative) return "negative";
574
+ return "neutral";
575
+ }
576
+ };
577
+
578
+ // src/core/failed-filter.ts
579
+ var FailedAttemptFilter = class {
580
+ failureIndicators = {
581
+ userSignals: [
582
+ "not working",
583
+ "doesn't work",
584
+ "wrong",
585
+ "undo",
586
+ "revert",
587
+ "go back",
588
+ "try again",
589
+ "that's not right",
590
+ "actually",
591
+ "\u4E0D\u5BF9",
592
+ "\u64A4\u9500",
593
+ "\u56DE\u6EDA",
594
+ "\u91CD\u8BD5",
595
+ "\u6362\u4E2A\u65B9\u6CD5"
596
+ ],
597
+ assistantSignals: [
598
+ "sorry",
599
+ "apologize",
600
+ "my mistake",
601
+ "let me try again",
602
+ "I made an error",
603
+ "that was incorrect",
604
+ "\u62B1\u6B49",
605
+ "\u5BF9\u4E0D\u8D77",
606
+ "\u6211\u7684\u9519\u8BEF",
607
+ "\u8BA9\u6211\u91CD\u8BD5"
608
+ ],
609
+ toolFailures: ["error", "failed", "exception", "not found", "permission denied"]
610
+ };
611
+ filter(messages) {
612
+ const failedTurns = this.identifyFailedTurns(messages);
613
+ const filtered = messages.filter((msg) => !failedTurns.has(msg.turnIndex));
614
+ return {
615
+ messages: filtered,
616
+ removedCount: messages.length - filtered.length,
617
+ removedTurns: Array.from(failedTurns)
618
+ };
619
+ }
620
+ identifyFailedTurns(messages) {
621
+ const failedTurns = /* @__PURE__ */ new Set();
622
+ for (let i = 0; i < messages.length; i++) {
623
+ const msg = messages[i];
624
+ const nextMsg = messages[i + 1];
625
+ if (msg.role === "user" && this.hasFailureSignal(msg.content, "userSignals")) {
626
+ if (msg.turnIndex > 1) {
627
+ failedTurns.add(msg.turnIndex - 1);
628
+ }
629
+ }
630
+ if (msg.role === "assistant" && this.hasFailureSignal(msg.content, "assistantSignals")) {
631
+ failedTurns.add(msg.turnIndex);
632
+ }
633
+ if (msg.toolResults?.some((r) => !r.success)) {
634
+ failedTurns.add(msg.turnIndex);
635
+ }
636
+ if (msg.toolResults?.some((r) => this.hasFailureSignal(r.output, "toolFailures"))) {
637
+ failedTurns.add(msg.turnIndex);
638
+ }
639
+ if (msg.role === "user" && nextMsg?.role === "user") {
640
+ failedTurns.add(msg.turnIndex);
641
+ }
642
+ }
643
+ return failedTurns;
644
+ }
645
+ hasFailureSignal(content, signalType) {
646
+ const lower = content.toLowerCase();
647
+ return this.failureIndicators[signalType].some((signal) => lower.includes(signal.toLowerCase()));
648
+ }
649
+ };
650
+
651
+ // src/core/parameter-extractor.ts
652
+ var ParameterExtractor = class {
653
+ patterns = [
654
+ { regex: /(?:src|lib|app|components|pages)\/[\w\-/]+\.\w+/g, type: "path" },
655
+ { regex: /(?:src|lib|app|components|pages)\/[\w\-/]+/g, type: "directory" },
656
+ {
657
+ regex: /\b[a-z][a-zA-Z0-9]*(?:Component|Hook|Service|Controller|Module)\b/g,
658
+ type: "name"
659
+ },
660
+ { regex: /:\d{4,5}\b/g, type: "port" },
661
+ { regex: /https?:\/\/[^\s]+/g, type: "url" }
662
+ ];
663
+ extract(steps, existingParams) {
664
+ const extractedParams = /* @__PURE__ */ new Map();
665
+ existingParams.forEach((p) => {
666
+ extractedParams.set(p.name, {
667
+ ...p,
668
+ required: p.default === void 0
669
+ });
670
+ });
671
+ const processedSteps = steps.filter((s) => s.isKeyStep).map((step) => {
672
+ let description = step.description;
673
+ for (const pattern of this.patterns) {
674
+ const matches = description.match(pattern.regex);
675
+ if (matches) {
676
+ for (const match of matches) {
677
+ const paramName = this.generateParamName(match, pattern.type);
678
+ if (!extractedParams.has(paramName)) {
679
+ extractedParams.set(paramName, {
680
+ name: paramName,
681
+ type: "string",
682
+ description: `${pattern.type} parameter extracted from step`,
683
+ default: match,
684
+ required: false
685
+ });
686
+ }
687
+ description = description.replace(match, `{${paramName}}`);
688
+ }
689
+ }
690
+ }
691
+ return {
692
+ title: step.title,
693
+ description,
694
+ substeps: step.substeps
695
+ };
696
+ });
697
+ return {
698
+ steps: processedSteps,
699
+ parameters: Array.from(extractedParams.values())
700
+ };
701
+ }
702
+ generateParamName(value, type) {
703
+ switch (type) {
704
+ case "path": {
705
+ const fileName = value.split("/").pop() ?? "file";
706
+ return `${fileName.replace(/\.\w+$/, "")}_path`;
707
+ }
708
+ case "directory": {
709
+ const dirName = value.split("/").pop() ?? "dir";
710
+ return `${dirName}_dir`;
711
+ }
712
+ case "name":
713
+ return value.toLowerCase().replace(/component|hook|service/gi, "") + "_name";
714
+ case "port":
715
+ return "port";
716
+ case "url":
717
+ return "api_url";
718
+ default:
719
+ return `param_${type}`;
720
+ }
721
+ }
722
+ };
723
+
724
+ // src/core/distiller.ts
725
+ var Distiller = class {
726
+ llmEngine;
727
+ preprocessor;
728
+ failedFilter;
729
+ parameterExtractor;
730
+ constructor(llmEngine) {
731
+ this.llmEngine = llmEngine ?? new LLMEngine();
732
+ this.preprocessor = new ConversationPreprocessor();
733
+ this.failedFilter = new FailedAttemptFilter();
734
+ this.parameterExtractor = new ParameterExtractor();
735
+ }
736
+ async distill(session, options = {}) {
737
+ const { userPrompts, skipFailedFilter, verbose } = options;
738
+ if (verbose) console.log("Preprocessing conversation...");
739
+ const preprocessed = this.preprocessor.process(session.messages);
740
+ let filteredMessages = preprocessed.messages;
741
+ let stepsFiltered = 0;
742
+ if (!skipFailedFilter) {
743
+ if (verbose) console.log("Filtering failed attempts...");
744
+ const filterResult = this.failedFilter.filter(preprocessed.messages);
745
+ filteredMessages = filterResult.messages;
746
+ stepsFiltered = filterResult.removedCount;
747
+ }
748
+ const filteredSession = {
749
+ ...session,
750
+ messages: filteredMessages
751
+ };
752
+ if (verbose) console.log("Extracting intent...");
753
+ const intent = await this.llmEngine.extractIntent(filteredSession);
754
+ if (verbose) console.log("Distilling steps...");
755
+ const stepsResult = await this.llmEngine.distillSteps(filteredSession, intent, userPrompts);
756
+ if (verbose) console.log("Extracting parameters...");
757
+ const { steps: parameterizedSteps, parameters } = this.parameterExtractor.extract(
758
+ stepsResult.steps,
759
+ this.convertParameters(stepsResult.parameters)
760
+ );
761
+ if (verbose) console.log("Generating description...");
762
+ const description = await this.llmEngine.generateDescription(intent);
763
+ const skill = this.assembleSkill(
764
+ intent,
765
+ description,
766
+ parameterizedSteps,
767
+ parameters,
768
+ stepsResult
769
+ );
770
+ if (verbose) console.log("Enhancing quality...");
771
+ const enhancedSkill = await this.llmEngine.enhanceQuality(skill);
772
+ const tokenUsage = this.llmEngine.getTokenUsage();
773
+ return {
774
+ skill: enhancedSkill,
775
+ metadata: {
776
+ sessionId: session.id,
777
+ distilledAt: (/* @__PURE__ */ new Date()).toISOString(),
778
+ tokenUsage: {
779
+ input: tokenUsage.inputTokens,
780
+ output: tokenUsage.outputTokens
781
+ },
782
+ stepsFiltered
783
+ }
784
+ };
785
+ }
786
+ convertParameters(params) {
787
+ return params.map((p) => ({
788
+ name: p.name,
789
+ type: p.type,
790
+ description: p.description,
791
+ default: p.defaultValue,
792
+ required: p.defaultValue === void 0
793
+ }));
794
+ }
795
+ assembleSkill(intent, description, steps, parameters, stepsResult) {
796
+ return {
797
+ metadata: {
798
+ name: this.generateName(intent),
799
+ description,
800
+ version: "1.0.0"
801
+ },
802
+ overview: intent.goal,
803
+ triggers: intent.triggers,
804
+ prerequisites: stepsResult.prerequisites,
805
+ steps,
806
+ parameters,
807
+ errorHandling: stepsResult.errorHandling,
808
+ examples: this.generateExamples(intent.triggers),
809
+ notes: []
810
+ };
811
+ }
812
+ generateName(intent) {
813
+ const words = intent.goal.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 2).slice(0, 4);
814
+ return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
815
+ }
816
+ generateExamples(triggers) {
817
+ return triggers.slice(0, 3).map((trigger) => `Help me ${trigger.toLowerCase()}`);
818
+ }
819
+ };
820
+
821
+ // src/prompt-handler/classifier.ts
822
+ var PromptClassifier = class {
823
+ constructor(language = "zh") {
824
+ this.language = language;
825
+ this.patterns = {
826
+ zh: [
827
+ // Order matters: more specific patterns (exclude with negation) must come before generic ones
828
+ { keywords: ["\u4E0D\u8981", "\u6392\u9664", "\u8DF3\u8FC7", "\u5FFD\u7565", "\u4E0D\u5305\u542B", "\u53BB\u6389"], type: "exclude" },
829
+ { keywords: ["\u91CD\u70B9", "\u5173\u6CE8", "\u5F3A\u8C03", "\u7740\u91CD", "\u7279\u522B\u6CE8\u610F"], type: "focus" },
830
+ { keywords: ["\u9700\u8981", "\u5FC5\u987B", "\u517C\u5BB9", "\u8981\u6C42", "\u786E\u4FDD", "\u652F\u6301"], type: "constraint" },
831
+ { keywords: ["\u6DFB\u52A0", "\u8865\u5145", "\u5305\u542B", "\u52A0\u5165", "\u589E\u52A0"], type: "supplement" },
832
+ { keywords: ["\u8BE6\u7EC6", "\u7B80\u6D01", "\u9002\u5408", "\u98CE\u683C", "\u5F62\u5F0F"], type: "style" }
833
+ ],
834
+ en: [
835
+ // Order matters: "don't include" must be checked before "include"
836
+ { keywords: ["don't include", "exclude", "skip", "ignore", "without", "do not include"], type: "exclude" },
837
+ { keywords: ["focus", "emphasize", "highlight", "prioritize"], type: "focus" },
838
+ { keywords: ["must", "require", "compatible", "ensure", "support"], type: "constraint" },
839
+ { keywords: ["add", "include", "supplement", "incorporate"], type: "supplement" },
840
+ { keywords: ["detailed", "concise", "style", "format", "suitable"], type: "style" }
841
+ ]
842
+ };
843
+ }
844
+ patterns;
845
+ classify(prompt) {
846
+ const lower = prompt.toLowerCase();
847
+ const patterns = this.patterns[this.language];
848
+ let matchedType = "supplement";
849
+ let matchedKeywords = [];
850
+ for (const pattern of patterns) {
851
+ const foundKeywords = pattern.keywords.filter((k) => lower.includes(k.toLowerCase()));
852
+ if (foundKeywords.length > 0) {
853
+ matchedType = pattern.type;
854
+ matchedKeywords = foundKeywords;
855
+ break;
856
+ }
857
+ }
858
+ const target = this.extractTarget(prompt, matchedKeywords);
859
+ return {
860
+ original: prompt,
861
+ type: matchedType,
862
+ keywords: matchedKeywords,
863
+ target
864
+ };
865
+ }
866
+ extractTarget(prompt, keywords) {
867
+ if (!keywords.length) return void 0;
868
+ for (const keyword of keywords) {
869
+ const index = prompt.toLowerCase().indexOf(keyword.toLowerCase());
870
+ if (index !== -1) {
871
+ const after = prompt.slice(index + keyword.length).trim();
872
+ const match = after.match(/^[^,。,.!!??]+/);
873
+ if (match) {
874
+ return match[0].trim();
875
+ }
876
+ }
877
+ }
878
+ return void 0;
879
+ }
880
+ };
881
+
882
+ // src/prompt-handler/fuser.ts
883
+ var PromptFuser = class {
884
+ fuse(prompts) {
885
+ const effect = {};
886
+ for (const prompt of prompts) {
887
+ switch (prompt.type) {
888
+ case "focus":
889
+ this.applyFocus(prompt, effect);
890
+ break;
891
+ case "constraint":
892
+ this.applyConstraint(prompt, effect);
893
+ break;
894
+ case "supplement":
895
+ this.applySupplement(prompt, effect);
896
+ break;
897
+ case "style":
898
+ this.applyStyle(prompt, effect);
899
+ break;
900
+ case "exclude":
901
+ this.applyExclude(prompt, effect);
902
+ break;
903
+ }
904
+ }
905
+ return effect;
906
+ }
907
+ applyFocus(prompt, effect) {
908
+ if (!effect.stepWeights) {
909
+ effect.stepWeights = /* @__PURE__ */ new Map();
910
+ }
911
+ if (prompt.target) {
912
+ effect.stepWeights.set(prompt.target.toLowerCase(), 2);
913
+ }
914
+ }
915
+ applyConstraint(prompt, effect) {
916
+ if (!effect.additionalPrerequisites) {
917
+ effect.additionalPrerequisites = [];
918
+ }
919
+ effect.additionalPrerequisites.push(prompt.original);
920
+ }
921
+ applySupplement(prompt, effect) {
922
+ if (!effect.stepEnrichments) {
923
+ effect.stepEnrichments = /* @__PURE__ */ new Map();
924
+ }
925
+ const existing = effect.stepEnrichments.get("additional") ?? [];
926
+ existing.push(prompt.original);
927
+ effect.stepEnrichments.set("additional", existing);
928
+ }
929
+ applyStyle(prompt, effect) {
930
+ const lower = prompt.original.toLowerCase();
931
+ if (lower.includes("\u8BE6\u7EC6") || lower.includes("detailed")) {
932
+ effect.verbosityLevel = "detailed";
933
+ } else if (lower.includes("\u7B80\u6D01") || lower.includes("concise")) {
934
+ effect.verbosityLevel = "minimal";
935
+ } else {
936
+ effect.verbosityLevel = "standard";
937
+ }
938
+ }
939
+ applyExclude(prompt, effect) {
940
+ if (!effect.excludePatterns) {
941
+ effect.excludePatterns = [];
942
+ }
943
+ if (prompt.target) {
944
+ effect.excludePatterns.push(prompt.target.toLowerCase());
945
+ }
946
+ }
947
+ };
948
+
949
+ // src/prompt-handler/prompt-handler.ts
950
+ var PromptHandler = class {
951
+ classifier;
952
+ fuser;
953
+ constructor(options = {}) {
954
+ this.classifier = new PromptClassifier(options.language ?? "zh");
955
+ this.fuser = new PromptFuser();
956
+ }
957
+ process(prompts) {
958
+ const classified = prompts.map((p) => this.classifier.classify(p));
959
+ return this.fuser.fuse(classified);
960
+ }
961
+ classify(prompt) {
962
+ return this.classifier.classify(prompt);
963
+ }
964
+ toPromptText(prompts) {
965
+ if (!prompts.length) return "";
966
+ const classified = prompts.map((p) => this.classifier.classify(p));
967
+ const sections = {
968
+ focus: classified.filter((c) => c.type === "focus"),
969
+ constraint: classified.filter((c) => c.type === "constraint"),
970
+ supplement: classified.filter((c) => c.type === "supplement"),
971
+ style: classified.filter((c) => c.type === "style"),
972
+ exclude: classified.filter((c) => c.type === "exclude")
973
+ };
974
+ const parts = ["## User Instructions"];
975
+ if (sections.focus.length) {
976
+ parts.push("\n### Focus Areas");
977
+ sections.focus.forEach((p) => parts.push(`- ${p.original}`));
978
+ }
979
+ if (sections.constraint.length) {
980
+ parts.push("\n### Constraints");
981
+ sections.constraint.forEach((p) => parts.push(`- ${p.original}`));
982
+ }
983
+ if (sections.supplement.length) {
984
+ parts.push("\n### Additional Content");
985
+ sections.supplement.forEach((p) => parts.push(`- ${p.original}`));
986
+ }
987
+ if (sections.style.length) {
988
+ parts.push("\n### Output Style");
989
+ sections.style.forEach((p) => parts.push(`- ${p.original}`));
990
+ }
991
+ if (sections.exclude.length) {
992
+ parts.push("\n### Exclusions");
993
+ sections.exclude.forEach((p) => parts.push(`- ${p.original}`));
994
+ }
995
+ return parts.join("\n");
996
+ }
997
+ };
998
+
999
+ // src/prompt-handler/interactive.ts
1000
+ import inquirer from "inquirer";
1001
+ import chalk2 from "chalk";
1002
+ async function collectPromptsInteractively() {
1003
+ const prompts = [];
1004
+ console.log(chalk2.cyan("\n\u{1F4DD} \u6DFB\u52A0\u9644\u52A0\u6307\u4EE4 (\u53EF\u9009)"));
1005
+ console.log(chalk2.gray(" \u8F93\u5165\u7A7A\u884C\u5B8C\u6210\u6536\u96C6\n"));
1006
+ for (; ; ) {
1007
+ const { prompt } = await inquirer.prompt([
1008
+ {
1009
+ type: "input",
1010
+ name: "prompt",
1011
+ message: "\u6307\u4EE4:",
1012
+ prefix: chalk2.yellow("\u2192")
1013
+ }
1014
+ ]);
1015
+ if (!prompt.trim()) {
1016
+ break;
1017
+ }
1018
+ prompts.push(prompt.trim());
1019
+ }
1020
+ if (prompts.length === 0) {
1021
+ return { prompts: [], confirmed: true };
1022
+ }
1023
+ console.log(chalk2.cyan("\n\u{1F4CB} \u5DF2\u6536\u96C6\u7684\u6307\u4EE4:"));
1024
+ prompts.forEach((p, i) => {
1025
+ console.log(chalk2.gray(` ${i + 1}. ${p}`));
1026
+ });
1027
+ const { confirm } = await inquirer.prompt([
1028
+ {
1029
+ type: "confirm",
1030
+ name: "confirm",
1031
+ message: "\u4F7F\u7528\u8FD9\u4E9B\u6307\u4EE4?",
1032
+ default: true
1033
+ }
1034
+ ]);
1035
+ return { prompts, confirmed: confirm };
1036
+ }
1037
+ async function selectPromptsFromSuggestions(suggestions) {
1038
+ const { selected } = await inquirer.prompt([
1039
+ {
1040
+ type: "checkbox",
1041
+ name: "selected",
1042
+ message: "\u9009\u62E9\u8981\u5E94\u7528\u7684\u6307\u4EE4:",
1043
+ choices: suggestions.map((s) => ({ name: s, value: s }))
1044
+ }
1045
+ ]);
1046
+ return selected;
1047
+ }
1048
+
1049
+ // src/generators/skill-generator.ts
1050
+ import { promises as fs4 } from "fs";
1051
+ import { join as join6 } from "path";
1052
+
1053
+ // src/generators/frontmatter-builder.ts
1054
+ import { stringify } from "yaml";
1055
+ var FrontmatterBuilder = class {
1056
+ build(metadata) {
1057
+ const frontmatter = {
1058
+ name: metadata.name,
1059
+ description: metadata.description,
1060
+ version: metadata.version
1061
+ };
1062
+ if (metadata.license) {
1063
+ frontmatter.license = metadata.license;
1064
+ }
1065
+ const yaml = stringify(frontmatter, {
1066
+ lineWidth: 0,
1067
+ defaultStringType: "PLAIN"
1068
+ });
1069
+ return `---
1070
+ ${yaml}---`;
1071
+ }
1072
+ validateDescription(description) {
1073
+ const thirdPersonStart = description.toLowerCase().startsWith("this skill");
1074
+ const hasTriggerPhrases = description.includes('"') || description.includes("'");
1075
+ return thirdPersonStart && hasTriggerPhrases;
1076
+ }
1077
+ };
1078
+
1079
+ // src/generators/markdown-renderer.ts
1080
+ var MarkdownRenderer = class {
1081
+ renderTitle(name) {
1082
+ return `# ${name}`;
1083
+ }
1084
+ renderOverview(overview) {
1085
+ return `## Overview
1086
+
1087
+ ${overview}`;
1088
+ }
1089
+ renderTriggers(triggers) {
1090
+ const lines = ["## When This Skill Applies", "", "This skill activates when the user's request involves:"];
1091
+ triggers.forEach((trigger) => {
1092
+ lines.push(`- ${trigger}`);
1093
+ });
1094
+ return lines.join("\n");
1095
+ }
1096
+ renderPrerequisites(prerequisites) {
1097
+ const lines = ["## Prerequisites", ""];
1098
+ prerequisites.forEach((prereq) => {
1099
+ lines.push(`- ${prereq}`);
1100
+ });
1101
+ return lines.join("\n");
1102
+ }
1103
+ renderSteps(steps) {
1104
+ const lines = ["## Core Workflow", ""];
1105
+ steps.forEach((step, index) => {
1106
+ lines.push(`### Step ${index + 1}: ${step.title}`);
1107
+ lines.push("");
1108
+ lines.push(step.description);
1109
+ if (step.substeps?.length) {
1110
+ lines.push("");
1111
+ step.substeps.forEach((substep, i) => {
1112
+ lines.push(`${i + 1}. ${substep}`);
1113
+ });
1114
+ }
1115
+ lines.push("");
1116
+ });
1117
+ return lines.join("\n").trim();
1118
+ }
1119
+ renderParameters(parameters) {
1120
+ const lines = [
1121
+ "## Parameters",
1122
+ "",
1123
+ "| Parameter | Type | Default | Description |",
1124
+ "|-----------|------|---------|-------------|"
1125
+ ];
1126
+ parameters.forEach((param) => {
1127
+ const defaultVal = param.default !== void 0 ? `\`${param.default}\`` : "-";
1128
+ const required = param.required ? " (required)" : "";
1129
+ lines.push(`| \`${param.name}\` | ${param.type} | ${defaultVal} | ${param.description}${required} |`);
1130
+ });
1131
+ return lines.join("\n");
1132
+ }
1133
+ renderErrorHandling(errorHandling) {
1134
+ const lines = ["## Error Handling", ""];
1135
+ Object.entries(errorHandling).forEach(([error, solution]) => {
1136
+ lines.push(`- **${error}**: ${solution}`);
1137
+ });
1138
+ return lines.join("\n");
1139
+ }
1140
+ renderExamples(examples) {
1141
+ const lines = ["## Example Usage", ""];
1142
+ examples.forEach((example) => {
1143
+ lines.push("```");
1144
+ lines.push(example);
1145
+ lines.push("```");
1146
+ lines.push("");
1147
+ });
1148
+ return lines.join("\n").trim();
1149
+ }
1150
+ renderReferences(references) {
1151
+ const lines = ["## Additional Resources", "", "### Reference Files", ""];
1152
+ references.forEach((ref) => {
1153
+ lines.push(`- **\`${ref}\`**`);
1154
+ });
1155
+ return lines.join("\n");
1156
+ }
1157
+ renderNotes(notes) {
1158
+ const lines = ["## Notes", ""];
1159
+ notes.forEach((note) => {
1160
+ lines.push(`- ${note}`);
1161
+ });
1162
+ return lines.join("\n");
1163
+ }
1164
+ };
1165
+
1166
+ // src/generators/directory-builder.ts
1167
+ import { promises as fs3 } from "fs";
1168
+ import { join as join5 } from "path";
1169
+ var DirectoryBuilder = class {
1170
+ async create(basePath, options = {}) {
1171
+ const { includeReferences = true, includeExamples = false, includeScripts = false } = options;
1172
+ await fs3.mkdir(basePath, { recursive: true });
1173
+ if (includeReferences) {
1174
+ await fs3.mkdir(join5(basePath, "references"), { recursive: true });
1175
+ }
1176
+ if (includeExamples) {
1177
+ await fs3.mkdir(join5(basePath, "examples"), { recursive: true });
1178
+ }
1179
+ if (includeScripts) {
1180
+ await fs3.mkdir(join5(basePath, "scripts"), { recursive: true });
1181
+ }
1182
+ }
1183
+ async listStructure(basePath) {
1184
+ const files = [];
1185
+ async function walk(dir, prefix = "") {
1186
+ const entries = await fs3.readdir(dir, { withFileTypes: true });
1187
+ for (const entry of entries) {
1188
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
1189
+ if (entry.isDirectory()) {
1190
+ files.push(`${relativePath}/`);
1191
+ await walk(join5(dir, entry.name), relativePath);
1192
+ } else {
1193
+ files.push(relativePath);
1194
+ }
1195
+ }
1196
+ }
1197
+ await walk(basePath);
1198
+ return files;
1199
+ }
1200
+ };
1201
+
1202
+ // src/generators/template-validator.ts
1203
+ var TemplateValidator = class {
1204
+ validate(content, skill) {
1205
+ const errors = [];
1206
+ const warnings = [];
1207
+ this.validateFrontmatter(content, skill, errors, warnings);
1208
+ this.validateRequiredSections(content, errors);
1209
+ this.validateWritingStyle(content, warnings);
1210
+ this.validateLength(content, warnings);
1211
+ return {
1212
+ valid: errors.length === 0,
1213
+ errors,
1214
+ warnings
1215
+ };
1216
+ }
1217
+ validateFrontmatter(content, skill, errors, warnings) {
1218
+ if (!content.startsWith("---")) {
1219
+ errors.push({
1220
+ field: "frontmatter",
1221
+ message: "Missing YAML frontmatter",
1222
+ severity: "error"
1223
+ });
1224
+ return;
1225
+ }
1226
+ if (!skill.metadata.description.toLowerCase().includes("this skill")) {
1227
+ warnings.push({
1228
+ field: "description",
1229
+ message: 'Description should start with "This skill should be used when..."',
1230
+ severity: "warning"
1231
+ });
1232
+ }
1233
+ if (!skill.metadata.description.includes('"')) {
1234
+ warnings.push({
1235
+ field: "description",
1236
+ message: "Description should include specific trigger phrases in quotes",
1237
+ severity: "warning"
1238
+ });
1239
+ }
1240
+ }
1241
+ validateRequiredSections(content, errors) {
1242
+ const requiredSections = [
1243
+ { pattern: /^## Overview/m, name: "Overview" },
1244
+ { pattern: /^## (When This Skill Applies|Triggers)/m, name: "Triggers" },
1245
+ { pattern: /^## (Core Workflow|Steps)/m, name: "Steps" }
1246
+ ];
1247
+ for (const section of requiredSections) {
1248
+ if (!section.pattern.test(content)) {
1249
+ errors.push({
1250
+ field: section.name,
1251
+ message: `Missing required section: ${section.name}`,
1252
+ severity: "error"
1253
+ });
1254
+ }
1255
+ }
1256
+ }
1257
+ validateWritingStyle(content, warnings) {
1258
+ const secondPersonPatterns = [/\bYou should\b/gi, /\bYou need to\b/gi, /\bYou can\b/gi, /\bYou must\b/gi];
1259
+ for (const pattern of secondPersonPatterns) {
1260
+ if (pattern.test(content)) {
1261
+ warnings.push({
1262
+ field: "style",
1263
+ message: `Avoid second person ("${pattern.source}"). Use imperative form instead.`,
1264
+ severity: "warning"
1265
+ });
1266
+ }
1267
+ }
1268
+ }
1269
+ validateLength(content, warnings) {
1270
+ const wordCount = content.split(/\s+/).length;
1271
+ if (wordCount > 5e3) {
1272
+ warnings.push({
1273
+ field: "length",
1274
+ message: `SKILL.md is too long (${wordCount} words). Consider moving content to references/`,
1275
+ severity: "warning"
1276
+ });
1277
+ }
1278
+ if (wordCount < 200) {
1279
+ warnings.push({
1280
+ field: "length",
1281
+ message: `SKILL.md is very short (${wordCount} words). Consider adding more detail.`,
1282
+ severity: "warning"
1283
+ });
1284
+ }
1285
+ }
1286
+ };
1287
+
1288
+ // src/generators/skill-generator.ts
1289
+ var SkillGenerator = class {
1290
+ frontmatterBuilder;
1291
+ markdownRenderer;
1292
+ directoryBuilder;
1293
+ validator;
1294
+ constructor() {
1295
+ this.frontmatterBuilder = new FrontmatterBuilder();
1296
+ this.markdownRenderer = new MarkdownRenderer();
1297
+ this.directoryBuilder = new DirectoryBuilder();
1298
+ this.validator = new TemplateValidator();
1299
+ }
1300
+ async generate(skill, options) {
1301
+ const { outputDir, overwrite = false, includeReferences = true } = options;
1302
+ const skillDirName = this.toKebabCase(skill.metadata.name);
1303
+ const skillDir = join6(outputDir, skillDirName);
1304
+ if (!overwrite && await this.exists(skillDir)) {
1305
+ throw new Error(`Skill directory already exists: ${skillDir}`);
1306
+ }
1307
+ await this.directoryBuilder.create(skillDir, {
1308
+ includeReferences,
1309
+ includeExamples: options.includeExamples ?? false
1310
+ });
1311
+ const content = this.renderSkill(skill);
1312
+ const validation = this.validator.validate(content, skill);
1313
+ const skillPath = join6(skillDir, "SKILL.md");
1314
+ await fs4.writeFile(skillPath, content, "utf-8");
1315
+ const files = [skillPath];
1316
+ if (includeReferences && skill.references?.length) {
1317
+ const refPath = await this.generateReferences(skillDir, skill);
1318
+ files.push(refPath);
1319
+ }
1320
+ return {
1321
+ skillPath,
1322
+ files,
1323
+ validation
1324
+ };
1325
+ }
1326
+ renderSkill(skill) {
1327
+ const parts = [];
1328
+ parts.push(this.frontmatterBuilder.build(skill.metadata));
1329
+ parts.push(this.markdownRenderer.renderTitle(skill.metadata.name));
1330
+ parts.push(this.markdownRenderer.renderOverview(skill.overview));
1331
+ parts.push(this.markdownRenderer.renderTriggers(skill.triggers));
1332
+ if (skill.prerequisites.length) {
1333
+ parts.push(this.markdownRenderer.renderPrerequisites(skill.prerequisites));
1334
+ }
1335
+ parts.push(this.markdownRenderer.renderSteps(skill.steps));
1336
+ if (skill.parameters.length) {
1337
+ parts.push(this.markdownRenderer.renderParameters(skill.parameters));
1338
+ }
1339
+ if (Object.keys(skill.errorHandling).length) {
1340
+ parts.push(this.markdownRenderer.renderErrorHandling(skill.errorHandling));
1341
+ }
1342
+ if (skill.examples.length) {
1343
+ parts.push(this.markdownRenderer.renderExamples(skill.examples));
1344
+ }
1345
+ if (skill.references?.length) {
1346
+ parts.push(this.markdownRenderer.renderReferences(skill.references));
1347
+ }
1348
+ if (skill.notes?.length) {
1349
+ parts.push(this.markdownRenderer.renderNotes(skill.notes));
1350
+ }
1351
+ return parts.join("\n\n");
1352
+ }
1353
+ async generateReferences(skillDir, skill) {
1354
+ const refDir = join6(skillDir, "references");
1355
+ await ensureDir(refDir);
1356
+ const patternsPath = join6(refDir, "patterns.md");
1357
+ const patternsContent = `# Patterns and Best Practices
1358
+
1359
+ This file contains detailed patterns extracted from the original conversation.
1360
+
1361
+ ## Common Patterns
1362
+
1363
+ ${skill.notes?.map((n) => `- ${n}`).join("\n") || "No additional patterns documented."}
1364
+ `;
1365
+ await fs4.writeFile(patternsPath, patternsContent, "utf-8");
1366
+ return patternsPath;
1367
+ }
1368
+ toKebabCase(str) {
1369
+ return str.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
1370
+ }
1371
+ async exists(path) {
1372
+ try {
1373
+ await fs4.access(path);
1374
+ return true;
1375
+ } catch {
1376
+ return false;
1377
+ }
1378
+ }
1379
+ };
1380
+
1381
+ // src/formatters/claude-formatter.ts
1382
+ import { stringify as stringify2 } from "yaml";
1383
+ var ClaudeFormatter = class {
1384
+ format(skill, options = {}) {
1385
+ const parts = [];
1386
+ parts.push(this.formatFrontmatter(skill));
1387
+ parts.push(`# ${skill.metadata.name}`);
1388
+ parts.push(`## Overview
1389
+
1390
+ ${skill.overview}`);
1391
+ parts.push(this.formatTriggers(skill.triggers));
1392
+ if (skill.prerequisites.length > 0) {
1393
+ parts.push(this.formatPrerequisites(skill.prerequisites));
1394
+ }
1395
+ parts.push(this.formatSteps(skill.steps));
1396
+ if (skill.parameters.length > 0) {
1397
+ parts.push(this.formatParameters(skill.parameters));
1398
+ }
1399
+ if (Object.keys(skill.errorHandling).length > 0) {
1400
+ parts.push(this.formatErrorHandling(skill.errorHandling));
1401
+ }
1402
+ if (options.includeExamples !== false && skill.examples.length > 0) {
1403
+ parts.push(this.formatExamples(skill.examples));
1404
+ }
1405
+ if (skill.notes && skill.notes.length > 0) {
1406
+ parts.push(this.formatNotes(skill.notes));
1407
+ }
1408
+ return parts.join("\n\n");
1409
+ }
1410
+ formatFrontmatter(skill) {
1411
+ const data = {
1412
+ name: skill.metadata.name,
1413
+ description: skill.metadata.description,
1414
+ version: skill.metadata.version
1415
+ };
1416
+ if (skill.metadata.license) {
1417
+ data.license = skill.metadata.license;
1418
+ }
1419
+ return `---
1420
+ ${stringify2(data)}---`;
1421
+ }
1422
+ formatTriggers(triggers) {
1423
+ const lines = ["## When This Skill Applies", "", "This skill activates when:"];
1424
+ triggers.forEach((t) => lines.push(`- ${t}`));
1425
+ return lines.join("\n");
1426
+ }
1427
+ formatPrerequisites(prerequisites) {
1428
+ const lines = ["## Prerequisites", ""];
1429
+ prerequisites.forEach((p) => lines.push(`- ${p}`));
1430
+ return lines.join("\n");
1431
+ }
1432
+ formatSteps(steps) {
1433
+ const lines = ["## Core Workflow", ""];
1434
+ steps.forEach((step, i) => {
1435
+ lines.push(`### Step ${i + 1}: ${step.title}`);
1436
+ lines.push("");
1437
+ lines.push(step.description);
1438
+ if (step.substeps?.length) {
1439
+ lines.push("");
1440
+ step.substeps.forEach((sub, j) => {
1441
+ lines.push(`${j + 1}. ${sub}`);
1442
+ });
1443
+ }
1444
+ lines.push("");
1445
+ });
1446
+ return lines.join("\n").trim();
1447
+ }
1448
+ formatParameters(parameters) {
1449
+ const lines = [
1450
+ "## Parameters",
1451
+ "",
1452
+ "| Parameter | Type | Default | Description |",
1453
+ "|-----------|------|---------|-------------|"
1454
+ ];
1455
+ parameters.forEach((p) => {
1456
+ const def = p.default !== void 0 ? `\`${p.default}\`` : "-";
1457
+ lines.push(`| \`${p.name}\` | ${p.type} | ${def} | ${p.description} |`);
1458
+ });
1459
+ return lines.join("\n");
1460
+ }
1461
+ formatErrorHandling(errorHandling) {
1462
+ const lines = ["## Error Handling", ""];
1463
+ Object.entries(errorHandling).forEach(([error, solution]) => {
1464
+ lines.push(`- **${error}**: ${solution}`);
1465
+ });
1466
+ return lines.join("\n");
1467
+ }
1468
+ formatExamples(examples) {
1469
+ const lines = ["## Example Usage", ""];
1470
+ examples.forEach((ex) => {
1471
+ lines.push("```");
1472
+ lines.push(ex);
1473
+ lines.push("```");
1474
+ lines.push("");
1475
+ });
1476
+ return lines.join("\n").trim();
1477
+ }
1478
+ formatNotes(notes) {
1479
+ const lines = ["## Notes", ""];
1480
+ notes.forEach((n) => lines.push(`- ${n}`));
1481
+ return lines.join("\n");
1482
+ }
1483
+ };
1484
+
1485
+ // src/formatters/codex-formatter.ts
1486
+ import { stringify as stringify3 } from "yaml";
1487
+ var CodexFormatter = class {
1488
+ format(skill, _options = {}) {
1489
+ const parts = [];
1490
+ parts.push(this.formatFrontmatter(skill));
1491
+ parts.push("# Agent Instructions");
1492
+ parts.push("");
1493
+ parts.push(skill.overview);
1494
+ parts.push("");
1495
+ parts.push("## Capabilities");
1496
+ parts.push("");
1497
+ skill.triggers.forEach((t) => parts.push(`- ${t}`));
1498
+ parts.push("");
1499
+ parts.push("## Workflow");
1500
+ parts.push("");
1501
+ skill.steps.forEach((step, i) => {
1502
+ parts.push(`${i + 1}. **${step.title}**: ${step.description}`);
1503
+ if (step.substeps?.length) {
1504
+ step.substeps.forEach((sub) => {
1505
+ parts.push(` - ${sub}`);
1506
+ });
1507
+ }
1508
+ });
1509
+ if (skill.prerequisites.length > 0) {
1510
+ parts.push("");
1511
+ parts.push("## Prerequisites");
1512
+ parts.push("");
1513
+ skill.prerequisites.forEach((p) => parts.push(`- ${p}`));
1514
+ }
1515
+ if (Object.keys(skill.errorHandling).length > 0) {
1516
+ parts.push("");
1517
+ parts.push("## Error Handling");
1518
+ parts.push("");
1519
+ Object.entries(skill.errorHandling).forEach(([error, solution]) => {
1520
+ parts.push(`- ${error}: ${solution}`);
1521
+ });
1522
+ }
1523
+ return parts.join("\n");
1524
+ }
1525
+ formatFrontmatter(skill) {
1526
+ const data = {
1527
+ name: this.toKebabCase(skill.metadata.name),
1528
+ description: skill.metadata.description,
1529
+ model: "o4-mini"
1530
+ };
1531
+ return `---
1532
+ ${stringify3(data)}---`;
1533
+ }
1534
+ toKebabCase(str) {
1535
+ return str.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
1536
+ }
1537
+ };
1538
+
1539
+ // src/formatters/cursor-formatter.ts
1540
+ var CursorFormatter = class {
1541
+ format(skill, _options = {}) {
1542
+ const parts = [];
1543
+ parts.push(`# ${skill.metadata.name}`);
1544
+ parts.push(`# Generated by Skill Distill`);
1545
+ parts.push("");
1546
+ parts.push("## Overview");
1547
+ parts.push("");
1548
+ parts.push(skill.overview);
1549
+ parts.push("");
1550
+ parts.push("## Workflow Rules");
1551
+ parts.push("");
1552
+ skill.steps.forEach((step) => {
1553
+ parts.push(`### ${step.title}`);
1554
+ parts.push("");
1555
+ parts.push(step.description);
1556
+ if (step.substeps?.length) {
1557
+ parts.push("");
1558
+ step.substeps.forEach((sub) => {
1559
+ parts.push(`- ${sub}`);
1560
+ });
1561
+ }
1562
+ parts.push("");
1563
+ });
1564
+ if (skill.prerequisites.length > 0) {
1565
+ parts.push("## Constraints");
1566
+ parts.push("");
1567
+ skill.prerequisites.forEach((p) => {
1568
+ parts.push(`- ${p}`);
1569
+ });
1570
+ parts.push("");
1571
+ }
1572
+ if (Object.keys(skill.errorHandling).length > 0) {
1573
+ parts.push("## Error Handling");
1574
+ parts.push("");
1575
+ Object.entries(skill.errorHandling).forEach(([error, solution]) => {
1576
+ parts.push(`- When encountering "${error}": ${solution}`);
1577
+ });
1578
+ parts.push("");
1579
+ }
1580
+ if (skill.parameters.length > 0) {
1581
+ parts.push("## Parameters");
1582
+ parts.push("");
1583
+ parts.push("The following parameters can be customized:");
1584
+ parts.push("");
1585
+ skill.parameters.forEach((p) => {
1586
+ const def = p.default ? ` (default: ${p.default})` : "";
1587
+ parts.push(`- \`${p.name}\`: ${p.description}${def}`);
1588
+ });
1589
+ }
1590
+ return parts.join("\n");
1591
+ }
1592
+ };
1593
+
1594
+ // src/formatters/converter.ts
1595
+ var FormatConverter = class {
1596
+ claudeFormatter;
1597
+ codexFormatter;
1598
+ cursorFormatter;
1599
+ constructor() {
1600
+ this.claudeFormatter = new ClaudeFormatter();
1601
+ this.codexFormatter = new CodexFormatter();
1602
+ this.cursorFormatter = new CursorFormatter();
1603
+ }
1604
+ convert(skill, format, options) {
1605
+ switch (format) {
1606
+ case "claude":
1607
+ return this.toClaudeFormat(skill, options);
1608
+ case "codex":
1609
+ return this.toCodexFormat(skill, options);
1610
+ case "cursor":
1611
+ return this.toCursorFormat(skill, options);
1612
+ default:
1613
+ throw new Error(`Unsupported format: ${format}`);
1614
+ }
1615
+ }
1616
+ convertAll(skill, options) {
1617
+ const results = /* @__PURE__ */ new Map();
1618
+ results.set("claude", this.toClaudeFormat(skill, options));
1619
+ results.set("codex", this.toCodexFormat(skill, options));
1620
+ results.set("cursor", this.toCursorFormat(skill, options));
1621
+ return results;
1622
+ }
1623
+ toClaudeFormat(skill, options) {
1624
+ return this.claudeFormatter.format(skill, options);
1625
+ }
1626
+ toCodexFormat(skill, options) {
1627
+ return this.codexFormatter.format(skill, options);
1628
+ }
1629
+ toCursorFormat(skill, options) {
1630
+ return this.cursorFormatter.format(skill, options);
1631
+ }
1632
+ };
1633
+
1634
+ // src/formatters/validator.ts
1635
+ var FormatValidator = class {
1636
+ validateClaude(content) {
1637
+ const errors = [];
1638
+ const warnings = [];
1639
+ if (!content.startsWith("---")) {
1640
+ errors.push("Missing YAML frontmatter");
1641
+ }
1642
+ if (!content.includes("name:")) {
1643
+ errors.push("Missing name in frontmatter");
1644
+ }
1645
+ if (!content.includes("description:")) {
1646
+ errors.push("Missing description in frontmatter");
1647
+ }
1648
+ if (!content.includes("## Overview")) {
1649
+ errors.push("Missing Overview section");
1650
+ }
1651
+ if (!content.includes("## Core Workflow") && !content.includes("## Steps")) {
1652
+ errors.push("Missing Core Workflow section");
1653
+ }
1654
+ if (/\bYou should\b/i.test(content)) {
1655
+ warnings.push('Avoid "You should" - use imperative form');
1656
+ }
1657
+ return {
1658
+ valid: errors.length === 0,
1659
+ format: "claude",
1660
+ errors,
1661
+ warnings
1662
+ };
1663
+ }
1664
+ validateCodex(content) {
1665
+ const errors = [];
1666
+ const warnings = [];
1667
+ if (!content.startsWith("---")) {
1668
+ errors.push("Missing YAML frontmatter");
1669
+ }
1670
+ if (!content.includes("model:")) {
1671
+ warnings.push("Missing model specification");
1672
+ }
1673
+ return {
1674
+ valid: errors.length === 0,
1675
+ format: "codex",
1676
+ errors,
1677
+ warnings
1678
+ };
1679
+ }
1680
+ validateCursor(content) {
1681
+ const errors = [];
1682
+ const warnings = [];
1683
+ if (content.trim().length < 100) {
1684
+ warnings.push("Content seems too short");
1685
+ }
1686
+ return {
1687
+ valid: errors.length === 0,
1688
+ format: "cursor",
1689
+ errors,
1690
+ warnings
1691
+ };
1692
+ }
1693
+ };
1694
+
1695
+ // src/installers/claude.ts
1696
+ import { promises as fs5 } from "fs";
1697
+ import { join as join7 } from "path";
1698
+ import { homedir as homedir5 } from "os";
1699
+
1700
+ // src/installers/base.ts
1701
+ var AgentInstaller = class {
1702
+ basePath;
1703
+ constructor(basePath) {
1704
+ this.basePath = basePath ?? this.getDefaultBasePath();
1705
+ }
1706
+ async verify(skillName) {
1707
+ const installed = await this.list();
1708
+ return installed.some((s) => s.name === skillName);
1709
+ }
1710
+ };
1711
+
1712
+ // src/installers/claude.ts
1713
+ var ClaudeCodeInstaller = class extends AgentInstaller {
1714
+ generator;
1715
+ constructor(basePath) {
1716
+ super(basePath);
1717
+ this.generator = new SkillGenerator();
1718
+ }
1719
+ getDefaultBasePath() {
1720
+ return join7(homedir5(), ".claude", "plugins", "local", "skills");
1721
+ }
1722
+ getPlatformName() {
1723
+ return "Claude Code";
1724
+ }
1725
+ async install(skill, options = {}) {
1726
+ const { overwrite = false, backup = true, verify = true } = options;
1727
+ const skillDirName = this.toKebabCase(skill.metadata.name);
1728
+ const skillPath = join7(this.basePath, skillDirName);
1729
+ if (await fileExists(skillPath)) {
1730
+ if (!overwrite) {
1731
+ return {
1732
+ success: false,
1733
+ path: skillPath,
1734
+ message: `Skill already exists: ${skillPath}. Use --overwrite to replace.`
1735
+ };
1736
+ }
1737
+ if (backup) {
1738
+ const backupPath = `${skillPath}.backup.${Date.now()}`;
1739
+ await fs5.rename(skillPath, backupPath);
1740
+ }
1741
+ }
1742
+ await ensureDir(this.basePath);
1743
+ const result = await this.generator.generate(skill, {
1744
+ outputDir: this.basePath,
1745
+ overwrite: true
1746
+ });
1747
+ if (verify) {
1748
+ const verified = await this.verifyInstallation(skillPath);
1749
+ if (!verified) {
1750
+ return {
1751
+ success: false,
1752
+ path: skillPath,
1753
+ message: "Installation verification failed"
1754
+ };
1755
+ }
1756
+ }
1757
+ return {
1758
+ success: true,
1759
+ path: result.skillPath,
1760
+ message: `Successfully installed to ${this.getPlatformName()}`
1761
+ };
1762
+ }
1763
+ async uninstall(skillName) {
1764
+ const skillPath = join7(this.basePath, this.toKebabCase(skillName));
1765
+ if (!await fileExists(skillPath)) {
1766
+ throw new Error(`Skill not found: ${skillName}`);
1767
+ }
1768
+ await fs5.rm(skillPath, { recursive: true });
1769
+ }
1770
+ async list() {
1771
+ const skills = [];
1772
+ try {
1773
+ const entries = await fs5.readdir(this.basePath, { withFileTypes: true });
1774
+ for (const entry of entries) {
1775
+ if (!entry.isDirectory()) continue;
1776
+ const skillMdPath = join7(this.basePath, entry.name, "SKILL.md");
1777
+ if (await fileExists(skillMdPath)) {
1778
+ const content = await fs5.readFile(skillMdPath, "utf-8");
1779
+ const metadata = this.parseMetadata(content);
1780
+ const stat = await fs5.stat(skillMdPath);
1781
+ skills.push({
1782
+ name: metadata.name ?? entry.name,
1783
+ path: join7(this.basePath, entry.name),
1784
+ version: metadata.version ?? "0.0.0",
1785
+ installedAt: stat.mtime.toISOString()
1786
+ });
1787
+ }
1788
+ }
1789
+ } catch {
1790
+ }
1791
+ return skills;
1792
+ }
1793
+ async verifyInstallation(skillPath) {
1794
+ const skillMd = join7(skillPath, "SKILL.md");
1795
+ if (!await fileExists(skillMd)) {
1796
+ return false;
1797
+ }
1798
+ const content = await fs5.readFile(skillMd, "utf-8");
1799
+ if (!content.startsWith("---")) {
1800
+ return false;
1801
+ }
1802
+ const metadata = this.parseMetadata(content);
1803
+ return !!(metadata.name && metadata.description);
1804
+ }
1805
+ parseMetadata(content) {
1806
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
1807
+ if (!match) return {};
1808
+ const yaml = match[1];
1809
+ const metadata = {};
1810
+ yaml.split("\n").forEach((line) => {
1811
+ const colonIndex = line.indexOf(":");
1812
+ if (colonIndex > 0) {
1813
+ const key = line.slice(0, colonIndex).trim();
1814
+ const value = line.slice(colonIndex + 1).trim();
1815
+ metadata[key] = value;
1816
+ }
1817
+ });
1818
+ return metadata;
1819
+ }
1820
+ toKebabCase(str) {
1821
+ return str.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
1822
+ }
1823
+ };
1824
+
1825
+ // src/installers/codex.ts
1826
+ import { promises as fs6 } from "fs";
1827
+ import { join as join8 } from "path";
1828
+ import { homedir as homedir6 } from "os";
1829
+ var CodexInstaller = class extends AgentInstaller {
1830
+ converter;
1831
+ constructor(basePath) {
1832
+ super(basePath);
1833
+ this.converter = new FormatConverter();
1834
+ }
1835
+ getDefaultBasePath() {
1836
+ return join8(homedir6(), ".codex", "agents");
1837
+ }
1838
+ getPlatformName() {
1839
+ return "Codex CLI";
1840
+ }
1841
+ async install(skill, options = {}) {
1842
+ const { overwrite = false } = options;
1843
+ const agentName = this.toKebabCase(skill.metadata.name);
1844
+ const agentPath = join8(this.basePath, `${agentName}.md`);
1845
+ if (await fileExists(agentPath) && !overwrite) {
1846
+ return {
1847
+ success: false,
1848
+ path: agentPath,
1849
+ message: `Agent already exists: ${agentPath}`
1850
+ };
1851
+ }
1852
+ await ensureDir(this.basePath);
1853
+ const content = this.converter.toCodexFormat(skill);
1854
+ await fs6.writeFile(agentPath, content, "utf-8");
1855
+ return {
1856
+ success: true,
1857
+ path: agentPath,
1858
+ message: `Successfully installed to ${this.getPlatformName()}`
1859
+ };
1860
+ }
1861
+ async uninstall(skillName) {
1862
+ const agentPath = join8(this.basePath, `${this.toKebabCase(skillName)}.md`);
1863
+ if (!await fileExists(agentPath)) {
1864
+ throw new Error(`Agent not found: ${skillName}`);
1865
+ }
1866
+ await fs6.unlink(agentPath);
1867
+ }
1868
+ async list() {
1869
+ const skills = [];
1870
+ try {
1871
+ const files = await fs6.readdir(this.basePath);
1872
+ for (const file of files) {
1873
+ if (!file.endsWith(".md")) continue;
1874
+ const agentPath = join8(this.basePath, file);
1875
+ const stat = await fs6.stat(agentPath);
1876
+ skills.push({
1877
+ name: file.replace(".md", ""),
1878
+ path: agentPath,
1879
+ version: "0.0.0",
1880
+ installedAt: stat.mtime.toISOString()
1881
+ });
1882
+ }
1883
+ } catch {
1884
+ }
1885
+ return skills;
1886
+ }
1887
+ toKebabCase(str) {
1888
+ return str.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
1889
+ }
1890
+ };
1891
+
1892
+ // src/installers/cursor.ts
1893
+ import { promises as fs7 } from "fs";
1894
+ import { join as join9 } from "path";
1895
+ var CursorInstaller = class extends AgentInstaller {
1896
+ converter;
1897
+ projectPath;
1898
+ constructor(projectPath) {
1899
+ super(projectPath);
1900
+ this.projectPath = projectPath;
1901
+ this.converter = new FormatConverter();
1902
+ }
1903
+ getDefaultBasePath() {
1904
+ return this.projectPath;
1905
+ }
1906
+ getPlatformName() {
1907
+ return "Cursor";
1908
+ }
1909
+ async install(skill, options = {}) {
1910
+ const { overwrite = false, backup = true } = options;
1911
+ const rulesPath = join9(this.basePath, ".cursorrules");
1912
+ if (await fileExists(rulesPath)) {
1913
+ if (!overwrite) {
1914
+ return {
1915
+ success: false,
1916
+ path: rulesPath,
1917
+ message: ".cursorrules already exists. Use --overwrite to replace."
1918
+ };
1919
+ }
1920
+ if (backup) {
1921
+ const backupPath = `${rulesPath}.backup.${Date.now()}`;
1922
+ await fs7.copyFile(rulesPath, backupPath);
1923
+ }
1924
+ }
1925
+ const content = this.converter.toCursorFormat(skill);
1926
+ await fs7.writeFile(rulesPath, content, "utf-8");
1927
+ return {
1928
+ success: true,
1929
+ path: rulesPath,
1930
+ message: `Successfully installed to ${this.getPlatformName()}`
1931
+ };
1932
+ }
1933
+ async uninstall(_skillName) {
1934
+ const rulesPath = join9(this.basePath, ".cursorrules");
1935
+ if (!await fileExists(rulesPath)) {
1936
+ throw new Error(".cursorrules not found");
1937
+ }
1938
+ await fs7.unlink(rulesPath);
1939
+ }
1940
+ async list() {
1941
+ const rulesPath = join9(this.basePath, ".cursorrules");
1942
+ if (!await fileExists(rulesPath)) {
1943
+ return [];
1944
+ }
1945
+ const stat = await fs7.stat(rulesPath);
1946
+ return [
1947
+ {
1948
+ name: "cursorrules",
1949
+ path: rulesPath,
1950
+ version: "0.0.0",
1951
+ installedAt: stat.mtime.toISOString()
1952
+ }
1953
+ ];
1954
+ }
1955
+ };
1956
+
1957
+ // src/installers/index.ts
1958
+ function createInstaller(platform, options = {}) {
1959
+ switch (platform) {
1960
+ case "claude":
1961
+ return new ClaudeCodeInstaller(options.basePath);
1962
+ case "codex":
1963
+ return new CodexInstaller(options.basePath);
1964
+ case "cursor":
1965
+ if (!options.projectPath) {
1966
+ throw new Error("Cursor installer requires projectPath");
1967
+ }
1968
+ return new CursorInstaller(options.projectPath);
1969
+ default:
1970
+ throw new Error(`Unsupported platform: ${platform}`);
1971
+ }
1972
+ }
1973
+
1974
+ // src/index.ts
1975
+ var VERSION = "0.1.0";
1976
+ export {
1977
+ AgentInstaller,
1978
+ ClaudeCodeInstaller,
1979
+ ClaudeFormatter,
1980
+ ClaudeSessionLoader,
1981
+ CodexFormatter,
1982
+ CodexInstaller,
1983
+ CodexSessionLoader,
1984
+ ConversationPreprocessor,
1985
+ CursorFormatter,
1986
+ CursorInstaller,
1987
+ CursorSessionLoader,
1988
+ DirectoryBuilder,
1989
+ DistillOptionsSchema,
1990
+ Distiller,
1991
+ FailedAttemptFilter,
1992
+ FormatConverter,
1993
+ FormatValidator,
1994
+ FrontmatterBuilder,
1995
+ LLMEngine,
1996
+ MarkdownRenderer,
1997
+ ParameterExtractor,
1998
+ PlatformSchema,
1999
+ PromptClassifier,
2000
+ PromptFuser,
2001
+ PromptHandler,
2002
+ SessionIdSchema,
2003
+ SessionLoader,
2004
+ SkillGenerator,
2005
+ TemplateValidator,
2006
+ VERSION,
2007
+ collectPromptsInteractively,
2008
+ createInstaller,
2009
+ createSessionLoader,
2010
+ ensureDir,
2011
+ expandHome,
2012
+ fileExists,
2013
+ logger,
2014
+ readJson,
2015
+ selectPromptsFromSuggestions,
2016
+ validatePlatform,
2017
+ validateSessionId,
2018
+ writeJson
2019
+ };
2020
+ //# sourceMappingURL=index.js.map