skill-distill 1.0.0 → 1.0.3

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,14 +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>;
152
- private readSessionFile;
153
- private transformSession;
154
- private transformMessage;
155
- private extractSummary;
156
+ private findProjectSessionFile;
157
+ private readTranscriptFile;
158
+ private readProjectFile;
159
+ private transformTranscriptSession;
160
+ private transformProjectSession;
161
+ private extractMessageContent;
162
+ private decodeProjectPath;
163
+ private extractTranscriptSummary;
164
+ private extractProjectSummary;
156
165
  }
157
166
 
158
167
  declare class CodexSessionLoader extends SessionLoader {
package/dist/index.js CHANGED
@@ -91,98 +91,276 @@ 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", "projects");
103
+ return join2(homedir2(), ".claude");
99
104
  }
100
105
  async listSessions() {
101
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() {
115
+ const sessions = [];
116
+ try {
117
+ const files = await fs2.readdir(this.transcriptsPath);
118
+ for (const file of files) {
119
+ if (!file.endsWith(".jsonl") || !file.startsWith("ses_")) continue;
120
+ const filePath = join2(this.transcriptsPath, file);
121
+ const sessionId = file.replace(".jsonl", "");
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
+ }
139
+ }
140
+ } catch {
141
+ }
142
+ return sessions;
143
+ }
144
+ async listProjectSessions() {
145
+ const sessions = [];
146
+ const sessionMap = /* @__PURE__ */ new Map();
102
147
  try {
103
- const projectDirs = await fs2.readdir(this.basePath);
148
+ const projectDirs = await fs2.readdir(this.projectsPath);
104
149
  for (const projectDir of projectDirs) {
105
- const projectPath = join2(this.basePath, projectDir);
150
+ if (projectDir.startsWith(".")) continue;
151
+ const projectPath = join2(this.projectsPath, projectDir);
106
152
  const stat = await fs2.stat(projectPath);
107
153
  if (!stat.isDirectory()) continue;
108
- const sessionsPath = join2(projectPath, "sessions");
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) {
109
169
  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
- }
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
+ });
126
185
  } catch {
127
186
  continue;
128
187
  }
129
188
  }
130
- } catch (error) {
131
- throw new Error(`Failed to list sessions: ${error}`);
189
+ } catch {
132
190
  }
133
191
  return sessions;
134
192
  }
135
193
  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}`);
194
+ const transcriptPath = join2(this.transcriptsPath, `${id}.jsonl`);
195
+ try {
196
+ await fs2.access(transcriptPath);
197
+ const entries = await this.readTranscriptFile(transcriptPath);
198
+ if (entries.length > 0) {
199
+ return this.transformTranscriptSession(id, entries);
200
+ }
201
+ } catch {
202
+ }
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);
207
+ }
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 {
140
230
  }
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}`);
231
+ return null;
232
+ }
233
+ async readTranscriptFile(path) {
234
+ try {
235
+ const content = await fs2.readFile(path, "utf-8");
236
+ const lines = content.trim().split("\n").filter(Boolean);
237
+ return lines.map((line) => JSON.parse(line));
238
+ } catch {
239
+ return [];
145
240
  }
146
- return this.transformSession(data, sessionInfo.projectPath);
147
241
  }
148
- async readSessionFile(path) {
242
+ async readProjectFile(path) {
149
243
  try {
150
244
  const content = await fs2.readFile(path, "utf-8");
151
- return JSON.parse(content);
245
+ const lines = content.trim().split("\n").filter(Boolean);
246
+ return lines.map((line) => JSON.parse(line));
152
247
  } catch {
153
- return null;
248
+ return [];
154
249
  }
155
250
  }
156
- transformSession(data, projectPath) {
251
+ transformTranscriptSession(id, entries) {
252
+ const messages = [];
253
+ let currentAssistantContent = "";
254
+ let currentToolCalls = [];
255
+ let currentToolResults = [];
256
+ let lastTimestamp = "";
257
+ for (const entry of entries) {
258
+ if (entry.type === "user") {
259
+ if (currentAssistantContent || currentToolCalls?.length) {
260
+ messages.push({
261
+ role: "assistant",
262
+ content: currentAssistantContent.trim(),
263
+ timestamp: lastTimestamp,
264
+ toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0,
265
+ toolResults: currentToolResults.length > 0 ? currentToolResults : void 0
266
+ });
267
+ currentAssistantContent = "";
268
+ currentToolCalls = [];
269
+ currentToolResults = [];
270
+ }
271
+ messages.push({
272
+ role: "user",
273
+ content: entry.content ?? "",
274
+ timestamp: entry.timestamp
275
+ });
276
+ } else if (entry.type === "assistant") {
277
+ currentAssistantContent += (entry.content ?? "") + "\n";
278
+ lastTimestamp = entry.timestamp;
279
+ } else if (entry.type === "tool_use") {
280
+ currentToolCalls.push({
281
+ name: entry.tool_name ?? "unknown",
282
+ input: entry.tool_input ?? {}
283
+ });
284
+ lastTimestamp = entry.timestamp;
285
+ } else if (entry.type === "tool_result") {
286
+ currentToolResults.push({
287
+ name: entry.tool_name ?? "unknown",
288
+ output: entry.tool_output?.preview ?? "",
289
+ success: true
290
+ });
291
+ lastTimestamp = entry.timestamp;
292
+ }
293
+ }
294
+ if (currentAssistantContent || currentToolCalls?.length) {
295
+ messages.push({
296
+ role: "assistant",
297
+ content: currentAssistantContent.trim(),
298
+ timestamp: lastTimestamp,
299
+ toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0,
300
+ toolResults: currentToolResults.length > 0 ? currentToolResults : void 0
301
+ });
302
+ }
303
+ const firstEntry = entries[0];
304
+ const lastEntry = entries[entries.length - 1];
157
305
  return {
158
- id: data.id,
159
- projectPath,
160
- startTime: data.createdAt,
161
- endTime: data.updatedAt,
162
- messages: data.messages.map((msg) => this.transformMessage(msg))
306
+ id,
307
+ projectPath: "[transcripts]",
308
+ startTime: firstEntry.timestamp,
309
+ endTime: lastEntry.timestamp,
310
+ messages
163
311
  };
164
312
  }
165
- transformMessage(msg) {
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);
166
329
  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
- }))
330
+ id,
331
+ projectPath,
332
+ startTime: timestamps[0] ?? "",
333
+ endTime: timestamps[timestamps.length - 1] ?? "",
334
+ messages
179
335
  };
180
336
  }
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;
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) {
350
+ const firstUserEntry = entries.find((e) => e.type === "user");
351
+ if (!firstUserEntry?.content) return "";
352
+ const content = firstUserEntry.content;
353
+ const firstLine = content.split("\n")[0];
354
+ return firstLine.length > 80 ? firstLine.slice(0, 80) + "..." : firstLine;
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;
186
364
  }
187
365
  };
188
366