skill-distill 1.0.1 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -145,13 +145,23 @@ declare abstract class SessionLoader {
145
145
  }
146
146
 
147
147
  declare class ClaudeSessionLoader extends SessionLoader {
148
+ private transcriptsPath;
149
+ private projectsPath;
148
150
  constructor(options?: SessionLoaderOptions);
149
151
  getDefaultBasePath(): string;
150
152
  listSessions(): Promise<SessionInfo[]>;
153
+ private listTranscriptSessions;
154
+ private listProjectSessions;
151
155
  getSession(id: string): Promise<Session>;
156
+ private findProjectSessionFile;
152
157
  private readTranscriptFile;
153
- private transformSession;
154
- private extractSummary;
158
+ private readProjectFile;
159
+ private transformTranscriptSession;
160
+ private transformProjectSession;
161
+ private extractMessageContent;
162
+ private decodeProjectPath;
163
+ private extractTranscriptSummary;
164
+ private extractProjectSummary;
155
165
  }
156
166
 
157
167
  declare class CodexSessionLoader extends SessionLoader {
package/dist/index.js CHANGED
@@ -91,54 +91,144 @@ var SessionLoader = class {
91
91
 
92
92
  // src/loaders/claude.ts
93
93
  var ClaudeSessionLoader = class extends SessionLoader {
94
+ transcriptsPath;
95
+ projectsPath;
94
96
  constructor(options = {}) {
95
97
  super(options);
98
+ const claudeDir = join2(homedir2(), ".claude");
99
+ this.transcriptsPath = join2(claudeDir, "transcripts");
100
+ this.projectsPath = join2(claudeDir, "projects");
96
101
  }
97
102
  getDefaultBasePath() {
98
- return join2(homedir2(), ".claude", "transcripts");
103
+ return join2(homedir2(), ".claude");
99
104
  }
100
105
  async listSessions() {
106
+ const sessions = [];
107
+ const transcriptSessions = await this.listTranscriptSessions();
108
+ const projectSessions = await this.listProjectSessions();
109
+ sessions.push(...transcriptSessions, ...projectSessions);
110
+ return sessions.sort(
111
+ (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
112
+ );
113
+ }
114
+ async listTranscriptSessions() {
101
115
  const sessions = [];
102
116
  try {
103
- const files = await fs2.readdir(this.basePath);
117
+ const files = await fs2.readdir(this.transcriptsPath);
104
118
  for (const file of files) {
105
119
  if (!file.endsWith(".jsonl") || !file.startsWith("ses_")) continue;
106
- const filePath = join2(this.basePath, file);
107
- const stat = await fs2.stat(filePath);
120
+ const filePath = join2(this.transcriptsPath, file);
108
121
  const sessionId = file.replace(".jsonl", "");
109
- const entries = await this.readTranscriptFile(filePath);
110
- if (entries.length === 0) continue;
111
- const userMessages = entries.filter((e) => e.type === "user");
112
- const firstEntry = entries[0];
113
- const lastEntry = entries[entries.length - 1];
114
- sessions.push({
115
- id: sessionId,
116
- projectPath: "",
117
- startTime: firstEntry.timestamp,
118
- endTime: lastEntry.timestamp,
119
- messageCount: userMessages.length,
120
- summary: this.extractSummary(entries)
121
- });
122
+ try {
123
+ const entries = await this.readTranscriptFile(filePath);
124
+ if (entries.length === 0) continue;
125
+ const userMessages = entries.filter((e) => e.type === "user");
126
+ const firstEntry = entries[0];
127
+ const lastEntry = entries[entries.length - 1];
128
+ sessions.push({
129
+ id: sessionId,
130
+ projectPath: "[transcripts]",
131
+ startTime: firstEntry.timestamp,
132
+ endTime: lastEntry.timestamp,
133
+ messageCount: userMessages.length,
134
+ summary: this.extractTranscriptSummary(entries)
135
+ });
136
+ } catch {
137
+ continue;
138
+ }
122
139
  }
123
- } catch (error) {
124
- throw new Error(`Failed to list sessions: ${error}`);
140
+ } catch {
125
141
  }
126
- return sessions.sort(
127
- (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
128
- );
142
+ return sessions;
143
+ }
144
+ async listProjectSessions() {
145
+ const sessions = [];
146
+ const sessionMap = /* @__PURE__ */ new Map();
147
+ try {
148
+ const projectDirs = await fs2.readdir(this.projectsPath);
149
+ for (const projectDir of projectDirs) {
150
+ if (projectDir.startsWith(".")) continue;
151
+ const projectPath = join2(this.projectsPath, projectDir);
152
+ const stat = await fs2.stat(projectPath);
153
+ if (!stat.isDirectory()) continue;
154
+ const files = await fs2.readdir(projectPath);
155
+ for (const file of files) {
156
+ if (!file.endsWith(".jsonl")) continue;
157
+ if (file.startsWith("agent-")) continue;
158
+ const filePath = join2(projectPath, file);
159
+ const sessionId = file.replace(".jsonl", "");
160
+ sessionMap.set(sessionId, {
161
+ id: sessionId,
162
+ filePath,
163
+ projectPath: this.decodeProjectPath(projectDir),
164
+ source: "projects"
165
+ });
166
+ }
167
+ }
168
+ for (const [sessionId, source] of sessionMap) {
169
+ try {
170
+ const entries = await this.readProjectFile(source.filePath);
171
+ if (entries.length === 0) continue;
172
+ const userMessages = entries.filter(
173
+ (e) => e.type === "user" && e.message?.role === "user"
174
+ );
175
+ const timestamps = entries.filter((e) => e.timestamp).map((e) => e.timestamp);
176
+ if (timestamps.length === 0) continue;
177
+ sessions.push({
178
+ id: sessionId,
179
+ projectPath: source.projectPath,
180
+ startTime: timestamps[0],
181
+ endTime: timestamps[timestamps.length - 1],
182
+ messageCount: userMessages.length,
183
+ summary: this.extractProjectSummary(entries)
184
+ });
185
+ } catch {
186
+ continue;
187
+ }
188
+ }
189
+ } catch {
190
+ }
191
+ return sessions;
129
192
  }
130
193
  async getSession(id) {
131
- const filePath = join2(this.basePath, `${id}.jsonl`);
194
+ const transcriptPath = join2(this.transcriptsPath, `${id}.jsonl`);
132
195
  try {
133
- await fs2.access(filePath);
196
+ await fs2.access(transcriptPath);
197
+ const entries = await this.readTranscriptFile(transcriptPath);
198
+ if (entries.length > 0) {
199
+ return this.transformTranscriptSession(id, entries);
200
+ }
134
201
  } catch {
135
- throw new Error(`Session not found: ${id}`);
136
202
  }
137
- const entries = await this.readTranscriptFile(filePath);
138
- if (entries.length === 0) {
139
- throw new Error(`Empty session: ${id}`);
203
+ const projectFile = await this.findProjectSessionFile(id);
204
+ if (projectFile) {
205
+ const entries = await this.readProjectFile(projectFile.filePath);
206
+ return this.transformProjectSession(id, entries, projectFile.projectPath);
140
207
  }
141
- return this.transformSession(id, entries);
208
+ throw new Error(`Session not found: ${id}`);
209
+ }
210
+ async findProjectSessionFile(sessionId) {
211
+ try {
212
+ const projectDirs = await fs2.readdir(this.projectsPath);
213
+ for (const projectDir of projectDirs) {
214
+ if (projectDir.startsWith(".")) continue;
215
+ const projectPath = join2(this.projectsPath, projectDir);
216
+ const stat = await fs2.stat(projectPath);
217
+ if (!stat.isDirectory()) continue;
218
+ const sessionFile = join2(projectPath, `${sessionId}.jsonl`);
219
+ try {
220
+ await fs2.access(sessionFile);
221
+ return {
222
+ filePath: sessionFile,
223
+ projectPath: this.decodeProjectPath(projectDir)
224
+ };
225
+ } catch {
226
+ continue;
227
+ }
228
+ }
229
+ } catch {
230
+ }
231
+ return null;
142
232
  }
143
233
  async readTranscriptFile(path) {
144
234
  try {
@@ -149,7 +239,16 @@ var ClaudeSessionLoader = class extends SessionLoader {
149
239
  return [];
150
240
  }
151
241
  }
152
- transformSession(id, entries) {
242
+ async readProjectFile(path) {
243
+ try {
244
+ const content = await fs2.readFile(path, "utf-8");
245
+ const lines = content.trim().split("\n").filter(Boolean);
246
+ return lines.map((line) => JSON.parse(line));
247
+ } catch {
248
+ return [];
249
+ }
250
+ }
251
+ transformTranscriptSession(id, entries) {
153
252
  const messages = [];
154
253
  let currentAssistantContent = "";
155
254
  let currentToolCalls = [];
@@ -160,7 +259,7 @@ var ClaudeSessionLoader = class extends SessionLoader {
160
259
  if (currentAssistantContent || currentToolCalls?.length) {
161
260
  messages.push({
162
261
  role: "assistant",
163
- content: currentAssistantContent,
262
+ content: currentAssistantContent.trim(),
164
263
  timestamp: lastTimestamp,
165
264
  toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0,
166
265
  toolResults: currentToolResults.length > 0 ? currentToolResults : void 0
@@ -205,19 +304,64 @@ var ClaudeSessionLoader = class extends SessionLoader {
205
304
  const lastEntry = entries[entries.length - 1];
206
305
  return {
207
306
  id,
208
- projectPath: "",
307
+ projectPath: "[transcripts]",
209
308
  startTime: firstEntry.timestamp,
210
309
  endTime: lastEntry.timestamp,
211
310
  messages
212
311
  };
213
312
  }
214
- extractSummary(entries) {
313
+ transformProjectSession(id, entries, projectPath) {
314
+ const messages = [];
315
+ for (const entry of entries) {
316
+ if (entry.type === "file-history-snapshot") continue;
317
+ if (entry.message) {
318
+ const content = this.extractMessageContent(entry.message.content);
319
+ if (content) {
320
+ messages.push({
321
+ role: entry.message.role === "user" ? "user" : "assistant",
322
+ content,
323
+ timestamp: entry.timestamp
324
+ });
325
+ }
326
+ }
327
+ }
328
+ const timestamps = entries.filter((e) => e.timestamp).map((e) => e.timestamp);
329
+ return {
330
+ id,
331
+ projectPath,
332
+ startTime: timestamps[0] ?? "",
333
+ endTime: timestamps[timestamps.length - 1] ?? "",
334
+ messages
335
+ };
336
+ }
337
+ extractMessageContent(content) {
338
+ if (typeof content === "string") {
339
+ return content;
340
+ }
341
+ if (Array.isArray(content)) {
342
+ return content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
343
+ }
344
+ return "";
345
+ }
346
+ decodeProjectPath(encoded) {
347
+ return encoded.replace(/-/g, "/").replace(/^\//, "");
348
+ }
349
+ extractTranscriptSummary(entries) {
215
350
  const firstUserEntry = entries.find((e) => e.type === "user");
216
351
  if (!firstUserEntry?.content) return "";
217
352
  const content = firstUserEntry.content;
218
353
  const firstLine = content.split("\n")[0];
219
354
  return firstLine.length > 80 ? firstLine.slice(0, 80) + "..." : firstLine;
220
355
  }
356
+ extractProjectSummary(entries) {
357
+ const firstUserEntry = entries.find(
358
+ (e) => e.type === "user" && e.message?.role === "user"
359
+ );
360
+ if (!firstUserEntry?.message) return "";
361
+ const content = this.extractMessageContent(firstUserEntry.message.content);
362
+ const firstLine = content.split("\n")[0];
363
+ return firstLine.length > 80 ? firstLine.slice(0, 80) + "..." : firstLine;
364
+ }
221
365
  };
222
366
 
223
367
  // src/loaders/codex.ts
@@ -462,7 +606,41 @@ ${userPrompts.map((p) => `- ${p}`).join("\n")}` : "";
462
606
  qualityEnhancement(JSON.stringify(skill, null, 2)),
463
607
  SYSTEM_SKILL_WRITER
464
608
  );
465
- return this.parseJson(response, z2.any());
609
+ try {
610
+ const enhanced = this.parseJson(response, z2.any());
611
+ const mergedSteps = (enhanced.steps ?? skill.steps ?? []).map((step) => {
612
+ if (typeof step === "object" && step !== null) {
613
+ const s = step;
614
+ return {
615
+ title: String(s.title ?? s.name ?? "Step"),
616
+ description: String(s.description ?? s.content ?? ""),
617
+ substeps: Array.isArray(s.substeps) ? s.substeps.map(String) : void 0
618
+ };
619
+ }
620
+ return { title: "Step", description: String(step) };
621
+ });
622
+ const mergedExamples = (enhanced.examples ?? skill.examples ?? []).map((ex) => {
623
+ if (typeof ex === "string") return ex;
624
+ if (typeof ex === "object" && ex !== null) {
625
+ const e = ex;
626
+ return String(e.text ?? e.example ?? e.content ?? JSON.stringify(ex));
627
+ }
628
+ return String(ex);
629
+ });
630
+ return {
631
+ metadata: enhanced.metadata ?? skill.metadata ?? { name: "Untitled", description: "", version: "1.0.0" },
632
+ overview: enhanced.overview ?? skill.overview ?? "",
633
+ triggers: enhanced.triggers ?? skill.triggers ?? [],
634
+ prerequisites: enhanced.prerequisites ?? skill.prerequisites ?? [],
635
+ steps: mergedSteps,
636
+ parameters: enhanced.parameters ?? skill.parameters ?? [],
637
+ errorHandling: enhanced.errorHandling ?? skill.errorHandling ?? {},
638
+ examples: mergedExamples,
639
+ notes: enhanced.notes ?? skill.notes ?? []
640
+ };
641
+ } catch {
642
+ return skill;
643
+ }
466
644
  }
467
645
  async generateDescription(intent) {
468
646
  const response = await this.chat(