skill-distill 1.0.1 → 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,54 +42,144 @@ 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", "transcripts");
54
+ return join(homedir(), ".claude");
50
55
  }
51
56
  async listSessions() {
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() {
52
66
  const sessions = [];
53
67
  try {
54
- const files = await fs.readdir(this.basePath);
68
+ const files = await fs.readdir(this.transcriptsPath);
55
69
  for (const file of files) {
56
70
  if (!file.endsWith(".jsonl") || !file.startsWith("ses_")) continue;
57
- const filePath = join(this.basePath, file);
58
- const stat = await fs.stat(filePath);
71
+ const filePath = join(this.transcriptsPath, file);
59
72
  const sessionId = file.replace(".jsonl", "");
60
- const entries = await this.readTranscriptFile(filePath);
61
- if (entries.length === 0) continue;
62
- const userMessages = entries.filter((e) => e.type === "user");
63
- const firstEntry = entries[0];
64
- const lastEntry = entries[entries.length - 1];
65
- sessions.push({
66
- id: sessionId,
67
- projectPath: "",
68
- startTime: firstEntry.timestamp,
69
- endTime: lastEntry.timestamp,
70
- messageCount: userMessages.length,
71
- summary: this.extractSummary(entries)
72
- });
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
+ }
73
90
  }
74
- } catch (error) {
75
- throw new Error(`Failed to list sessions: ${error}`);
91
+ } catch {
76
92
  }
77
- return sessions.sort(
78
- (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
79
- );
93
+ return sessions;
94
+ }
95
+ async listProjectSessions() {
96
+ const sessions = [];
97
+ const sessionMap = /* @__PURE__ */ new Map();
98
+ try {
99
+ const projectDirs = await fs.readdir(this.projectsPath);
100
+ for (const projectDir of projectDirs) {
101
+ if (projectDir.startsWith(".")) continue;
102
+ const projectPath = join(this.projectsPath, projectDir);
103
+ const stat = await fs.stat(projectPath);
104
+ if (!stat.isDirectory()) continue;
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) {
120
+ try {
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
+ });
136
+ } catch {
137
+ continue;
138
+ }
139
+ }
140
+ } catch {
141
+ }
142
+ return sessions;
80
143
  }
81
144
  async getSession(id) {
82
- const filePath = join(this.basePath, `${id}.jsonl`);
145
+ const transcriptPath = join(this.transcriptsPath, `${id}.jsonl`);
83
146
  try {
84
- await fs.access(filePath);
147
+ await fs.access(transcriptPath);
148
+ const entries = await this.readTranscriptFile(transcriptPath);
149
+ if (entries.length > 0) {
150
+ return this.transformTranscriptSession(id, entries);
151
+ }
85
152
  } catch {
86
- throw new Error(`Session not found: ${id}`);
87
153
  }
88
- const entries = await this.readTranscriptFile(filePath);
89
- if (entries.length === 0) {
90
- throw new Error(`Empty session: ${id}`);
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
- return this.transformSession(id, entries);
182
+ return null;
93
183
  }
94
184
  async readTranscriptFile(path) {
95
185
  try {
@@ -100,7 +190,16 @@ var ClaudeSessionLoader = class extends SessionLoader {
100
190
  return [];
101
191
  }
102
192
  }
103
- transformSession(id, entries) {
193
+ async readProjectFile(path) {
194
+ try {
195
+ const content = await fs.readFile(path, "utf-8");
196
+ const lines = content.trim().split("\n").filter(Boolean);
197
+ return lines.map((line) => JSON.parse(line));
198
+ } catch {
199
+ return [];
200
+ }
201
+ }
202
+ transformTranscriptSession(id, entries) {
104
203
  const messages = [];
105
204
  let currentAssistantContent = "";
106
205
  let currentToolCalls = [];
@@ -111,7 +210,7 @@ var ClaudeSessionLoader = class extends SessionLoader {
111
210
  if (currentAssistantContent || currentToolCalls?.length) {
112
211
  messages.push({
113
212
  role: "assistant",
114
- content: currentAssistantContent,
213
+ content: currentAssistantContent.trim(),
115
214
  timestamp: lastTimestamp,
116
215
  toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0,
117
216
  toolResults: currentToolResults.length > 0 ? currentToolResults : void 0
@@ -156,19 +255,64 @@ var ClaudeSessionLoader = class extends SessionLoader {
156
255
  const lastEntry = entries[entries.length - 1];
157
256
  return {
158
257
  id,
159
- projectPath: "",
258
+ projectPath: "[transcripts]",
160
259
  startTime: firstEntry.timestamp,
161
260
  endTime: lastEntry.timestamp,
162
261
  messages
163
262
  };
164
263
  }
165
- extractSummary(entries) {
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);
280
+ return {
281
+ id,
282
+ projectPath,
283
+ startTime: timestamps[0] ?? "",
284
+ endTime: timestamps[timestamps.length - 1] ?? "",
285
+ messages
286
+ };
287
+ }
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) {
166
301
  const firstUserEntry = entries.find((e) => e.type === "user");
167
302
  if (!firstUserEntry?.content) return "";
168
303
  const content = firstUserEntry.content;
169
304
  const firstLine = content.split("\n")[0];
170
305
  return firstLine.length > 80 ? firstLine.slice(0, 80) + "..." : firstLine;
171
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;
315
+ }
172
316
  };
173
317
 
174
318
  // src/loaders/codex.ts