@arcreflex/agent-transcripts 0.1.1
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/.prettierignore +6 -0
- package/README.md +58 -0
- package/bin/agent-transcripts +2 -0
- package/bun.lock +54 -0
- package/package.json +31 -0
- package/spec.md +239 -0
- package/src/adapters/claude-code.ts +310 -0
- package/src/adapters/index.ts +45 -0
- package/src/cli.ts +114 -0
- package/src/parse.ts +127 -0
- package/src/render.ts +354 -0
- package/src/types.ts +75 -0
- package/src/utils/summary.ts +43 -0
- package/test/fixtures/claude/basic-conversation.input.jsonl +4 -0
- package/test/fixtures/claude/basic-conversation.output.md +28 -0
- package/test/fixtures/claude/multiple-tools.input.jsonl +2 -0
- package/test/fixtures/claude/multiple-tools.output.md +15 -0
- package/test/fixtures/claude/with-thinking.input.jsonl +2 -0
- package/test/fixtures/claude/with-thinking.output.md +24 -0
- package/test/fixtures/claude/with-tools.input.jsonl +2 -0
- package/test/fixtures/claude/with-tools.output.md +16 -0
- package/test/snapshots.test.ts +67 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract one-line summaries from tool call inputs.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
function truncate(str: string, maxLen: number): string {
|
|
6
|
+
if (str.length <= maxLen) return str;
|
|
7
|
+
return str.slice(0, maxLen - 3) + "...";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type ToolInput = Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
const extractors: Record<string, (input: ToolInput) => string> = {
|
|
13
|
+
Read: (i) => String(i.file_path || ""),
|
|
14
|
+
Write: (i) => String(i.file_path || ""),
|
|
15
|
+
Edit: (i) => String(i.file_path || ""),
|
|
16
|
+
Bash: (i) => {
|
|
17
|
+
if (i.description) return String(i.description);
|
|
18
|
+
if (i.command) return truncate(String(i.command), 60);
|
|
19
|
+
return "";
|
|
20
|
+
},
|
|
21
|
+
Grep: (i) => {
|
|
22
|
+
const pattern = String(i.pattern || "");
|
|
23
|
+
const path = i.path ? ` in ${i.path}` : "";
|
|
24
|
+
return truncate(pattern + path, 80);
|
|
25
|
+
},
|
|
26
|
+
Glob: (i) => String(i.pattern || ""),
|
|
27
|
+
WebFetch: (i) => String(i.url || ""),
|
|
28
|
+
WebSearch: (i) => String(i.query || ""),
|
|
29
|
+
Task: (i) => truncate(String(i.description || i.prompt || ""), 60),
|
|
30
|
+
TodoWrite: () => "update todos",
|
|
31
|
+
AskUserQuestion: () => "ask user",
|
|
32
|
+
NotebookEdit: (i) => String(i.notebook_path || ""),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function extractToolSummary(toolName: string, input: ToolInput): string {
|
|
36
|
+
const extractor = extractors[toolName];
|
|
37
|
+
if (extractor) {
|
|
38
|
+
const summary = extractor(input);
|
|
39
|
+
if (summary) return summary;
|
|
40
|
+
}
|
|
41
|
+
// Fallback: just use tool name
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{"type": "user", "uuid": "msg-1", "message": {"role": "user", "content": "Hello, can you help me with a coding question?"}, "timestamp": "2024-01-15T10:00:00Z"}
|
|
2
|
+
{"type": "assistant", "uuid": "msg-2", "parentUuid": "msg-1", "message": {"role": "assistant", "content": [{"type": "text", "text": "Of course! I'd be happy to help. What's your question?"}]}, "timestamp": "2024-01-15T10:00:05Z"}
|
|
3
|
+
{"type": "user", "uuid": "msg-3", "parentUuid": "msg-2", "message": {"role": "user", "content": "How do I reverse a string in Python?"}, "timestamp": "2024-01-15T10:00:30Z"}
|
|
4
|
+
{"type": "assistant", "uuid": "msg-4", "parentUuid": "msg-3", "message": {"role": "assistant", "content": [{"type": "text", "text": "You can reverse a string in Python using slicing:\n\n```python\nreversed_string = original[::-1]\n```\n\nThe `[::-1]` slice notation means start from the end and step backwards."}]}, "timestamp": "2024-01-15T10:00:35Z"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Transcript
|
|
2
|
+
|
|
3
|
+
**Source**: `test/fixtures/claude/basic-conversation.input.jsonl`
|
|
4
|
+
**Adapter**: claude-code
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## User
|
|
9
|
+
|
|
10
|
+
Hello, can you help me with a coding question?
|
|
11
|
+
|
|
12
|
+
## Assistant
|
|
13
|
+
|
|
14
|
+
Of course! I'd be happy to help. What's your question?
|
|
15
|
+
|
|
16
|
+
## User
|
|
17
|
+
|
|
18
|
+
How do I reverse a string in Python?
|
|
19
|
+
|
|
20
|
+
## Assistant
|
|
21
|
+
|
|
22
|
+
You can reverse a string in Python using slicing:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
reversed_string = original[::-1]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The `[::-1]` slice notation means start from the end and step backwards.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
{"type": "user", "uuid": "msg-1", "message": {"role": "user", "content": "Check the files"}, "timestamp": "2024-01-15T10:00:00Z"}
|
|
2
|
+
{"type": "assistant", "uuid": "msg-2", "parentUuid": "msg-1", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "tool-1", "name": "Read", "input": {"file_path": "/src/main.ts"}}, {"type": "tool_use", "id": "tool-2", "name": "Glob", "input": {"pattern": "**/*.test.ts"}}, {"type": "tool_use", "id": "tool-3", "name": "Bash", "input": {"command": "ls -la", "description": "List files"}}]}, "timestamp": "2024-01-15T10:00:02Z"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
{"type": "user", "uuid": "msg-1", "message": {"role": "user", "content": "What's 17 * 23?"}, "timestamp": "2024-01-15T10:00:00Z"}
|
|
2
|
+
{"type": "assistant", "uuid": "msg-2", "parentUuid": "msg-1", "message": {"role": "assistant", "content": [{"type": "thinking", "thinking": "Let me calculate 17 * 23.\n17 * 23 = 17 * 20 + 17 * 3\n = 340 + 51\n = 391"}, {"type": "text", "text": "17 * 23 = 391"}]}, "timestamp": "2024-01-15T10:00:02Z"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Transcript
|
|
2
|
+
|
|
3
|
+
**Source**: `test/fixtures/claude/with-thinking.input.jsonl`
|
|
4
|
+
**Adapter**: claude-code
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## User
|
|
9
|
+
|
|
10
|
+
What's 17 * 23?
|
|
11
|
+
|
|
12
|
+
## Assistant
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary>Thinking...</summary>
|
|
17
|
+
|
|
18
|
+
Let me calculate 17 * 23.
|
|
19
|
+
17 * 23 = 17 * 20 + 17 * 3
|
|
20
|
+
= 340 + 51
|
|
21
|
+
= 391
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
17 * 23 = 391
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
{"type": "user", "uuid": "msg-1", "message": {"role": "user", "content": "Read the config file"}, "timestamp": "2024-01-15T10:00:00Z"}
|
|
2
|
+
{"type": "assistant", "uuid": "msg-2", "parentUuid": "msg-1", "message": {"role": "assistant", "content": [{"type": "text", "text": "I'll read the config file for you."}, {"type": "tool_use", "id": "tool-1", "name": "Read", "input": {"file_path": "/project/config.json"}}]}, "timestamp": "2024-01-15T10:00:02Z"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Transcript
|
|
2
|
+
|
|
3
|
+
**Source**: `test/fixtures/claude/with-tools.input.jsonl`
|
|
4
|
+
**Adapter**: claude-code
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## User
|
|
9
|
+
|
|
10
|
+
Read the config file
|
|
11
|
+
|
|
12
|
+
## Assistant
|
|
13
|
+
|
|
14
|
+
I'll read the config file for you.
|
|
15
|
+
|
|
16
|
+
**Tool**: Read `/project/config.json`
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { Glob } from "bun";
|
|
4
|
+
|
|
5
|
+
const fixturesDir = join(dirname(import.meta.path), "fixtures/claude");
|
|
6
|
+
const binPath = join(dirname(import.meta.path), "../bin/agent-transcripts");
|
|
7
|
+
|
|
8
|
+
// Find all input fixtures
|
|
9
|
+
const inputFiles: string[] = [];
|
|
10
|
+
const glob = new Glob("*.input.jsonl");
|
|
11
|
+
for await (const file of glob.scan(fixturesDir)) {
|
|
12
|
+
inputFiles.push(file);
|
|
13
|
+
}
|
|
14
|
+
inputFiles.sort();
|
|
15
|
+
|
|
16
|
+
describe("snapshot tests", () => {
|
|
17
|
+
for (const inputFile of inputFiles) {
|
|
18
|
+
const name = inputFile.replace(".input.jsonl", "");
|
|
19
|
+
|
|
20
|
+
it(name, async () => {
|
|
21
|
+
const inputPath = join(fixturesDir, inputFile);
|
|
22
|
+
const expectedPath = inputPath.replace(".input.jsonl", ".output.md");
|
|
23
|
+
// Use relative path for consistent Source: field in output
|
|
24
|
+
const relativeInputPath = `test/fixtures/claude/${inputFile}`;
|
|
25
|
+
|
|
26
|
+
const expectedOutput = await Bun.file(expectedPath).text();
|
|
27
|
+
|
|
28
|
+
// Run the CLI: parse to temp JSON, then render to temp MD
|
|
29
|
+
const tempJson = `/tmp/test-${name}-${Date.now()}.json`;
|
|
30
|
+
const tempMd = `/tmp/test-${name}-${Date.now()}.md`;
|
|
31
|
+
|
|
32
|
+
// Parse
|
|
33
|
+
const parseResult = Bun.spawnSync([
|
|
34
|
+
binPath,
|
|
35
|
+
"parse",
|
|
36
|
+
relativeInputPath,
|
|
37
|
+
"--adapter",
|
|
38
|
+
"claude-code",
|
|
39
|
+
"-o",
|
|
40
|
+
tempJson,
|
|
41
|
+
]);
|
|
42
|
+
expect(parseResult.exitCode).toBe(0);
|
|
43
|
+
|
|
44
|
+
// Render
|
|
45
|
+
const renderResult = Bun.spawnSync([
|
|
46
|
+
binPath,
|
|
47
|
+
"render",
|
|
48
|
+
tempJson,
|
|
49
|
+
"-o",
|
|
50
|
+
tempMd,
|
|
51
|
+
]);
|
|
52
|
+
expect(renderResult.exitCode).toBe(0);
|
|
53
|
+
|
|
54
|
+
// Compare output
|
|
55
|
+
const actualOutput = await Bun.file(tempMd).text();
|
|
56
|
+
expect(actualOutput.trimEnd()).toBe(expectedOutput.trimEnd());
|
|
57
|
+
|
|
58
|
+
// Cleanup
|
|
59
|
+
await Bun.file(tempJson)
|
|
60
|
+
.delete()
|
|
61
|
+
.catch(() => {});
|
|
62
|
+
await Bun.file(tempMd)
|
|
63
|
+
.delete()
|
|
64
|
+
.catch(() => {});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"allowImportingTsExtensions": true,
|
|
7
|
+
"types": ["bun"],
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"resolveJsonModule": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"]
|
|
15
|
+
}
|