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