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/cli.js CHANGED
@@ -42,98 +42,276 @@ var SessionLoader = class {
42
42
 
43
43
  // src/loaders/claude.ts
44
44
  var ClaudeSessionLoader = class extends SessionLoader {
45
+ transcriptsPath;
46
+ projectsPath;
45
47
  constructor(options = {}) {
46
48
  super(options);
49
+ const claudeDir = join(homedir(), ".claude");
50
+ this.transcriptsPath = join(claudeDir, "transcripts");
51
+ this.projectsPath = join(claudeDir, "projects");
47
52
  }
48
53
  getDefaultBasePath() {
49
- return join(homedir(), ".claude", "projects");
54
+ return join(homedir(), ".claude");
50
55
  }
51
56
  async listSessions() {
52
57
  const sessions = [];
58
+ const transcriptSessions = await this.listTranscriptSessions();
59
+ const projectSessions = await this.listProjectSessions();
60
+ sessions.push(...transcriptSessions, ...projectSessions);
61
+ return sessions.sort(
62
+ (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
63
+ );
64
+ }
65
+ async listTranscriptSessions() {
66
+ const sessions = [];
67
+ try {
68
+ const files = await fs.readdir(this.transcriptsPath);
69
+ for (const file of files) {
70
+ if (!file.endsWith(".jsonl") || !file.startsWith("ses_")) continue;
71
+ const filePath = join(this.transcriptsPath, file);
72
+ const sessionId = file.replace(".jsonl", "");
73
+ try {
74
+ const entries = await this.readTranscriptFile(filePath);
75
+ if (entries.length === 0) continue;
76
+ const userMessages = entries.filter((e) => e.type === "user");
77
+ const firstEntry = entries[0];
78
+ const lastEntry = entries[entries.length - 1];
79
+ sessions.push({
80
+ id: sessionId,
81
+ projectPath: "[transcripts]",
82
+ startTime: firstEntry.timestamp,
83
+ endTime: lastEntry.timestamp,
84
+ messageCount: userMessages.length,
85
+ summary: this.extractTranscriptSummary(entries)
86
+ });
87
+ } catch {
88
+ continue;
89
+ }
90
+ }
91
+ } catch {
92
+ }
93
+ return sessions;
94
+ }
95
+ async listProjectSessions() {
96
+ const sessions = [];
97
+ const sessionMap = /* @__PURE__ */ new Map();
53
98
  try {
54
- const projectDirs = await fs.readdir(this.basePath);
99
+ const projectDirs = await fs.readdir(this.projectsPath);
55
100
  for (const projectDir of projectDirs) {
56
- const projectPath = join(this.basePath, projectDir);
101
+ if (projectDir.startsWith(".")) continue;
102
+ const projectPath = join(this.projectsPath, projectDir);
57
103
  const stat = await fs.stat(projectPath);
58
104
  if (!stat.isDirectory()) continue;
59
- const sessionsPath = join(projectPath, "sessions");
105
+ const files = await fs.readdir(projectPath);
106
+ for (const file of files) {
107
+ if (!file.endsWith(".jsonl")) continue;
108
+ if (file.startsWith("agent-")) continue;
109
+ const filePath = join(projectPath, file);
110
+ const sessionId = file.replace(".jsonl", "");
111
+ sessionMap.set(sessionId, {
112
+ id: sessionId,
113
+ filePath,
114
+ projectPath: this.decodeProjectPath(projectDir),
115
+ source: "projects"
116
+ });
117
+ }
118
+ }
119
+ for (const [sessionId, source] of sessionMap) {
60
120
  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
- }
121
+ const entries = await this.readProjectFile(source.filePath);
122
+ if (entries.length === 0) continue;
123
+ const userMessages = entries.filter(
124
+ (e) => e.type === "user" && e.message?.role === "user"
125
+ );
126
+ const timestamps = entries.filter((e) => e.timestamp).map((e) => e.timestamp);
127
+ if (timestamps.length === 0) continue;
128
+ sessions.push({
129
+ id: sessionId,
130
+ projectPath: source.projectPath,
131
+ startTime: timestamps[0],
132
+ endTime: timestamps[timestamps.length - 1],
133
+ messageCount: userMessages.length,
134
+ summary: this.extractProjectSummary(entries)
135
+ });
77
136
  } catch {
78
137
  continue;
79
138
  }
80
139
  }
81
- } catch (error) {
82
- throw new Error(`Failed to list sessions: ${error}`);
140
+ } catch {
83
141
  }
84
142
  return sessions;
85
143
  }
86
144
  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}`);
145
+ const transcriptPath = join(this.transcriptsPath, `${id}.jsonl`);
146
+ try {
147
+ await fs.access(transcriptPath);
148
+ const entries = await this.readTranscriptFile(transcriptPath);
149
+ if (entries.length > 0) {
150
+ return this.transformTranscriptSession(id, entries);
151
+ }
152
+ } catch {
153
+ }
154
+ const projectFile = await this.findProjectSessionFile(id);
155
+ if (projectFile) {
156
+ const entries = await this.readProjectFile(projectFile.filePath);
157
+ return this.transformProjectSession(id, entries, projectFile.projectPath);
158
+ }
159
+ throw new Error(`Session not found: ${id}`);
160
+ }
161
+ async findProjectSessionFile(sessionId) {
162
+ try {
163
+ const projectDirs = await fs.readdir(this.projectsPath);
164
+ for (const projectDir of projectDirs) {
165
+ if (projectDir.startsWith(".")) continue;
166
+ const projectPath = join(this.projectsPath, projectDir);
167
+ const stat = await fs.stat(projectPath);
168
+ if (!stat.isDirectory()) continue;
169
+ const sessionFile = join(projectPath, `${sessionId}.jsonl`);
170
+ try {
171
+ await fs.access(sessionFile);
172
+ return {
173
+ filePath: sessionFile,
174
+ projectPath: this.decodeProjectPath(projectDir)
175
+ };
176
+ } catch {
177
+ continue;
178
+ }
179
+ }
180
+ } catch {
91
181
  }
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}`);
182
+ return null;
183
+ }
184
+ async readTranscriptFile(path) {
185
+ try {
186
+ const content = await fs.readFile(path, "utf-8");
187
+ const lines = content.trim().split("\n").filter(Boolean);
188
+ return lines.map((line) => JSON.parse(line));
189
+ } catch {
190
+ return [];
96
191
  }
97
- return this.transformSession(data, sessionInfo.projectPath);
98
192
  }
