@nocoo/pew 0.4.0 → 0.6.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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +117 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/session-sync.d.ts +62 -0
- package/dist/commands/session-sync.d.ts.map +1 -0
- package/dist/commands/session-sync.js +443 -0
- package/dist/commands/session-sync.js.map +1 -0
- package/dist/commands/session-upload.d.ts +55 -0
- package/dist/commands/session-upload.d.ts.map +1 -0
- package/dist/commands/session-upload.js +177 -0
- package/dist/commands/session-upload.js.map +1 -0
- package/dist/commands/status.d.ts +9 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +22 -11
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/sync.d.ts +12 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +159 -3
- package/dist/commands/sync.js.map +1 -1
- package/dist/discovery/sources.d.ts +5 -0
- package/dist/discovery/sources.d.ts.map +1 -1
- package/dist/discovery/sources.js +7 -0
- package/dist/discovery/sources.js.map +1 -1
- package/dist/parsers/claude-session.d.ts +19 -0
- package/dist/parsers/claude-session.d.ts.map +1 -0
- package/dist/parsers/claude-session.js +131 -0
- package/dist/parsers/claude-session.js.map +1 -0
- package/dist/parsers/codex-session.d.ts +24 -0
- package/dist/parsers/codex-session.d.ts.map +1 -0
- package/dist/parsers/codex-session.js +140 -0
- package/dist/parsers/codex-session.js.map +1 -0
- package/dist/parsers/codex.d.ts +37 -0
- package/dist/parsers/codex.d.ts.map +1 -0
- package/dist/parsers/codex.js +136 -0
- package/dist/parsers/codex.js.map +1 -0
- package/dist/parsers/gemini-session.d.ts +19 -0
- package/dist/parsers/gemini-session.d.ts.map +1 -0
- package/dist/parsers/gemini-session.js +103 -0
- package/dist/parsers/gemini-session.js.map +1 -0
- package/dist/parsers/openclaw-session.d.ts +20 -0
- package/dist/parsers/openclaw-session.d.ts.map +1 -0
- package/dist/parsers/openclaw-session.js +122 -0
- package/dist/parsers/openclaw-session.js.map +1 -0
- package/dist/parsers/opencode-session.d.ts +15 -0
- package/dist/parsers/opencode-session.d.ts.map +1 -0
- package/dist/parsers/opencode-session.js +131 -0
- package/dist/parsers/opencode-session.js.map +1 -0
- package/dist/parsers/opencode-sqlite-db.d.ts +29 -0
- package/dist/parsers/opencode-sqlite-db.d.ts.map +1 -0
- package/dist/parsers/opencode-sqlite-db.js +71 -0
- package/dist/parsers/opencode-sqlite-db.js.map +1 -0
- package/dist/parsers/opencode-sqlite-session.d.ts +32 -0
- package/dist/parsers/opencode-sqlite-session.d.ts.map +1 -0
- package/dist/parsers/opencode-sqlite-session.js +121 -0
- package/dist/parsers/opencode-sqlite-session.js.map +1 -0
- package/dist/parsers/opencode-sqlite.d.ts +53 -0
- package/dist/parsers/opencode-sqlite.d.ts.map +1 -0
- package/dist/parsers/opencode-sqlite.js +104 -0
- package/dist/parsers/opencode-sqlite.js.map +1 -0
- package/dist/storage/session-cursor-store.d.ts +14 -0
- package/dist/storage/session-cursor-store.d.ts.map +1 -0
- package/dist/storage/session-cursor-store.js +34 -0
- package/dist/storage/session-cursor-store.js.map +1 -0
- package/dist/storage/session-queue.d.ts +28 -0
- package/dist/storage/session-queue.d.ts.map +1 -0
- package/dist/storage/session-queue.js +65 -0
- package/dist/storage/session-queue.js.map +1 -0
- package/dist/utils/paths.d.ts +4 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +5 -0
- package/dist/utils/paths.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code session collector.
|
|
3
|
+
*
|
|
4
|
+
* Full-scans a Claude JSONL file and extracts session-level metadata.
|
|
5
|
+
* Groups lines by sessionId, counts message types, computes duration.
|
|
6
|
+
*/
|
|
7
|
+
import type { SessionSnapshot } from "@pew/core";
|
|
8
|
+
/**
|
|
9
|
+
* Collect session snapshots from a Claude Code JSONL file.
|
|
10
|
+
*
|
|
11
|
+
* Each line may contain a sessionId. Lines are grouped by sessionId,
|
|
12
|
+
* and for each group we produce a SessionSnapshot with:
|
|
13
|
+
* - message counts (user/assistant/total)
|
|
14
|
+
* - wall-clock duration (min timestamp → max timestamp)
|
|
15
|
+
* - last seen model
|
|
16
|
+
* - project ref from file path
|
|
17
|
+
*/
|
|
18
|
+
export declare function collectClaudeSessions(filePath: string): Promise<SessionSnapshot[]>;
|
|
19
|
+
//# sourceMappingURL=claude-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-session.d.ts","sourceRoot":"","sources":["../../src/parsers/claude-session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAU,MAAM,WAAW,CAAC;AA2BzD;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,EAAE,CAAC,CA0G5B"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code session collector.
|
|
3
|
+
*
|
|
4
|
+
* Full-scans a Claude JSONL file and extracts session-level metadata.
|
|
5
|
+
* Groups lines by sessionId, counts message types, computes duration.
|
|
6
|
+
*/
|
|
7
|
+
import { createReadStream } from "node:fs";
|
|
8
|
+
import { stat } from "node:fs/promises";
|
|
9
|
+
import { createInterface } from "node:readline";
|
|
10
|
+
/**
|
|
11
|
+
* Extract the project reference from a Claude file path.
|
|
12
|
+
*
|
|
13
|
+
* Claude stores files under ~/.claude/projects/{hash}/{file}.jsonl.
|
|
14
|
+
* We extract the directory name immediately after "projects/".
|
|
15
|
+
* Returns null if the path doesn't contain "projects/".
|
|
16
|
+
*/
|
|
17
|
+
function extractProjectRef(filePath) {
|
|
18
|
+
const parts = filePath.split("/");
|
|
19
|
+
const projectsIdx = parts.lastIndexOf("projects");
|
|
20
|
+
if (projectsIdx < 0 || projectsIdx + 1 >= parts.length - 1)
|
|
21
|
+
return null;
|
|
22
|
+
return parts[projectsIdx + 1] || null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Collect session snapshots from a Claude Code JSONL file.
|
|
26
|
+
*
|
|
27
|
+
* Each line may contain a sessionId. Lines are grouped by sessionId,
|
|
28
|
+
* and for each group we produce a SessionSnapshot with:
|
|
29
|
+
* - message counts (user/assistant/total)
|
|
30
|
+
* - wall-clock duration (min timestamp → max timestamp)
|
|
31
|
+
* - last seen model
|
|
32
|
+
* - project ref from file path
|
|
33
|
+
*/
|
|
34
|
+
export async function collectClaudeSessions(filePath) {
|
|
35
|
+
const st = await stat(filePath).catch(() => null);
|
|
36
|
+
if (!st || !st.isFile() || st.size === 0)
|
|
37
|
+
return [];
|
|
38
|
+
const sessions = new Map();
|
|
39
|
+
const stream = createReadStream(filePath, { encoding: "utf8" });
|
|
40
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
41
|
+
try {
|
|
42
|
+
for await (const line of rl) {
|
|
43
|
+
if (!line)
|
|
44
|
+
continue;
|
|
45
|
+
let obj;
|
|
46
|
+
try {
|
|
47
|
+
obj = JSON.parse(line);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const sessionId = typeof obj.sessionId === "string" ? obj.sessionId : null;
|
|
53
|
+
if (!sessionId)
|
|
54
|
+
continue;
|
|
55
|
+
const timestamp = typeof obj.timestamp === "string" ? obj.timestamp : null;
|
|
56
|
+
const type = typeof obj.type === "string" ? obj.type : null;
|
|
57
|
+
// Get or create session accumulator
|
|
58
|
+
let accum = sessions.get(sessionId);
|
|
59
|
+
if (!accum) {
|
|
60
|
+
accum = {
|
|
61
|
+
sessionId,
|
|
62
|
+
userMessages: 0,
|
|
63
|
+
assistantMessages: 0,
|
|
64
|
+
totalMessages: 0,
|
|
65
|
+
minTimestamp: null,
|
|
66
|
+
maxTimestamp: null,
|
|
67
|
+
lastModel: null,
|
|
68
|
+
};
|
|
69
|
+
sessions.set(sessionId, accum);
|
|
70
|
+
}
|
|
71
|
+
// Count messages
|
|
72
|
+
accum.totalMessages++;
|
|
73
|
+
if (type === "user") {
|
|
74
|
+
accum.userMessages++;
|
|
75
|
+
}
|
|
76
|
+
else if (type === "assistant") {
|
|
77
|
+
accum.assistantMessages++;
|
|
78
|
+
}
|
|
79
|
+
// Track timestamps
|
|
80
|
+
if (timestamp) {
|
|
81
|
+
if (!accum.minTimestamp || timestamp < accum.minTimestamp) {
|
|
82
|
+
accum.minTimestamp = timestamp;
|
|
83
|
+
}
|
|
84
|
+
if (!accum.maxTimestamp || timestamp > accum.maxTimestamp) {
|
|
85
|
+
accum.maxTimestamp = timestamp;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Track model (from message.model or obj.model)
|
|
89
|
+
const msg = obj.message;
|
|
90
|
+
const model = typeof msg?.model === "string"
|
|
91
|
+
? msg.model.trim()
|
|
92
|
+
: typeof obj.model === "string"
|
|
93
|
+
? obj.model.trim()
|
|
94
|
+
: null;
|
|
95
|
+
if (model) {
|
|
96
|
+
accum.lastModel = model;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
rl.close();
|
|
102
|
+
stream.destroy();
|
|
103
|
+
}
|
|
104
|
+
// Convert accumulators to snapshots
|
|
105
|
+
const projectRef = extractProjectRef(filePath);
|
|
106
|
+
const snapshotAt = new Date().toISOString();
|
|
107
|
+
const results = [];
|
|
108
|
+
for (const accum of sessions.values()) {
|
|
109
|
+
if (!accum.minTimestamp)
|
|
110
|
+
continue; // no valid timestamps → skip
|
|
111
|
+
const startedAt = accum.minTimestamp;
|
|
112
|
+
const lastMessageAt = accum.maxTimestamp ?? accum.minTimestamp;
|
|
113
|
+
const durationMs = new Date(lastMessageAt).getTime() - new Date(startedAt).getTime();
|
|
114
|
+
results.push({
|
|
115
|
+
sessionKey: `claude:${accum.sessionId}`,
|
|
116
|
+
source: "claude-code",
|
|
117
|
+
kind: "human",
|
|
118
|
+
startedAt,
|
|
119
|
+
lastMessageAt,
|
|
120
|
+
durationSeconds: Math.max(0, Math.floor(durationMs / 1000)),
|
|
121
|
+
userMessages: accum.userMessages,
|
|
122
|
+
assistantMessages: accum.assistantMessages,
|
|
123
|
+
totalMessages: accum.totalMessages,
|
|
124
|
+
projectRef,
|
|
125
|
+
model: accum.lastModel,
|
|
126
|
+
snapshotAt,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return results;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=claude-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-session.js","sourceRoot":"","sources":["../../src/parsers/claude-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI session collector.
|
|
3
|
+
*
|
|
4
|
+
* Full-scans a Codex JSONL rollout file and produces a single SessionSnapshot.
|
|
5
|
+
* Each rollout file represents one session. Codex is user-driven (kind: "human").
|
|
6
|
+
*
|
|
7
|
+
* Session ID comes from session_meta.payload.id (UUID).
|
|
8
|
+
* Project ref is a SHA-256 hash of session_meta.payload.cwd (privacy-safe).
|
|
9
|
+
* Model comes from turn_context.payload.model (preferred) or session_meta.payload.model (fallback).
|
|
10
|
+
*
|
|
11
|
+
* Message counting:
|
|
12
|
+
* - response_item with payload.role === "user" → userMessages
|
|
13
|
+
* - response_item with payload.role === "assistant" → assistantMessages
|
|
14
|
+
* - All valid JSON lines → totalMessages
|
|
15
|
+
*/
|
|
16
|
+
import type { SessionSnapshot } from "@pew/core";
|
|
17
|
+
/**
|
|
18
|
+
* Collect session snapshots from a Codex CLI JSONL rollout file.
|
|
19
|
+
*
|
|
20
|
+
* Reads every line, extracts session metadata, counts messages,
|
|
21
|
+
* and produces 0 or 1 SessionSnapshot.
|
|
22
|
+
*/
|
|
23
|
+
export declare function collectCodexSessions(filePath: string): Promise<SessionSnapshot[]>;
|
|
24
|
+
//# sourceMappingURL=codex-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-session.d.ts","sourceRoot":"","sources":["../../src/parsers/codex-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAOH,OAAO,KAAK,EAAE,eAAe,EAAU,MAAM,WAAW,CAAC;AAEzD;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,EAAE,CAAC,CA2H5B"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI session collector.
|
|
3
|
+
*
|
|
4
|
+
* Full-scans a Codex JSONL rollout file and produces a single SessionSnapshot.
|
|
5
|
+
* Each rollout file represents one session. Codex is user-driven (kind: "human").
|
|
6
|
+
*
|
|
7
|
+
* Session ID comes from session_meta.payload.id (UUID).
|
|
8
|
+
* Project ref is a SHA-256 hash of session_meta.payload.cwd (privacy-safe).
|
|
9
|
+
* Model comes from turn_context.payload.model (preferred) or session_meta.payload.model (fallback).
|
|
10
|
+
*
|
|
11
|
+
* Message counting:
|
|
12
|
+
* - response_item with payload.role === "user" → userMessages
|
|
13
|
+
* - response_item with payload.role === "assistant" → assistantMessages
|
|
14
|
+
* - All valid JSON lines → totalMessages
|
|
15
|
+
*/
|
|
16
|
+
import { createReadStream } from "node:fs";
|
|
17
|
+
import { stat } from "node:fs/promises";
|
|
18
|
+
import { createInterface } from "node:readline";
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
import { resolve } from "node:path";
|
|
21
|
+
/**
|
|
22
|
+
* Collect session snapshots from a Codex CLI JSONL rollout file.
|
|
23
|
+
*
|
|
24
|
+
* Reads every line, extracts session metadata, counts messages,
|
|
25
|
+
* and produces 0 or 1 SessionSnapshot.
|
|
26
|
+
*/
|
|
27
|
+
export async function collectCodexSessions(filePath) {
|
|
28
|
+
const st = await stat(filePath).catch(() => null);
|
|
29
|
+
if (!st || !st.isFile() || st.size === 0)
|
|
30
|
+
return [];
|
|
31
|
+
let sessionId = null;
|
|
32
|
+
let projectRef = null;
|
|
33
|
+
let lastModel = null;
|
|
34
|
+
let userMessages = 0;
|
|
35
|
+
let assistantMessages = 0;
|
|
36
|
+
let totalMessages = 0;
|
|
37
|
+
let minTimestamp = null;
|
|
38
|
+
let maxTimestamp = null;
|
|
39
|
+
const stream = createReadStream(filePath, { encoding: "utf8" });
|
|
40
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
41
|
+
try {
|
|
42
|
+
for await (const line of rl) {
|
|
43
|
+
if (!line)
|
|
44
|
+
continue;
|
|
45
|
+
let obj;
|
|
46
|
+
try {
|
|
47
|
+
obj = JSON.parse(line);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
totalMessages++;
|
|
53
|
+
const type = typeof obj.type === "string" ? obj.type : null;
|
|
54
|
+
const timestamp = typeof obj.timestamp === "string" ? obj.timestamp : null;
|
|
55
|
+
// Track timestamps
|
|
56
|
+
if (timestamp) {
|
|
57
|
+
if (!minTimestamp || timestamp < minTimestamp) {
|
|
58
|
+
minTimestamp = timestamp;
|
|
59
|
+
}
|
|
60
|
+
if (!maxTimestamp || timestamp > maxTimestamp) {
|
|
61
|
+
maxTimestamp = timestamp;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const payload = obj.payload;
|
|
65
|
+
// Extract session ID and project ref from session_meta
|
|
66
|
+
if (type === "session_meta" && payload) {
|
|
67
|
+
if (typeof payload.id === "string" && payload.id) {
|
|
68
|
+
sessionId = payload.id;
|
|
69
|
+
}
|
|
70
|
+
if (typeof payload.cwd === "string" && payload.cwd) {
|
|
71
|
+
projectRef = createHash("sha256")
|
|
72
|
+
.update(payload.cwd)
|
|
73
|
+
.digest("hex")
|
|
74
|
+
.slice(0, 12);
|
|
75
|
+
}
|
|
76
|
+
// Fallback model from session_meta
|
|
77
|
+
if (typeof payload.model === "string" && payload.model.trim()) {
|
|
78
|
+
lastModel = payload.model.trim();
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// Track model from turn_context (overrides session_meta)
|
|
83
|
+
if (type === "turn_context" && payload) {
|
|
84
|
+
if (typeof payload.model === "string" && payload.model.trim()) {
|
|
85
|
+
lastModel = payload.model.trim();
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
// Count user/assistant messages from response_item
|
|
90
|
+
if (type === "response_item" && payload) {
|
|
91
|
+
const role = typeof payload.role === "string" ? payload.role : null;
|
|
92
|
+
if (role === "user") {
|
|
93
|
+
userMessages++;
|
|
94
|
+
}
|
|
95
|
+
else if (role === "assistant") {
|
|
96
|
+
assistantMessages++;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
rl.close();
|
|
103
|
+
stream.destroy();
|
|
104
|
+
}
|
|
105
|
+
// No valid timestamps → can't produce a snapshot
|
|
106
|
+
if (!minTimestamp)
|
|
107
|
+
return [];
|
|
108
|
+
const startedAt = minTimestamp;
|
|
109
|
+
const lastMessageAt = maxTimestamp ?? minTimestamp;
|
|
110
|
+
const durationMs = new Date(lastMessageAt).getTime() - new Date(startedAt).getTime();
|
|
111
|
+
// Session key: prefer native UUID from session_meta, fallback to sha256 of path
|
|
112
|
+
let sessionKey;
|
|
113
|
+
if (sessionId) {
|
|
114
|
+
sessionKey = `codex:${sessionId}`;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const hash = createHash("sha256")
|
|
118
|
+
.update(resolve(filePath))
|
|
119
|
+
.digest("hex")
|
|
120
|
+
.slice(0, 16);
|
|
121
|
+
sessionKey = `codex:${hash}`;
|
|
122
|
+
}
|
|
123
|
+
return [
|
|
124
|
+
{
|
|
125
|
+
sessionKey,
|
|
126
|
+
source: "codex",
|
|
127
|
+
kind: "human",
|
|
128
|
+
startedAt,
|
|
129
|
+
lastMessageAt,
|
|
130
|
+
durationSeconds: Math.max(0, Math.floor(durationMs / 1000)),
|
|
131
|
+
userMessages,
|
|
132
|
+
assistantMessages,
|
|
133
|
+
totalMessages,
|
|
134
|
+
projectRef,
|
|
135
|
+
model: lastModel,
|
|
136
|
+
snapshotAt: new Date().toISOString(),
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=codex-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-session.js","sourceRoot":"","sources":["../../src/parsers/codex-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI token parser.
|
|
3
|
+
*
|
|
4
|
+
* Parses Codex JSONL rollout files (~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl)
|
|
5
|
+
* incrementally from a byte offset.
|
|
6
|
+
*
|
|
7
|
+
* Strategy: Cumulative total_token_usage with diff (like Gemini).
|
|
8
|
+
* Each `event_msg` with `payload.type === "token_count"` contains running totals.
|
|
9
|
+
* We diff consecutive totals to produce per-turn deltas.
|
|
10
|
+
*
|
|
11
|
+
* Model is tracked from `turn_context.payload.model` or `session_meta.payload.model`.
|
|
12
|
+
*/
|
|
13
|
+
import type { TokenDelta } from "@pew/core";
|
|
14
|
+
import type { ParsedDelta } from "./claude.js";
|
|
15
|
+
/** Result of parsing a single Codex JSONL rollout file */
|
|
16
|
+
export interface CodexFileResult {
|
|
17
|
+
deltas: ParsedDelta[];
|
|
18
|
+
endOffset: number;
|
|
19
|
+
/** Last seen cumulative totals (for resuming incremental parsing) */
|
|
20
|
+
lastTotals: TokenDelta | null;
|
|
21
|
+
/** Last seen model identifier */
|
|
22
|
+
lastModel: string | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parse a Codex CLI JSONL rollout file incrementally from a byte offset.
|
|
26
|
+
*
|
|
27
|
+
* Extracts token deltas from `event_msg` lines with `payload.type === "token_count"`.
|
|
28
|
+
* Uses cumulative `total_token_usage` with diff strategy.
|
|
29
|
+
* Tracks model from `turn_context` and `session_meta` events.
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseCodexFile(opts: {
|
|
32
|
+
filePath: string;
|
|
33
|
+
startOffset: number;
|
|
34
|
+
lastTotals: TokenDelta | null;
|
|
35
|
+
lastModel: string | null;
|
|
36
|
+
}): Promise<CodexFileResult>;
|
|
37
|
+
//# sourceMappingURL=codex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/parsers/codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAU,UAAU,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,0DAA0D;AAC1D,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,iCAAiC;IACjC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AA0CD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,GAAG,OAAO,CAAC,eAAe,CAAC,CAoF3B"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI token parser.
|
|
3
|
+
*
|
|
4
|
+
* Parses Codex JSONL rollout files (~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl)
|
|
5
|
+
* incrementally from a byte offset.
|
|
6
|
+
*
|
|
7
|
+
* Strategy: Cumulative total_token_usage with diff (like Gemini).
|
|
8
|
+
* Each `event_msg` with `payload.type === "token_count"` contains running totals.
|
|
9
|
+
* We diff consecutive totals to produce per-turn deltas.
|
|
10
|
+
*
|
|
11
|
+
* Model is tracked from `turn_context.payload.model` or `session_meta.payload.model`.
|
|
12
|
+
*/
|
|
13
|
+
import { createReadStream } from "node:fs";
|
|
14
|
+
import { stat } from "node:fs/promises";
|
|
15
|
+
import { createInterface } from "node:readline";
|
|
16
|
+
/** Check if a TokenDelta is all zeros */
|
|
17
|
+
function isAllZero(d) {
|
|
18
|
+
return (d.inputTokens === 0 &&
|
|
19
|
+
d.cachedInputTokens === 0 &&
|
|
20
|
+
d.outputTokens === 0 &&
|
|
21
|
+
d.reasoningOutputTokens === 0);
|
|
22
|
+
}
|
|
23
|
+
/** Coerce to non-negative integer */
|
|
24
|
+
function toNonNegInt(v) {
|
|
25
|
+
const n = Number(v);
|
|
26
|
+
if (!Number.isFinite(n) || n < 0)
|
|
27
|
+
return 0;
|
|
28
|
+
return Math.floor(n);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Diff two cumulative TokenDelta values.
|
|
32
|
+
* If any field goes negative (counter reset), treat the new value as absolute.
|
|
33
|
+
*/
|
|
34
|
+
function diffTotals(current, previous) {
|
|
35
|
+
const dInput = current.inputTokens - previous.inputTokens;
|
|
36
|
+
const dCached = current.cachedInputTokens - previous.cachedInputTokens;
|
|
37
|
+
const dOutput = current.outputTokens - previous.outputTokens;
|
|
38
|
+
const dReasoning = current.reasoningOutputTokens - previous.reasoningOutputTokens;
|
|
39
|
+
// If any field is negative, assume counter reset — use absolute values
|
|
40
|
+
if (dInput < 0 || dCached < 0 || dOutput < 0 || dReasoning < 0) {
|
|
41
|
+
return { ...current };
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
inputTokens: dInput,
|
|
45
|
+
cachedInputTokens: dCached,
|
|
46
|
+
outputTokens: dOutput,
|
|
47
|
+
reasoningOutputTokens: dReasoning,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parse a Codex CLI JSONL rollout file incrementally from a byte offset.
|
|
52
|
+
*
|
|
53
|
+
* Extracts token deltas from `event_msg` lines with `payload.type === "token_count"`.
|
|
54
|
+
* Uses cumulative `total_token_usage` with diff strategy.
|
|
55
|
+
* Tracks model from `turn_context` and `session_meta` events.
|
|
56
|
+
*/
|
|
57
|
+
export async function parseCodexFile(opts) {
|
|
58
|
+
const { filePath, startOffset } = opts;
|
|
59
|
+
const deltas = [];
|
|
60
|
+
let lastTotals = opts.lastTotals;
|
|
61
|
+
let lastModel = opts.lastModel;
|
|
62
|
+
const st = await stat(filePath).catch(() => null);
|
|
63
|
+
if (!st || !st.isFile())
|
|
64
|
+
return { deltas, endOffset: startOffset, lastTotals, lastModel };
|
|
65
|
+
const endOffset = st.size;
|
|
66
|
+
if (endOffset === 0 || startOffset >= endOffset) {
|
|
67
|
+
return { deltas, endOffset: endOffset === 0 ? 0 : endOffset, lastTotals, lastModel };
|
|
68
|
+
}
|
|
69
|
+
const stream = createReadStream(filePath, {
|
|
70
|
+
encoding: "utf8",
|
|
71
|
+
start: startOffset,
|
|
72
|
+
});
|
|
73
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
74
|
+
try {
|
|
75
|
+
for await (const line of rl) {
|
|
76
|
+
if (!line)
|
|
77
|
+
continue;
|
|
78
|
+
let obj;
|
|
79
|
+
try {
|
|
80
|
+
obj = JSON.parse(line);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const type = typeof obj.type === "string" ? obj.type : null;
|
|
86
|
+
const timestamp = typeof obj.timestamp === "string" ? obj.timestamp : null;
|
|
87
|
+
const payload = obj.payload;
|
|
88
|
+
// Track model from session_meta
|
|
89
|
+
if (type === "session_meta" && payload) {
|
|
90
|
+
const model = typeof payload.model === "string" ? payload.model.trim() : null;
|
|
91
|
+
if (model)
|
|
92
|
+
lastModel = model;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// Track model from turn_context (overrides session_meta)
|
|
96
|
+
if (type === "turn_context" && payload) {
|
|
97
|
+
const model = typeof payload.model === "string" ? payload.model.trim() : null;
|
|
98
|
+
if (model)
|
|
99
|
+
lastModel = model;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// Extract token counts from event_msg with type=token_count
|
|
103
|
+
if (type === "event_msg" && payload?.type === "token_count" && timestamp) {
|
|
104
|
+
const info = payload.info;
|
|
105
|
+
if (!info)
|
|
106
|
+
continue;
|
|
107
|
+
const usage = info.total_token_usage;
|
|
108
|
+
if (!usage || typeof usage !== "object")
|
|
109
|
+
continue;
|
|
110
|
+
const currentTotals = {
|
|
111
|
+
inputTokens: toNonNegInt(usage.input_tokens),
|
|
112
|
+
cachedInputTokens: toNonNegInt(usage.cached_input_tokens),
|
|
113
|
+
outputTokens: toNonNegInt(usage.output_tokens),
|
|
114
|
+
reasoningOutputTokens: toNonNegInt(usage.reasoning_output_tokens),
|
|
115
|
+
};
|
|
116
|
+
// Compute delta
|
|
117
|
+
const delta = lastTotals ? diffTotals(currentTotals, lastTotals) : { ...currentTotals };
|
|
118
|
+
lastTotals = currentTotals;
|
|
119
|
+
if (isAllZero(delta))
|
|
120
|
+
continue;
|
|
121
|
+
deltas.push({
|
|
122
|
+
source: "codex",
|
|
123
|
+
model: lastModel || "unknown",
|
|
124
|
+
timestamp,
|
|
125
|
+
tokens: delta,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
rl.close();
|
|
132
|
+
stream.destroy();
|
|
133
|
+
}
|
|
134
|
+
return { deltas, endOffset, lastTotals, lastModel };
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/parsers/codex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI session collector.
|
|
3
|
+
*
|
|
4
|
+
* Reads a Gemini session JSON file and extracts session-level metadata.
|
|
5
|
+
* Each file contains one session with a messages[] array.
|
|
6
|
+
*/
|
|
7
|
+
import type { SessionSnapshot } from "@pew/core";
|
|
8
|
+
/**
|
|
9
|
+
* Collect session snapshots from a Gemini CLI session JSON file.
|
|
10
|
+
*
|
|
11
|
+
* Gemini stores one JSON file per session with:
|
|
12
|
+
* - sessionId (optional)
|
|
13
|
+
* - projectHash (optional)
|
|
14
|
+
* - messages[]: { type, timestamp, model? }
|
|
15
|
+
*
|
|
16
|
+
* Returns an array of 0 or 1 SessionSnapshot.
|
|
17
|
+
*/
|
|
18
|
+
export declare function collectGeminiSessions(filePath: string): Promise<SessionSnapshot[]>;
|
|
19
|
+
//# sourceMappingURL=gemini-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini-session.d.ts","sourceRoot":"","sources":["../../src/parsers/gemini-session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAU,MAAM,WAAW,CAAC;AAEzD;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,EAAE,CAAC,CA8F5B"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI session collector.
|
|
3
|
+
*
|
|
4
|
+
* Reads a Gemini session JSON file and extracts session-level metadata.
|
|
5
|
+
* Each file contains one session with a messages[] array.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
|
+
/**
|
|
11
|
+
* Collect session snapshots from a Gemini CLI session JSON file.
|
|
12
|
+
*
|
|
13
|
+
* Gemini stores one JSON file per session with:
|
|
14
|
+
* - sessionId (optional)
|
|
15
|
+
* - projectHash (optional)
|
|
16
|
+
* - messages[]: { type, timestamp, model? }
|
|
17
|
+
*
|
|
18
|
+
* Returns an array of 0 or 1 SessionSnapshot.
|
|
19
|
+
*/
|
|
20
|
+
export async function collectGeminiSessions(filePath) {
|
|
21
|
+
let raw;
|
|
22
|
+
try {
|
|
23
|
+
raw = await readFile(filePath, "utf8");
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
if (!raw.trim())
|
|
29
|
+
return [];
|
|
30
|
+
let session;
|
|
31
|
+
try {
|
|
32
|
+
session = JSON.parse(raw);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const messages = Array.isArray(session?.messages)
|
|
38
|
+
? session.messages
|
|
39
|
+
: [];
|
|
40
|
+
if (messages.length === 0)
|
|
41
|
+
return [];
|
|
42
|
+
// Derive session key
|
|
43
|
+
const sessionId = typeof session.sessionId === "string" ? session.sessionId : null;
|
|
44
|
+
const sessionKey = sessionId
|
|
45
|
+
? `gemini:${sessionId}`
|
|
46
|
+
: `gemini:${createHash("sha256").update(resolve(filePath)).digest("hex").slice(0, 16)}`;
|
|
47
|
+
// Extract projectRef
|
|
48
|
+
const projectRef = typeof session.projectHash === "string" ? session.projectHash : null;
|
|
49
|
+
// Count messages and track timestamps/model
|
|
50
|
+
let userMessages = 0;
|
|
51
|
+
let assistantMessages = 0;
|
|
52
|
+
let totalMessages = 0;
|
|
53
|
+
let minTimestamp = null;
|
|
54
|
+
let maxTimestamp = null;
|
|
55
|
+
let lastModel = null;
|
|
56
|
+
for (const msg of messages) {
|
|
57
|
+
if (!msg || typeof msg !== "object")
|
|
58
|
+
continue;
|
|
59
|
+
totalMessages++;
|
|
60
|
+
const type = typeof msg.type === "string" ? msg.type : null;
|
|
61
|
+
if (type === "user") {
|
|
62
|
+
userMessages++;
|
|
63
|
+
}
|
|
64
|
+
else if (type === "gemini") {
|
|
65
|
+
assistantMessages++;
|
|
66
|
+
}
|
|
67
|
+
const timestamp = typeof msg.timestamp === "string" ? msg.timestamp : null;
|
|
68
|
+
if (timestamp) {
|
|
69
|
+
if (!minTimestamp || timestamp < minTimestamp) {
|
|
70
|
+
minTimestamp = timestamp;
|
|
71
|
+
}
|
|
72
|
+
if (!maxTimestamp || timestamp > maxTimestamp) {
|
|
73
|
+
maxTimestamp = timestamp;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const model = typeof msg.model === "string" ? msg.model.trim() : null;
|
|
77
|
+
if (model) {
|
|
78
|
+
lastModel = model;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!minTimestamp)
|
|
82
|
+
return [];
|
|
83
|
+
const startedAt = minTimestamp;
|
|
84
|
+
const lastMessageAt = maxTimestamp ?? minTimestamp;
|
|
85
|
+
const durationMs = new Date(lastMessageAt).getTime() - new Date(startedAt).getTime();
|
|
86
|
+
return [
|
|
87
|
+
{
|
|
88
|
+
sessionKey,
|
|
89
|
+
source: "gemini-cli",
|
|
90
|
+
kind: "human",
|
|
91
|
+
startedAt,
|
|
92
|
+
lastMessageAt,
|
|
93
|
+
durationSeconds: Math.max(0, Math.floor(durationMs / 1000)),
|
|
94
|
+
userMessages,
|
|
95
|
+
assistantMessages,
|
|
96
|
+
totalMessages,
|
|
97
|
+
projectRef,
|
|
98
|
+
model: lastModel,
|
|
99
|
+
snapshotAt: new Date().toISOString(),
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=gemini-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini-session.js","sourceRoot":"","sources":["../../src/parsers/gemini-session.ts"],"names":[],"mappings":"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"}
|