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 +236 -58
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.js +236 -58
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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"
|
|
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.
|
|
99
|
+
const projectDirs = await fs.readdir(this.projectsPath);
|
|
55
100
|
for (const projectDir of projectDirs) {
|
|
56
|
-
|
|
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
|
|
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
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
193
|
+
async readProjectFile(path) {
|
|
100
194
|
try {
|
|
101
195
|
const content = await fs.readFile(path, "utf-8");
|
|
102
|
-
|
|
196
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
197
|
+
return lines.map((line) => JSON.parse(line));
|
|
103
198
|
} catch {
|
|
104
|
-
return
|
|
199
|
+
return [];
|
|
105
200
|
}
|
|
106
201
|
}
|
|
107
|
-
|
|
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
|
|
110
|
-
projectPath,
|
|
111
|
-
startTime:
|
|
112
|
-
endTime:
|
|
113
|
-
messages
|
|
257
|
+
id,
|
|
258
|
+
projectPath: "[transcripts]",
|
|
259
|
+
startTime: firstEntry.timestamp,
|
|
260
|
+
endTime: lastEntry.timestamp,
|
|
261
|
+
messages
|
|
114
262
|
};
|
|
115
263
|
}
|
|
116
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|