99
- async readSessionFile(path) {
193
+ async readProjectFile(path) {
100
194
  try {
101
195
  const content = await fs.readFile(path, "utf-8");
102
- return JSON.parse(content);
196
+ const lines = content.trim().split("\n").filter(Boolean);
197
+ return lines.map((line) => JSON.parse(line));
103
198
  } catch {
104
- return null;
199
+ return [];
105
200
  }
106
201
  }
107
- transformSession(data, projectPath) {
202
+ transformTranscriptSession(id, entries) {
203
+ const messages = [];
204
+ let currentAssistantContent = "";
205
+ let currentToolCalls = [];
206
+ let currentToolResults = [];
207
+ let lastTimestamp = "";
208
+ for (const entry of entries) {
209
+ if (entry.type === "user") {
210
+ if (currentAssistantContent || currentToolCalls?.length) {
211
+ messages.push({
212
+ role: "assistant",
213
+ content: currentAssistantContent.trim(),
214
+ timestamp: lastTimestamp,
215
+ toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0,
216
+ toolResults: currentToolResults.length > 0 ? currentToolResults : void 0
217
+ });
218
+ currentAssistantContent = "";
219
+ currentToolCalls = [];
220
+ currentToolResults = [];
221
+ }
222
+ messages.push({
223
+ role: "user",
224
+ content: entry.content ?? "",
225
+ timestamp: entry.timestamp
226
+ });
227
+ } else if (entry.type === "assistant") {
228
+ currentAssistantContent += (entry.content ?? "") + "\n";
229
+ lastTimestamp = entry.timestamp;
230
+ } else if (entry.type === "tool_use") {
231
+ currentToolCalls.push({
232
+ name: entry.tool_name ?? "unknown",
233
+ input: entry.tool_input ?? {}
234
+ });
235
+ lastTimestamp = entry.timestamp;
236
+ } else if (entry.type === "tool_result") {
237
+ currentToolResults.push({
238
+ name: entry.tool_name ?? "unknown",
239
+ output: entry.tool_output?.preview ?? "",
240
+ success: true
241
+ });
242
+ lastTimestamp = entry.timestamp;
243
+ }
244
+ }
245
+ if (currentAssistantContent || currentToolCalls?.length) {
246
+ messages.push({
247
+ role: "assistant",
248
+ content: currentAssistantContent.trim(),
249
+ timestamp: lastTimestamp,
250
+ toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0,
251
+ toolResults: currentToolResults.length > 0 ? currentToolResults : void 0
252
+ });
253
+ }
254
+ const firstEntry = entries[0];
255
+ const lastEntry = entries[entries.length - 1];
108
256
  return {
109
- id: data.id,
110
- projectPath,
111
- startTime: data.createdAt,
112
- endTime: data.updatedAt,
113
- messages: data.messages.map((msg) => this.transformMessage(msg))
257
+ id,
258
+ projectPath: "[transcripts]",
259
+ startTime: firstEntry.timestamp,
260
+ endTime: lastEntry.timestamp,
261
+ messages
114
262
  };
115
263
  }
116
- transformMessage(msg) {
264
+ transformProjectSession(id, entries, projectPath) {
265
+ const messages = [];
266
+ for (const entry of entries) {
267
+ if (entry.type === "file-history-snapshot") continue;
268
+ if (entry.message) {
269
+ const content = this.extractMessageContent(entry.message.content);
270
+ if (content) {
271
+ messages.push({
272
+ role: entry.message.role === "user" ? "user" : "assistant",
273
+ content,
274
+ timestamp: entry.timestamp
275
+ });
276
+ }
277
+ }
278
+ }
279
+ const timestamps = entries.filter((e) => e.timestamp).map((e) => e.timestamp);
117
280
  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
- }))
281
+ id,
282
+ projectPath,
283
+ startTime: timestamps[0] ?? "",
284
+ endTime: timestamps[timestamps.length - 1] ?? "",
285
+ messages
130
286
  };
