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/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1940 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +631 -0
- package/dist/index.js +2020 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
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
|