@henteko/ccdigest 0.1.0
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/LICENSE +21 -0
- package/README.md +85 -0
- package/dist/cli/commands/export.d.ts +5 -0
- package/dist/cli/commands/export.js +27 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/list.d.ts +6 -0
- package/dist/cli/commands/list.js +41 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/show.d.ts +4 -0
- package/dist/cli/commands/show.js +25 -0
- package/dist/cli/commands/show.js.map +1 -0
- package/dist/cli/options.d.ts +8 -0
- package/dist/cli/options.js +13 -0
- package/dist/cli/options.js.map +1 -0
- package/dist/core/merger.d.ts +6 -0
- package/dist/core/merger.js +55 -0
- package/dist/core/merger.js.map +1 -0
- package/dist/core/parser.d.ts +9 -0
- package/dist/core/parser.js +190 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core/session-store.d.ts +35 -0
- package/dist/core/session-store.js +104 -0
- package/dist/core/session-store.js.map +1 -0
- package/dist/core/types.d.ts +133 -0
- package/dist/core/types.js +5 -0
- package/dist/core/types.js.map +1 -0
- package/dist/formatter/markdown.d.ts +5 -0
- package/dist/formatter/markdown.js +120 -0
- package/dist/formatter/markdown.js.map +1 -0
- package/dist/formatter/templates.d.ts +24 -0
- package/dist/formatter/templates.js +80 -0
- package/dist/formatter/templates.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 henteko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# ccdigest
|
|
2
|
+
|
|
3
|
+
Format [Claude Code](https://docs.anthropic.com/en/docs/claude-code) session data into readable Markdown for team sharing and code review.
|
|
4
|
+
|
|
5
|
+
Claude Code stores session logs as JSONL files under `~/.claude/projects/`. ccdigest reads these files and converts them into clean, human-readable Markdown.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @henteko/ccdigest
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run directly with npx:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @henteko/ccdigest list
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### List sessions
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
ccdigest list
|
|
25
|
+
ccdigest list --branch main
|
|
26
|
+
ccdigest list --json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Show session as Markdown
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
ccdigest show <session-id>
|
|
33
|
+
ccdigest show <session-id-1> <session-id-2> # merge multiple sessions
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Export to file
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
ccdigest export <session-id> -o output.md
|
|
40
|
+
ccdigest export <id-1> <id-2> -o combined.md
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Options
|
|
44
|
+
|
|
45
|
+
| Option | Description |
|
|
46
|
+
|--------|-------------|
|
|
47
|
+
| `--project <path>` | Project path (defaults to cwd) |
|
|
48
|
+
| `--no-thinking` | Hide thinking blocks |
|
|
49
|
+
| `--show-thinking` | Show thinking blocks expanded |
|
|
50
|
+
| `--no-tools` | Hide tool calls |
|
|
51
|
+
| `--max-tool-lines <n>` | Max lines for tool results (default: 50) |
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install
|
|
57
|
+
npm run build
|
|
58
|
+
npm test
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Release
|
|
62
|
+
|
|
63
|
+
1. Update version in `package.json`:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm version patch # or minor, major
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
2. Push the tag:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git push origin main --tags
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
3. Publish to npm:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm publish --access public
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`prepublishOnly` hook will automatically run build and tests before publishing.
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { findSession } from '../../core/session-store.js';
|
|
3
|
+
import { parseSession } from '../../core/parser.js';
|
|
4
|
+
import { mergeSessions } from '../../core/merger.js';
|
|
5
|
+
import { formatSession } from '../../formatter/markdown.js';
|
|
6
|
+
import { resolveFormatOptions } from '../options.js';
|
|
7
|
+
export async function runExport(sessionIds, options) {
|
|
8
|
+
const projectPath = options.project || process.cwd();
|
|
9
|
+
try {
|
|
10
|
+
const parsedSessions = await Promise.all(sessionIds.map(async (id) => {
|
|
11
|
+
const session = findSession(projectPath, id);
|
|
12
|
+
return parseSession(session.filePath);
|
|
13
|
+
}));
|
|
14
|
+
const merged = parsedSessions.length > 1
|
|
15
|
+
? mergeSessions(parsedSessions)
|
|
16
|
+
: parsedSessions[0];
|
|
17
|
+
const formatOpts = resolveFormatOptions(options);
|
|
18
|
+
const markdown = formatSession(merged, formatOpts);
|
|
19
|
+
fs.writeFileSync(options.output, markdown, 'utf-8');
|
|
20
|
+
console.error(`Exported to ${options.output}`);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
console.error(`Error: ${err.message}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../../src/cli/commands/export.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAoB,MAAM,eAAe,CAAC;AAEvE,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,UAAoB,EACpB,OAA2D;IAE3D,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAErD,IAAI,CAAC;QACH,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,GAAG,CACtC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YAC1B,MAAM,OAAO,GAAG,WAAW,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YAC7C,OAAO,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACxC,CAAC,CAAC,CACH,CAAC;QAEF,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,GAAG,CAAC;YACtC,CAAC,CAAC,aAAa,CAAC,cAAc,CAAC;YAC/B,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;QAEtB,MAAM,UAAU,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACjD,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAEnD,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QACpD,OAAO,CAAC,KAAK,CAAC,eAAe,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,UAAW,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { listSessions } from '../../core/session-store.js';
|
|
2
|
+
export function runList(options) {
|
|
3
|
+
const projectPath = options.project || process.cwd();
|
|
4
|
+
let sessions = listSessions(projectPath);
|
|
5
|
+
if (options.branch) {
|
|
6
|
+
sessions = sessions.filter((s) => s.branch === options.branch);
|
|
7
|
+
}
|
|
8
|
+
if (sessions.length === 0) {
|
|
9
|
+
console.error('No sessions found.');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
if (options.json) {
|
|
13
|
+
console.log(JSON.stringify(sessions.map((s) => ({
|
|
14
|
+
sessionId: s.sessionId,
|
|
15
|
+
modifiedAt: s.modifiedAt.toISOString(),
|
|
16
|
+
sizeBytes: s.sizeBytes,
|
|
17
|
+
branch: s.branch,
|
|
18
|
+
})), null, 2));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Table format
|
|
22
|
+
const maxIdLen = Math.max(...sessions.map((s) => s.sessionId.length), 10);
|
|
23
|
+
const maxBranchLen = Math.max(...sessions.map((s) => s.branch.length), 6);
|
|
24
|
+
console.log(`${'SESSION ID'.padEnd(maxIdLen)} ${'BRANCH'.padEnd(maxBranchLen)} ${'MODIFIED'.padEnd(19)} ${'SIZE'.padStart(10)}`);
|
|
25
|
+
console.log('-'.repeat(maxIdLen + 2 + maxBranchLen + 2 + 19 + 2 + 10));
|
|
26
|
+
for (const s of sessions) {
|
|
27
|
+
const date = s.modifiedAt.toISOString().replace('T', ' ').substring(0, 19);
|
|
28
|
+
const size = formatBytes(s.sizeBytes);
|
|
29
|
+
console.log(`${s.sessionId.padEnd(maxIdLen)} ${s.branch.padEnd(maxBranchLen)} ${date} ${size.padStart(10)}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function formatBytes(bytes) {
|
|
33
|
+
if (bytes < 1024)
|
|
34
|
+
return `${bytes} B`;
|
|
35
|
+
const kb = bytes / 1024;
|
|
36
|
+
if (kb < 1024)
|
|
37
|
+
return `${kb.toFixed(1)} KB`;
|
|
38
|
+
const mb = kb / 1024;
|
|
39
|
+
return `${mb.toFixed(1)} MB`;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/cli/commands/list.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { findSession } from '../../core/session-store.js';
|
|
2
|
+
import { parseSession } from '../../core/parser.js';
|
|
3
|
+
import { mergeSessions } from '../../core/merger.js';
|
|
4
|
+
import { formatSession } from '../../formatter/markdown.js';
|
|
5
|
+
import { resolveFormatOptions } from '../options.js';
|
|
6
|
+
export async function runShow(sessionIds, options) {
|
|
7
|
+
const projectPath = options.project || process.cwd();
|
|
8
|
+
try {
|
|
9
|
+
const parsedSessions = await Promise.all(sessionIds.map(async (id) => {
|
|
10
|
+
const session = findSession(projectPath, id);
|
|
11
|
+
return parseSession(session.filePath);
|
|
12
|
+
}));
|
|
13
|
+
const merged = parsedSessions.length > 1
|
|
14
|
+
? mergeSessions(parsedSessions)
|
|
15
|
+
: parsedSessions[0];
|
|
16
|
+
const formatOpts = resolveFormatOptions(options);
|
|
17
|
+
const markdown = formatSession(merged, formatOpts);
|
|
18
|
+
process.stdout.write(markdown);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
console.error(`Error: ${err.message}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=show.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"show.js","sourceRoot":"","sources":["../../../src/cli/commands/show.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAoB,MAAM,eAAe,CAAC;AAEvE,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,UAAoB,EACpB,OAA2C;IAE3C,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAErD,IAAI,CAAC;QACH,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,GAAG,CACtC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YAC1B,MAAM,OAAO,GAAG,WAAW,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YAC7C,OAAO,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACxC,CAAC,CAAC,CACH,CAAC;QAEF,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,GAAG,CAAC;YACtC,CAAC,CAAC,aAAa,CAAC,cAAc,CAAC;YAC/B,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;QAEtB,MAAM,UAAU,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACjD,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAEnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,UAAW,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function resolveFormatOptions(opts) {
|
|
2
|
+
let showThinking = 'collapsed';
|
|
3
|
+
if (opts.thinking === false)
|
|
4
|
+
showThinking = 'hidden';
|
|
5
|
+
if (opts.showThinking)
|
|
6
|
+
showThinking = 'expanded';
|
|
7
|
+
return {
|
|
8
|
+
showThinking,
|
|
9
|
+
showTools: opts.tools !== false,
|
|
10
|
+
maxToolLines: opts.maxToolLines ? parseInt(opts.maxToolLines, 10) : 50,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/cli/options.ts"],"names":[],"mappings":"AAWA,MAAM,UAAU,oBAAoB,CAAC,IAAiB;IACpD,IAAI,YAAY,GAAkC,WAAW,CAAC;IAC9D,IAAI,IAAI,CAAC,QAAQ,KAAK,KAAK;QAAE,YAAY,GAAG,QAAQ,CAAC;IACrD,IAAI,IAAI,CAAC,YAAY;QAAE,YAAY,GAAG,UAAU,CAAC;IAEjD,OAAO;QACL,YAAY;QACZ,SAAS,EAAE,IAAI,CAAC,KAAK,KAAK,KAAK;QAC/B,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE;KACvE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ParsedSession } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Merge multiple parsed sessions into a single session with turns sorted by timestamp.
|
|
4
|
+
* If only one session is provided, it is returned as-is.
|
|
5
|
+
*/
|
|
6
|
+
export declare function mergeSessions(sessions: ParsedSession[]): ParsedSession;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge multiple parsed sessions into a single session with turns sorted by timestamp.
|
|
3
|
+
* If only one session is provided, it is returned as-is.
|
|
4
|
+
*/
|
|
5
|
+
export function mergeSessions(sessions) {
|
|
6
|
+
if (sessions.length === 0) {
|
|
7
|
+
throw new Error('At least one session is required');
|
|
8
|
+
}
|
|
9
|
+
if (sessions.length === 1) {
|
|
10
|
+
return sessions[0];
|
|
11
|
+
}
|
|
12
|
+
// Collect all turns from all sessions
|
|
13
|
+
const allTurns = sessions.flatMap((s) => s.turns);
|
|
14
|
+
// Sort by timestamp (turns without timestamp go to the end)
|
|
15
|
+
allTurns.sort((a, b) => {
|
|
16
|
+
if (!a.timestamp && !b.timestamp)
|
|
17
|
+
return 0;
|
|
18
|
+
if (!a.timestamp)
|
|
19
|
+
return 1;
|
|
20
|
+
if (!b.timestamp)
|
|
21
|
+
return -1;
|
|
22
|
+
return a.timestamp.localeCompare(b.timestamp);
|
|
23
|
+
});
|
|
24
|
+
// Re-number turns
|
|
25
|
+
for (let i = 0; i < allTurns.length; i++) {
|
|
26
|
+
allTurns[i].turnNumber = i + 1;
|
|
27
|
+
}
|
|
28
|
+
// Merge metadata
|
|
29
|
+
const first = sessions[0].metadata;
|
|
30
|
+
const sessionId = sessions.map((s) => s.metadata.sessionId).join(',');
|
|
31
|
+
let startTime = first.startTime;
|
|
32
|
+
let endTime = first.endTime;
|
|
33
|
+
for (const s of sessions) {
|
|
34
|
+
const m = s.metadata;
|
|
35
|
+
if (m.startTime && (!startTime || m.startTime < startTime)) {
|
|
36
|
+
startTime = m.startTime;
|
|
37
|
+
}
|
|
38
|
+
if (m.endTime && (!endTime || m.endTime > endTime)) {
|
|
39
|
+
endTime = m.endTime;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
metadata: {
|
|
44
|
+
sessionId,
|
|
45
|
+
project: first.project,
|
|
46
|
+
branch: first.branch,
|
|
47
|
+
claudeVersion: first.claudeVersion,
|
|
48
|
+
startTime,
|
|
49
|
+
endTime,
|
|
50
|
+
model: first.model,
|
|
51
|
+
},
|
|
52
|
+
turns: allTurns,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=merger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merger.js","sourceRoot":"","sources":["../../src/core/merger.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,QAAyB;IACrD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IAED,sCAAsC;IACtC,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAElD,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACrB,IAAI,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,SAAS;YAAE,OAAO,CAAC,CAAC;QAC3C,IAAI,CAAC,CAAC,CAAC,SAAS;YAAE,OAAO,CAAC,CAAC;QAC3B,IAAI,CAAC,CAAC,CAAC,SAAS;YAAE,OAAO,CAAC,CAAC,CAAC;QAC5B,OAAO,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,kBAAkB;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAED,iBAAiB;IACjB,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACnC,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEtE,IAAI,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;IAChC,IAAI,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC5B,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;QACrB,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC,EAAE,CAAC;YAC3D,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC;QAC1B,CAAC;QACD,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,EAAE,CAAC;YACnD,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;QACtB,CAAC;IACH,CAAC;IAED,OAAO;QACL,QAAQ,EAAE;YACR,SAAS;YACT,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,SAAS;YACT,OAAO;YACP,KAAK,EAAE,KAAK,CAAC,KAAK;SACnB;QACD,KAAK,EAAE,QAAQ;KAChB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SessionEvent, ParsedSession } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Read all events from a JSONL file.
|
|
4
|
+
*/
|
|
5
|
+
export declare function readEvents(filePath: string): Promise<SessionEvent[]>;
|
|
6
|
+
/**
|
|
7
|
+
* Parse events into a structured session with conversation turns.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseSession(filePath: string): Promise<ParsedSession>;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import readline from 'node:readline';
|
|
3
|
+
/**
|
|
4
|
+
* Read all events from a JSONL file.
|
|
5
|
+
*/
|
|
6
|
+
export async function readEvents(filePath) {
|
|
7
|
+
const events = [];
|
|
8
|
+
const fileStream = fs.createReadStream(filePath, { encoding: 'utf-8' });
|
|
9
|
+
const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
10
|
+
for await (const line of rl) {
|
|
11
|
+
const trimmed = line.trim();
|
|
12
|
+
if (!trimmed)
|
|
13
|
+
continue;
|
|
14
|
+
try {
|
|
15
|
+
events.push(JSON.parse(trimmed));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Skip malformed lines
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return events;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Extract session metadata from the events.
|
|
25
|
+
*/
|
|
26
|
+
function extractMetadata(events) {
|
|
27
|
+
let sessionId = '';
|
|
28
|
+
let project = '';
|
|
29
|
+
let branch = '';
|
|
30
|
+
let version = '';
|
|
31
|
+
let model = '';
|
|
32
|
+
let startTime = '';
|
|
33
|
+
let endTime = '';
|
|
34
|
+
for (const event of events) {
|
|
35
|
+
if ('sessionId' in event && event.sessionId) {
|
|
36
|
+
sessionId = event.sessionId;
|
|
37
|
+
}
|
|
38
|
+
if ('cwd' in event && event.cwd) {
|
|
39
|
+
project = event.cwd;
|
|
40
|
+
}
|
|
41
|
+
if ('gitBranch' in event && event.gitBranch) {
|
|
42
|
+
branch = event.gitBranch;
|
|
43
|
+
}
|
|
44
|
+
if ('version' in event && event.version) {
|
|
45
|
+
version = event.version;
|
|
46
|
+
}
|
|
47
|
+
if (event.type === 'assistant') {
|
|
48
|
+
const ae = event;
|
|
49
|
+
if (ae.message?.model) {
|
|
50
|
+
model = ae.message.model;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Track time range
|
|
54
|
+
const ts = 'timestamp' in event ? event.timestamp : undefined;
|
|
55
|
+
if (ts) {
|
|
56
|
+
if (!startTime || ts < startTime)
|
|
57
|
+
startTime = ts;
|
|
58
|
+
if (!endTime || ts > endTime)
|
|
59
|
+
endTime = ts;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { sessionId, project, branch, claudeVersion: version, startTime, endTime, model };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Extract text from a tool result content (string or array).
|
|
66
|
+
*/
|
|
67
|
+
function extractToolResultText(content) {
|
|
68
|
+
if (typeof content === 'string')
|
|
69
|
+
return content;
|
|
70
|
+
if (Array.isArray(content)) {
|
|
71
|
+
return content
|
|
72
|
+
.filter((item) => item.type === 'text' && item.text)
|
|
73
|
+
.map((item) => item.text)
|
|
74
|
+
.join('\n');
|
|
75
|
+
}
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Parse events into a structured session with conversation turns.
|
|
80
|
+
*/
|
|
81
|
+
export async function parseSession(filePath) {
|
|
82
|
+
const events = await readEvents(filePath);
|
|
83
|
+
const metadata = extractMetadata(events);
|
|
84
|
+
const turns = [];
|
|
85
|
+
let currentTurn = null;
|
|
86
|
+
// Accumulate assistant message fragments by message.id
|
|
87
|
+
// Each streaming chunk for the same message.id gets merged
|
|
88
|
+
const pendingAssistantChunks = new Map();
|
|
89
|
+
// Map tool_use id -> ToolCall for filling in results later
|
|
90
|
+
const toolCallMap = new Map();
|
|
91
|
+
function flushAssistantMessages() {
|
|
92
|
+
const messages = [];
|
|
93
|
+
for (const [messageId, data] of pendingAssistantChunks) {
|
|
94
|
+
messages.push({
|
|
95
|
+
messageId,
|
|
96
|
+
model: data.model,
|
|
97
|
+
thinkingBlocks: data.thinkingBlocks,
|
|
98
|
+
textBlocks: data.textBlocks,
|
|
99
|
+
toolCalls: data.toolCalls,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
pendingAssistantChunks.clear();
|
|
103
|
+
return messages;
|
|
104
|
+
}
|
|
105
|
+
for (const event of events) {
|
|
106
|
+
if (event.type === 'user') {
|
|
107
|
+
const ue = event;
|
|
108
|
+
const content = ue.message.content;
|
|
109
|
+
if (typeof content === 'string') {
|
|
110
|
+
// New user prompt → new turn
|
|
111
|
+
if (currentTurn) {
|
|
112
|
+
currentTurn.assistantMessages = flushAssistantMessages();
|
|
113
|
+
turns.push(currentTurn);
|
|
114
|
+
}
|
|
115
|
+
currentTurn = {
|
|
116
|
+
turnNumber: turns.length + 1,
|
|
117
|
+
userMessage: content,
|
|
118
|
+
assistantMessages: [],
|
|
119
|
+
timestamp: ue.timestamp,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
else if (Array.isArray(content)) {
|
|
123
|
+
// tool_result → same turn, fill in tool results
|
|
124
|
+
for (const block of content) {
|
|
125
|
+
if (block.type === 'tool_result') {
|
|
126
|
+
const tr = block;
|
|
127
|
+
const tc = toolCallMap.get(tr.tool_use_id);
|
|
128
|
+
if (tc) {
|
|
129
|
+
tc.result = extractToolResultText(tr.content);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else if (event.type === 'assistant') {
|
|
136
|
+
const ae = event;
|
|
137
|
+
const msgId = ae.message.id;
|
|
138
|
+
if (!msgId)
|
|
139
|
+
continue;
|
|
140
|
+
let accumulated = pendingAssistantChunks.get(msgId);
|
|
141
|
+
if (!accumulated) {
|
|
142
|
+
accumulated = {
|
|
143
|
+
model: ae.message.model,
|
|
144
|
+
thinkingBlocks: [],
|
|
145
|
+
textBlocks: [],
|
|
146
|
+
toolCalls: [],
|
|
147
|
+
};
|
|
148
|
+
pendingAssistantChunks.set(msgId, accumulated);
|
|
149
|
+
}
|
|
150
|
+
for (const block of ae.message.content) {
|
|
151
|
+
switch (block.type) {
|
|
152
|
+
case 'thinking':
|
|
153
|
+
if (block.thinking) {
|
|
154
|
+
accumulated.thinkingBlocks.push(block.thinking);
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
case 'text':
|
|
158
|
+
if (block.text && block.text.trim()) {
|
|
159
|
+
accumulated.textBlocks.push(block.text);
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
case 'tool_use': {
|
|
163
|
+
const tc = {
|
|
164
|
+
name: block.name,
|
|
165
|
+
input: block.input,
|
|
166
|
+
result: '',
|
|
167
|
+
};
|
|
168
|
+
accumulated.toolCalls.push(tc);
|
|
169
|
+
toolCallMap.set(block.id, tc);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (event.type === 'system') {
|
|
176
|
+
const se = event;
|
|
177
|
+
if (se.subtype === 'turn_duration' && se.durationMs && currentTurn) {
|
|
178
|
+
currentTurn.durationMs = se.durationMs;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Skip progress and file-history-snapshot events
|
|
182
|
+
}
|
|
183
|
+
// Flush the last turn
|
|
184
|
+
if (currentTurn) {
|
|
185
|
+
currentTurn.assistantMessages = flushAssistantMessages();
|
|
186
|
+
turns.push(currentTurn);
|
|
187
|
+
}
|
|
188
|
+
return { metadata, turns };
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/core/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,QAAQ,MAAM,eAAe,CAAC;AAerC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,MAAM,UAAU,GAAG,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;IACxE,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;IAEhF,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,EAAE,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,IAAI,CAAC;YACH,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAiB,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,MAAsB;IAC7C,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,IAAI,OAAO,GAAG,EAAE,CAAC;IAEjB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,WAAW,IAAI,KAAK,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YAC5C,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;QAC9B,CAAC;QACD,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;YAChC,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC;QACtB,CAAC;QACD,IAAI,WAAW,IAAI,KAAK,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YAC5C,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC;QAC3B,CAAC;QACD,IAAI,SAAS,IAAI,KAAK,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACxC,OAAO,GAAG,KAAK,CAAC,OAAiB,CAAC;QACpC,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAC/B,MAAM,EAAE,GAAG,KAAuB,CAAC;YACnC,IAAI,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;gBACtB,KAAK,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;YAC3B,CAAC;QACH,CAAC;QAED,mBAAmB;QACnB,MAAM,EAAE,GAAG,WAAW,IAAI,KAAK,CAAC,CAAC,CAAE,KAA+B,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QACzF,IAAI,EAAE,EAAE,CAAC;YACP,IAAI,CAAC,SAAS,IAAI,EAAE,GAAG,SAAS;gBAAE,SAAS,GAAG,EAAE,CAAC;YACjD,IAAI,CAAC,OAAO,IAAI,EAAE,GAAG,OAAO;gBAAE,OAAO,GAAG,EAAE,CAAC;QAC7C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC3F,CAAC;AAED;;GAEG;AACH,SAAS,qBAAqB,CAAC,OAAwD;IACrF,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,OAAO,OAAO;aACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC;aACnD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAK,CAAC;aACzB,IAAI,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAgB;IACjD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAEzC,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,IAAI,WAAW,GAA4B,IAAI,CAAC;IAEhD,uDAAuD;IACvD,2DAA2D;IAC3D,MAAM,sBAAsB,GAAG,IAAI,GAAG,EAGnC,CAAC;IAEJ,2DAA2D;IAC3D,MAAM,WAAW,GAAG,IAAI,GAAG,EAAoB,CAAC;IAEhD,SAAS,sBAAsB;QAC7B,MAAM,QAAQ,GAAuB,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,sBAAsB,EAAE,CAAC;YACvD,QAAQ,CAAC,IAAI,CAAC;gBACZ,SAAS;gBACT,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;aAC1B,CAAC,CAAC;QACL,CAAC;QACD,sBAAsB,CAAC,KAAK,EAAE,CAAC;QAC/B,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,MAAM,EAAE,GAAG,KAAkB,CAAC;YAC9B,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;YAEnC,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAChC,6BAA6B;gBAC7B,IAAI,WAAW,EAAE,CAAC;oBAChB,WAAW,CAAC,iBAAiB,GAAG,sBAAsB,EAAE,CAAC;oBACzD,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBAC1B,CAAC;gBACD,WAAW,GAAG;oBACZ,UAAU,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC;oBAC5B,WAAW,EAAE,OAAO;oBACpB,iBAAiB,EAAE,EAAE;oBACrB,SAAS,EAAE,EAAE,CAAC,SAAS;iBACxB,CAAC;YACJ,CAAC;iBAAM,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBAClC,gDAAgD;gBAChD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;oBAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;wBACjC,MAAM,EAAE,GAAG,KAAwB,CAAC;wBACpC,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC;wBAC3C,IAAI,EAAE,EAAE,CAAC;4BACP,EAAE,CAAC,MAAM,GAAG,qBAAqB,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;wBAChD,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACtC,MAAM,EAAE,GAAG,KAAuB,CAAC;YACnC,MAAM,KAAK,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK;gBAAE,SAAS;YAErB,IAAI,WAAW,GAAG,sBAAsB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACpD,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,WAAW,GAAG;oBACZ,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK;oBACvB,cAAc,EAAE,EAAE;oBAClB,UAAU,EAAE,EAAE;oBACd,SAAS,EAAE,EAAE;iBACd,CAAC;gBACF,sBAAsB,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;YACjD,CAAC;YAED,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;gBACvC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;oBACnB,KAAK,UAAU;wBACb,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;4BACnB,WAAW,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;wBAClD,CAAC;wBACD,MAAM;oBACR,KAAK,MAAM;wBACT,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;4BACpC,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;wBAC1C,CAAC;wBACD,MAAM;oBACR,KAAK,UAAU,CAAC,CAAC,CAAC;wBAChB,MAAM,EAAE,GAAa;4BACnB,IAAI,EAAE,KAAK,CAAC,IAAI;4BAChB,KAAK,EAAE,KAAK,CAAC,KAAK;4BAClB,MAAM,EAAE,EAAE;yBACX,CAAC;wBACF,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;wBAC/B,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;wBAC9B,MAAM;oBACR,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACnC,MAAM,EAAE,GAAG,KAAoB,CAAC;YAChC,IAAI,EAAE,CAAC,OAAO,KAAK,eAAe,IAAI,EAAE,CAAC,UAAU,IAAI,WAAW,EAAE,CAAC;gBACnE,WAAW,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC;YACzC,CAAC;QACH,CAAC;QACD,iDAAiD;IACnD,CAAC;IAED,sBAAsB;IACtB,IAAI,WAAW,EAAE,CAAC;QAChB,WAAW,CAAC,iBAAiB,GAAG,sBAAsB,EAAE,CAAC;QACzD,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface SessionInfo {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
filePath: string;
|
|
4
|
+
modifiedAt: Date;
|
|
5
|
+
sizeBytes: number;
|
|
6
|
+
branch: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Encode a filesystem path to the directory name format used by Claude Code.
|
|
10
|
+
* e.g. "/Users/alice/dev/myproject" → "-Users-alice-dev-myproject"
|
|
11
|
+
*/
|
|
12
|
+
export declare function encodeProjectPath(projectPath: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Decode a Claude Code project directory name back to a filesystem path.
|
|
15
|
+
* e.g. "-Users-alice-dev-myproject" → "/Users/alice/dev/myproject"
|
|
16
|
+
*/
|
|
17
|
+
export declare function decodeProjectPath(encoded: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Get the Claude Code project data directory for a given project path.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getProjectDir(projectPath: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Extract the git branch from a JSONL session file by reading the first few lines.
|
|
24
|
+
* Returns an empty string if no gitBranch is found.
|
|
25
|
+
*/
|
|
26
|
+
export declare function extractBranch(filePath: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* List all session JSONL files for a project, sorted by modification time (newest first).
|
|
29
|
+
*/
|
|
30
|
+
export declare function listSessions(projectPath: string): SessionInfo[];
|
|
31
|
+
/**
|
|
32
|
+
* Find a session by exact ID or prefix match.
|
|
33
|
+
* Returns the matching session, or throws if no match or ambiguous.
|
|
34
|
+
*/
|
|
35
|
+
export declare function findSession(projectPath: string, idOrPrefix: string): SessionInfo;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
const CLAUDE_PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
|
|
5
|
+
/**
|
|
6
|
+
* Encode a filesystem path to the directory name format used by Claude Code.
|
|
7
|
+
* e.g. "/Users/alice/dev/myproject" → "-Users-alice-dev-myproject"
|
|
8
|
+
*/
|
|
9
|
+
export function encodeProjectPath(projectPath) {
|
|
10
|
+
return projectPath.replace(/\//g, '-');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Decode a Claude Code project directory name back to a filesystem path.
|
|
14
|
+
* e.g. "-Users-alice-dev-myproject" → "/Users/alice/dev/myproject"
|
|
15
|
+
*/
|
|
16
|
+
export function decodeProjectPath(encoded) {
|
|
17
|
+
// The encoded string starts with '-' which represents the leading '/'
|
|
18
|
+
// Then each '-' represents a '/'
|
|
19
|
+
return encoded.replace(/-/g, '/');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get the Claude Code project data directory for a given project path.
|
|
23
|
+
*/
|
|
24
|
+
export function getProjectDir(projectPath) {
|
|
25
|
+
const encoded = encodeProjectPath(projectPath);
|
|
26
|
+
return path.join(CLAUDE_PROJECTS_DIR, encoded);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Extract the git branch from a JSONL session file by reading the first few lines.
|
|
30
|
+
* Returns an empty string if no gitBranch is found.
|
|
31
|
+
*/
|
|
32
|
+
export function extractBranch(filePath) {
|
|
33
|
+
const MAX_BYTES = 4096;
|
|
34
|
+
const fd = fs.openSync(filePath, 'r');
|
|
35
|
+
try {
|
|
36
|
+
const buf = Buffer.alloc(MAX_BYTES);
|
|
37
|
+
const bytesRead = fs.readSync(fd, buf, 0, MAX_BYTES, 0);
|
|
38
|
+
const chunk = buf.toString('utf8', 0, bytesRead);
|
|
39
|
+
const lines = chunk.split('\n');
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
if (!line.trim())
|
|
42
|
+
continue;
|
|
43
|
+
try {
|
|
44
|
+
const event = JSON.parse(line);
|
|
45
|
+
if (event.gitBranch) {
|
|
46
|
+
return event.gitBranch;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// skip malformed lines
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
fs.closeSync(fd);
|
|
56
|
+
}
|
|
57
|
+
return '';
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* List all session JSONL files for a project, sorted by modification time (newest first).
|
|
61
|
+
*/
|
|
62
|
+
export function listSessions(projectPath) {
|
|
63
|
+
const projectDir = getProjectDir(projectPath);
|
|
64
|
+
if (!fs.existsSync(projectDir)) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const entries = fs.readdirSync(projectDir, { withFileTypes: true });
|
|
68
|
+
const sessions = [];
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
if (!entry.isFile() || !entry.name.endsWith('.jsonl')) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const filePath = path.join(projectDir, entry.name);
|
|
74
|
+
const stat = fs.statSync(filePath);
|
|
75
|
+
const sessionId = entry.name.replace('.jsonl', '');
|
|
76
|
+
sessions.push({
|
|
77
|
+
sessionId,
|
|
78
|
+
filePath,
|
|
79
|
+
modifiedAt: stat.mtime,
|
|
80
|
+
sizeBytes: stat.size,
|
|
81
|
+
branch: extractBranch(filePath),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Sort newest first
|
|
85
|
+
sessions.sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime());
|
|
86
|
+
return sessions;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Find a session by exact ID or prefix match.
|
|
90
|
+
* Returns the matching session, or throws if no match or ambiguous.
|
|
91
|
+
*/
|
|
92
|
+
export function findSession(projectPath, idOrPrefix) {
|
|
93
|
+
const sessions = listSessions(projectPath);
|
|
94
|
+
const matches = sessions.filter((s) => s.sessionId.startsWith(idOrPrefix));
|
|
95
|
+
if (matches.length === 0) {
|
|
96
|
+
throw new Error(`No session found matching "${idOrPrefix}"`);
|
|
97
|
+
}
|
|
98
|
+
if (matches.length > 1) {
|
|
99
|
+
const ids = matches.map((s) => s.sessionId).join('\n ');
|
|
100
|
+
throw new Error(`Ambiguous session prefix "${idOrPrefix}". Matches:\n ${ids}`);
|
|
101
|
+
}
|
|
102
|
+
return matches[0];
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=session-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/core/session-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/** Common fields present on most JSONL events */
|
|
2
|
+
export interface BaseEvent {
|
|
3
|
+
type: string;
|
|
4
|
+
uuid: string;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
parentUuid: string | null;
|
|
7
|
+
isSidechain: boolean;
|
|
8
|
+
userType: string;
|
|
9
|
+
cwd: string;
|
|
10
|
+
sessionId: string;
|
|
11
|
+
version: string;
|
|
12
|
+
gitBranch: string;
|
|
13
|
+
}
|
|
14
|
+
export interface TextBlock {
|
|
15
|
+
type: 'text';
|
|
16
|
+
text: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ThinkingBlock {
|
|
19
|
+
type: 'thinking';
|
|
20
|
+
thinking: string;
|
|
21
|
+
signature?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ToolUseBlock {
|
|
24
|
+
type: 'tool_use';
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
input: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export interface ToolResultBlock {
|
|
30
|
+
type: 'tool_result';
|
|
31
|
+
tool_use_id: string;
|
|
32
|
+
content: string | ToolResultContentItem[];
|
|
33
|
+
}
|
|
34
|
+
export interface ToolResultContentItem {
|
|
35
|
+
type: 'text' | 'image';
|
|
36
|
+
text?: string;
|
|
37
|
+
source?: unknown;
|
|
38
|
+
}
|
|
39
|
+
export type ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock;
|
|
40
|
+
export interface UserEvent extends BaseEvent {
|
|
41
|
+
type: 'user';
|
|
42
|
+
message: {
|
|
43
|
+
role: 'user';
|
|
44
|
+
content: string | ContentBlock[];
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface AssistantEvent extends BaseEvent {
|
|
48
|
+
type: 'assistant';
|
|
49
|
+
message: {
|
|
50
|
+
role: 'assistant';
|
|
51
|
+
id: string;
|
|
52
|
+
model: string;
|
|
53
|
+
stop_reason: string | null;
|
|
54
|
+
content: ContentBlock[];
|
|
55
|
+
usage?: {
|
|
56
|
+
input_tokens: number;
|
|
57
|
+
output_tokens: number;
|
|
58
|
+
cache_creation_input_tokens?: number;
|
|
59
|
+
cache_read_input_tokens?: number;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
requestId?: string;
|
|
63
|
+
}
|
|
64
|
+
export interface ProgressEvent extends BaseEvent {
|
|
65
|
+
type: 'progress';
|
|
66
|
+
data: {
|
|
67
|
+
type: string;
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
};
|
|
70
|
+
toolUseID: string;
|
|
71
|
+
parentToolUseID: string;
|
|
72
|
+
}
|
|
73
|
+
export interface SystemEvent extends BaseEvent {
|
|
74
|
+
type: 'system';
|
|
75
|
+
subtype: string;
|
|
76
|
+
durationMs?: number;
|
|
77
|
+
hookCount?: number;
|
|
78
|
+
hookInfos?: Array<{
|
|
79
|
+
command: string;
|
|
80
|
+
}>;
|
|
81
|
+
hookErrors?: unknown[];
|
|
82
|
+
stopReason?: string;
|
|
83
|
+
level?: string;
|
|
84
|
+
[key: string]: unknown;
|
|
85
|
+
}
|
|
86
|
+
export interface FileHistorySnapshotEvent {
|
|
87
|
+
type: 'file-history-snapshot';
|
|
88
|
+
messageId: string;
|
|
89
|
+
snapshot: {
|
|
90
|
+
messageId: string;
|
|
91
|
+
trackedFileBackups: Record<string, unknown>;
|
|
92
|
+
timestamp: string;
|
|
93
|
+
};
|
|
94
|
+
isSnapshotUpdate: boolean;
|
|
95
|
+
}
|
|
96
|
+
export type SessionEvent = UserEvent | AssistantEvent | ProgressEvent | SystemEvent | FileHistorySnapshotEvent;
|
|
97
|
+
export interface SessionMetadata {
|
|
98
|
+
sessionId: string;
|
|
99
|
+
project: string;
|
|
100
|
+
branch: string;
|
|
101
|
+
claudeVersion: string;
|
|
102
|
+
startTime: string;
|
|
103
|
+
endTime: string;
|
|
104
|
+
model: string;
|
|
105
|
+
}
|
|
106
|
+
export interface ToolCall {
|
|
107
|
+
name: string;
|
|
108
|
+
input: Record<string, unknown>;
|
|
109
|
+
result: string;
|
|
110
|
+
}
|
|
111
|
+
export interface AssistantMessage {
|
|
112
|
+
messageId: string;
|
|
113
|
+
model: string;
|
|
114
|
+
thinkingBlocks: string[];
|
|
115
|
+
textBlocks: string[];
|
|
116
|
+
toolCalls: ToolCall[];
|
|
117
|
+
}
|
|
118
|
+
export interface ConversationTurn {
|
|
119
|
+
turnNumber: number;
|
|
120
|
+
userMessage: string;
|
|
121
|
+
assistantMessages: AssistantMessage[];
|
|
122
|
+
durationMs?: number;
|
|
123
|
+
timestamp?: string;
|
|
124
|
+
}
|
|
125
|
+
export interface ParsedSession {
|
|
126
|
+
metadata: SessionMetadata;
|
|
127
|
+
turns: ConversationTurn[];
|
|
128
|
+
}
|
|
129
|
+
export interface FormatOptions {
|
|
130
|
+
showThinking: 'collapsed' | 'expanded' | 'hidden';
|
|
131
|
+
showTools: boolean;
|
|
132
|
+
maxToolLines: number;
|
|
133
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,mDAAmD;AACnD,+DAA+D"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { formatDuration, formatTimestamp, truncateLines, details, formatToolInput, } from './templates.js';
|
|
2
|
+
const DEFAULT_OPTIONS = {
|
|
3
|
+
showThinking: 'collapsed',
|
|
4
|
+
showTools: true,
|
|
5
|
+
maxToolLines: 50,
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Format a parsed session into Markdown.
|
|
9
|
+
*/
|
|
10
|
+
export function formatSession(session, options = {}) {
|
|
11
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
12
|
+
const parts = [];
|
|
13
|
+
parts.push(renderHeader(session));
|
|
14
|
+
for (const turn of session.turns) {
|
|
15
|
+
parts.push(renderTurn(turn, opts));
|
|
16
|
+
}
|
|
17
|
+
return parts.join('\n\n---\n\n') + '\n';
|
|
18
|
+
}
|
|
19
|
+
function renderHeader(session) {
|
|
20
|
+
const { metadata } = session;
|
|
21
|
+
const lines = [];
|
|
22
|
+
const isMerged = metadata.sessionId.includes(',');
|
|
23
|
+
if (isMerged) {
|
|
24
|
+
lines.push('# Merged Session Report');
|
|
25
|
+
lines.push('');
|
|
26
|
+
const ids = metadata.sessionId.split(',');
|
|
27
|
+
lines.push(`- **Sessions** (${ids.length}):`);
|
|
28
|
+
for (const id of ids) {
|
|
29
|
+
lines.push(` - \`${id}\``);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
lines.push('# Session Report');
|
|
34
|
+
lines.push('');
|
|
35
|
+
lines.push(`- **Session ID**: \`${metadata.sessionId}\``);
|
|
36
|
+
}
|
|
37
|
+
lines.push(`- **Project**: \`${metadata.project}\``);
|
|
38
|
+
if (metadata.branch) {
|
|
39
|
+
lines.push(`- **Branch**: \`${metadata.branch}\``);
|
|
40
|
+
}
|
|
41
|
+
if (metadata.model) {
|
|
42
|
+
lines.push(`- **Model**: \`${metadata.model}\``);
|
|
43
|
+
}
|
|
44
|
+
if (metadata.startTime && metadata.endTime) {
|
|
45
|
+
const start = formatTimestamp(metadata.startTime);
|
|
46
|
+
const end = formatTimestamp(metadata.endTime);
|
|
47
|
+
const durationMs = new Date(metadata.endTime).getTime() - new Date(metadata.startTime).getTime();
|
|
48
|
+
const duration = formatDuration(durationMs);
|
|
49
|
+
if (start === end) {
|
|
50
|
+
lines.push(`- **Date**: ${start}`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
lines.push(`- **Date**: ${start} - ${end} (${duration})`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (session.turns.length > 0) {
|
|
57
|
+
lines.push(`- **Turns**: ${session.turns.length}`);
|
|
58
|
+
}
|
|
59
|
+
return lines.join('\n');
|
|
60
|
+
}
|
|
61
|
+
function renderTurn(turn, opts) {
|
|
62
|
+
const parts = [];
|
|
63
|
+
// Turn header
|
|
64
|
+
let turnHeader = `## Turn ${turn.turnNumber}`;
|
|
65
|
+
if (turn.durationMs) {
|
|
66
|
+
turnHeader += ` (${formatDuration(turn.durationMs)})`;
|
|
67
|
+
}
|
|
68
|
+
parts.push(turnHeader);
|
|
69
|
+
// User message
|
|
70
|
+
parts.push('### User');
|
|
71
|
+
parts.push(turn.userMessage
|
|
72
|
+
.split('\n')
|
|
73
|
+
.map((line) => `> ${line}`)
|
|
74
|
+
.join('\n'));
|
|
75
|
+
// Assistant messages
|
|
76
|
+
for (const msg of turn.assistantMessages) {
|
|
77
|
+
parts.push(renderAssistantMessage(msg, opts));
|
|
78
|
+
}
|
|
79
|
+
return parts.join('\n\n');
|
|
80
|
+
}
|
|
81
|
+
function renderAssistantMessage(msg, opts) {
|
|
82
|
+
const parts = [];
|
|
83
|
+
parts.push('### Assistant');
|
|
84
|
+
// Thinking blocks
|
|
85
|
+
if (opts.showThinking !== 'hidden' && msg.thinkingBlocks.length > 0) {
|
|
86
|
+
const thinking = msg.thinkingBlocks.join('\n\n');
|
|
87
|
+
if (opts.showThinking === 'expanded') {
|
|
88
|
+
parts.push(details('Thinking', thinking, true));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
parts.push(details('Thinking', thinking, false));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Text blocks
|
|
95
|
+
for (const text of msg.textBlocks) {
|
|
96
|
+
parts.push(text);
|
|
97
|
+
}
|
|
98
|
+
// Tool calls
|
|
99
|
+
if (opts.showTools) {
|
|
100
|
+
for (const tc of msg.toolCalls) {
|
|
101
|
+
parts.push(renderToolCall(tc, opts));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return parts.join('\n\n');
|
|
105
|
+
}
|
|
106
|
+
function renderToolCall(tc, opts) {
|
|
107
|
+
const inputDisplay = formatToolInput(tc.name, tc.input);
|
|
108
|
+
const header = inputDisplay
|
|
109
|
+
? `**Tool: ${tc.name}** (${inputDisplay})`
|
|
110
|
+
: `**Tool: ${tc.name}**`;
|
|
111
|
+
if (!tc.result) {
|
|
112
|
+
return header;
|
|
113
|
+
}
|
|
114
|
+
const { text, truncated, totalLines } = truncateLines(tc.result, opts.maxToolLines);
|
|
115
|
+
const summaryExtra = truncated
|
|
116
|
+
? ` (${totalLines} lines, showing first ${opts.maxToolLines})`
|
|
117
|
+
: ` (${totalLines} lines)`;
|
|
118
|
+
return `${header}\n\n${details(`Result${summaryExtra}`, '```\n' + text + (truncated ? '\n...' : '') + '\n```', false)}`;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/formatter/markdown.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a duration in milliseconds to a human-readable string.
|
|
3
|
+
*/
|
|
4
|
+
export declare function formatDuration(ms: number): string;
|
|
5
|
+
/**
|
|
6
|
+
* Format an ISO timestamp to a localized date-time string.
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatTimestamp(iso: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Truncate text to a maximum number of lines, with a note if truncated.
|
|
11
|
+
*/
|
|
12
|
+
export declare function truncateLines(text: string, maxLines: number): {
|
|
13
|
+
text: string;
|
|
14
|
+
truncated: boolean;
|
|
15
|
+
totalLines: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Wrap content in a collapsible <details> block.
|
|
19
|
+
*/
|
|
20
|
+
export declare function details(summary: string, content: string, open?: boolean): string;
|
|
21
|
+
/**
|
|
22
|
+
* Format a tool input for display. Extracts the most relevant parameter.
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatToolInput(name: string, input: Record<string, unknown>): string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a duration in milliseconds to a human-readable string.
|
|
3
|
+
*/
|
|
4
|
+
export function formatDuration(ms) {
|
|
5
|
+
const seconds = Math.floor(ms / 1000);
|
|
6
|
+
if (seconds < 60)
|
|
7
|
+
return `${seconds}秒`;
|
|
8
|
+
const minutes = Math.floor(seconds / 60);
|
|
9
|
+
const remainingSeconds = seconds % 60;
|
|
10
|
+
if (minutes < 60) {
|
|
11
|
+
return remainingSeconds > 0 ? `${minutes}分${remainingSeconds}秒` : `${minutes}分`;
|
|
12
|
+
}
|
|
13
|
+
const hours = Math.floor(minutes / 60);
|
|
14
|
+
const remainingMinutes = minutes % 60;
|
|
15
|
+
return remainingMinutes > 0 ? `${hours}時間${remainingMinutes}分` : `${hours}時間`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Format an ISO timestamp to a localized date-time string.
|
|
19
|
+
*/
|
|
20
|
+
export function formatTimestamp(iso) {
|
|
21
|
+
const d = new Date(iso);
|
|
22
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
23
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Truncate text to a maximum number of lines, with a note if truncated.
|
|
27
|
+
*/
|
|
28
|
+
export function truncateLines(text, maxLines) {
|
|
29
|
+
const lines = text.split('\n');
|
|
30
|
+
if (lines.length <= maxLines) {
|
|
31
|
+
return { text, truncated: false, totalLines: lines.length };
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
text: lines.slice(0, maxLines).join('\n'),
|
|
35
|
+
truncated: true,
|
|
36
|
+
totalLines: lines.length,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Wrap content in a collapsible <details> block.
|
|
41
|
+
*/
|
|
42
|
+
export function details(summary, content, open = false) {
|
|
43
|
+
const openAttr = open ? ' open' : '';
|
|
44
|
+
return `<details${openAttr}><summary>${summary}</summary>\n\n${content}\n\n</details>`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Format a tool input for display. Extracts the most relevant parameter.
|
|
48
|
+
*/
|
|
49
|
+
export function formatToolInput(name, input) {
|
|
50
|
+
// Show the most relevant parameter based on tool name
|
|
51
|
+
switch (name) {
|
|
52
|
+
case 'Read':
|
|
53
|
+
return input.file_path ? `\`${input.file_path}\`` : '';
|
|
54
|
+
case 'Write':
|
|
55
|
+
return input.file_path ? `\`${input.file_path}\`` : '';
|
|
56
|
+
case 'Edit':
|
|
57
|
+
return input.file_path ? `\`${input.file_path}\`` : '';
|
|
58
|
+
case 'Bash':
|
|
59
|
+
return input.command ? `\`${String(input.command).substring(0, 100)}\`` : '';
|
|
60
|
+
case 'Glob':
|
|
61
|
+
return input.pattern ? `\`${input.pattern}\`` : '';
|
|
62
|
+
case 'Grep':
|
|
63
|
+
return input.pattern ? `\`${input.pattern}\`` : '';
|
|
64
|
+
case 'Task':
|
|
65
|
+
return input.description ? `"${input.description}"` : '';
|
|
66
|
+
case 'WebFetch':
|
|
67
|
+
return input.url ? `\`${input.url}\`` : '';
|
|
68
|
+
case 'WebSearch':
|
|
69
|
+
return input.query ? `"${input.query}"` : '';
|
|
70
|
+
default:
|
|
71
|
+
// Generic: show first string parameter
|
|
72
|
+
for (const [, val] of Object.entries(input)) {
|
|
73
|
+
if (typeof val === 'string' && val.length > 0) {
|
|
74
|
+
return `\`${val.substring(0, 80)}\``;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return '';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/formatter/templates.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { runList } from './cli/commands/list.js';
|
|
4
|
+
import { runShow } from './cli/commands/show.js';
|
|
5
|
+
import { runExport } from './cli/commands/export.js';
|
|
6
|
+
const program = new Command();
|
|
7
|
+
program
|
|
8
|
+
.name('ccdigest')
|
|
9
|
+
.description('Format Claude Code session data into readable Markdown')
|
|
10
|
+
.version('0.1.0');
|
|
11
|
+
program
|
|
12
|
+
.command('list')
|
|
13
|
+
.description('List available sessions')
|
|
14
|
+
.option('--project <path>', 'Project path (defaults to cwd)')
|
|
15
|
+
.option('--branch <name>', 'Filter by git branch')
|
|
16
|
+
.option('--json', 'Output as JSON')
|
|
17
|
+
.action(runList);
|
|
18
|
+
const showFormatOptions = (cmd) => cmd
|
|
19
|
+
.option('--no-thinking', 'Hide thinking blocks')
|
|
20
|
+
.option('--show-thinking', 'Show thinking blocks expanded')
|
|
21
|
+
.option('--no-tools', 'Hide tool calls')
|
|
22
|
+
.option('--max-tool-lines <n>', 'Max lines for tool results (default: 50)')
|
|
23
|
+
.option('--project <path>', 'Project path (defaults to cwd)');
|
|
24
|
+
showFormatOptions(program
|
|
25
|
+
.command('show <session-ids...>')
|
|
26
|
+
.description('Display session(s) as Markdown (stdout). Multiple IDs are merged chronologically.')).action(runShow);
|
|
27
|
+
showFormatOptions(program
|
|
28
|
+
.command('export <session-ids...>')
|
|
29
|
+
.description('Export session(s) to a Markdown file. Multiple IDs are merged chronologically.')
|
|
30
|
+
.requiredOption('-o, --output <file>', 'Output file path')).action(runExport);
|
|
31
|
+
program.parse();
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,UAAU,CAAC;KAChB,WAAW,CAAC,wDAAwD,CAAC;KACrE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,yBAAyB,CAAC;KACtC,MAAM,CAAC,kBAAkB,EAAE,gCAAgC,CAAC;KAC5D,MAAM,CAAC,iBAAiB,EAAE,sBAAsB,CAAC;KACjD,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;KAClC,MAAM,CAAC,OAAO,CAAC,CAAC;AAEnB,MAAM,iBAAiB,GAAG,CAAC,GAAY,EAAE,EAAE,CACzC,GAAG;KACA,MAAM,CAAC,eAAe,EAAE,sBAAsB,CAAC;KAC/C,MAAM,CAAC,iBAAiB,EAAE,+BAA+B,CAAC;KAC1D,MAAM,CAAC,YAAY,EAAE,iBAAiB,CAAC;KACvC,MAAM,CAAC,sBAAsB,EAAE,0CAA0C,CAAC;KAC1E,MAAM,CAAC,kBAAkB,EAAE,gCAAgC,CAAC,CAAC;AAElE,iBAAiB,CACf,OAAO;KACJ,OAAO,CAAC,uBAAuB,CAAC;KAChC,WAAW,CAAC,mFAAmF,CAAC,CACpG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAElB,iBAAiB,CACf,OAAO;KACJ,OAAO,CAAC,yBAAyB,CAAC;KAClC,WAAW,CAAC,gFAAgF,CAAC;KAC7F,cAAc,CAAC,qBAAqB,EAAE,kBAAkB,CAAC,CAC7D,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AAEpB,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@henteko/ccdigest",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Format Claude Code session data into readable Markdown for team sharing and code review",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "henteko",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/henteko/ccdigest.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"claude-code",
|
|
13
|
+
"session",
|
|
14
|
+
"markdown",
|
|
15
|
+
"export",
|
|
16
|
+
"cli"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "dist/index.js",
|
|
20
|
+
"bin": {
|
|
21
|
+
"ccdigest": "dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"dev": "tsx src/index.ts",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest",
|
|
36
|
+
"prepublishOnly": "npm run build && npm test"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"commander": "^13.1.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"typescript": "^5.7.0",
|
|
43
|
+
"vitest": "^3.0.0",
|
|
44
|
+
"tsx": "^4.19.0",
|
|
45
|
+
"@types/node": "^22.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|