131
287
  }
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;
288
+ extractMessageContent(content) {
289
+ if (typeof content === "string") {
290
+ return content;
291
+ }
292
+ if (Array.isArray(content)) {
293
+ return content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
294
+ }
295
+ return "";
296
+ }
297
+ decodeProjectPath(encoded) {
298
+ return encoded.replace(/-/g, "/").replace(/^\//, "");
299
+ }
300
+ extractTranscriptSummary(entries) {
301
+ const firstUserEntry = entries.find((e) => e.type === "user");
302
+ if (!firstUserEntry?.content) return "";
303
+ const content = firstUserEntry.content;
304
+ const firstLine = content.split("\n")[0];
305
+ return firstLine.length > 80 ? firstLine.slice(0, 80) + "..." : firstLine;
306
+ }
307
+ extractProjectSummary(entries) {
308
+ const firstUserEntry = entries.find(
309
+ (e) => e.type === "user" && e.message?.role === "user"
310
+ );
311
+ if (!firstUserEntry?.message) return "";
312
+ const content = this.extractMessageContent(firstUserEntry.message.content);
313
+ const firstLine = content.split("\n")[0];
314
+ return firstLine.length > 80 ? firstLine.slice(0, 80) + "..." : firstLine;
137
315
  }
138
316
  };
139
317