@nocoo/pew 0.4.0 → 0.6.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/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 +39 -0
- package/dist/commands/session-upload.d.ts.map +1 -0
- package/dist/commands/session-upload.js +45 -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/commands/upload-engine.d.ts +63 -0
- package/dist/commands/upload-engine.d.ts.map +1 -0
- package/dist/commands/upload-engine.js +164 -0
- package/dist/commands/upload-engine.js.map +1 -0
- package/dist/commands/upload.d.ts +5 -21
- package/dist/commands/upload.d.ts.map +1 -1
- package/dist/commands/upload.js +11 -144
- package/dist/commands/upload.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/base-queue.d.ts +40 -0
- package/dist/storage/base-queue.d.ts.map +1 -0
- package/dist/storage/base-queue.js +89 -0
- package/dist/storage/base-queue.js.map +1 -0
- package/dist/storage/local-queue.d.ts +4 -24
- package/dist/storage/local-queue.d.ts.map +1 -1
- package/dist/storage/local-queue.js +5 -64
- package/dist/storage/local-queue.js.map +1 -1
- 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 +10 -0
- package/dist/storage/session-queue.d.ts.map +1 -0
- package/dist/storage/session-queue.js +11 -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,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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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw session collector.
|
|
3
|
+
*
|
|
4
|
+
* Full-scans an OpenClaw JSONL file and produces a single SessionSnapshot.
|
|
5
|
+
* OpenClaw is automated (kind: "automated"), has no user messages,
|
|
6
|
+
* and counts all line types in totalMessages.
|
|
7
|
+
*/
|
|
8
|
+
import type { SessionSnapshot } from "@pew/core";
|
|
9
|
+
/**
|
|
10
|
+
* Collect session snapshots from an OpenClaw JSONL session file.
|
|
11
|
+
*
|
|
12
|
+
* Reads every line, counts by type:
|
|
13
|
+
* - `type: "message"` → assistantMessages
|
|
14
|
+
* - All valid lines → totalMessages
|
|
15
|
+
* - userMessages always 0 (no user messages observable in OpenClaw)
|
|
16
|
+
*
|
|
17
|
+
* Returns 0 or 1 SessionSnapshot.
|
|
18
|
+
*/
|
|
19
|
+
export declare function collectOpenClawSessions(filePath: string): Promise<SessionSnapshot[]>;
|
|
20
|
+
//# sourceMappingURL=openclaw-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openclaw-session.d.ts","sourceRoot":"","sources":["../../src/parsers/openclaw-session.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,KAAK,EAAE,eAAe,EAAU,MAAM,WAAW,CAAC;AAiBzD;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,EAAE,CAAC,CA6F5B"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw session collector.
|
|
3
|
+
*
|
|
4
|
+
* Full-scans an OpenClaw JSONL file and produces a single SessionSnapshot.
|
|
5
|
+
* OpenClaw is automated (kind: "automated"), has no user messages,
|
|
6
|
+
* and counts all line types in totalMessages.
|
|
7
|
+
*/
|
|
8
|
+
import { createReadStream } from "node:fs";
|
|
9
|
+
import { stat } from "node:fs/promises";
|
|
10
|
+
import { createInterface } from "node:readline";
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { resolve } from "node:path";
|
|
13
|
+
/**
|
|
14
|
+
* Extract agent name from an OpenClaw file path.
|
|
15
|
+
*
|
|
16
|
+
* Expected pattern: .../agents/{agentName}/sessions/*.jsonl
|
|
17
|
+
* Returns null if the path doesn't match.
|
|
18
|
+
*/
|
|
19
|
+
function extractAgentName(filePath) {
|
|
20
|
+
const parts = filePath.split("/");
|
|
21
|
+
const agentsIdx = parts.lastIndexOf("agents");
|
|
22
|
+
if (agentsIdx < 0 || agentsIdx + 2 >= parts.length)
|
|
23
|
+
return null;
|
|
24
|
+
// Check that the part after agent name is "sessions"
|
|
25
|
+
if (parts[agentsIdx + 2] !== "sessions")
|
|
26
|
+
return null;
|
|
27
|
+
return parts[agentsIdx + 1] || null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Collect session snapshots from an OpenClaw JSONL session file.
|
|
31
|
+
*
|
|
32
|
+
* Reads every line, counts by type:
|
|
33
|
+
* - `type: "message"` → assistantMessages
|
|
34
|
+
* - All valid lines → totalMessages
|
|
35
|
+
* - userMessages always 0 (no user messages observable in OpenClaw)
|
|
36
|
+
*
|
|
37
|
+
* Returns 0 or 1 SessionSnapshot.
|
|
38
|
+
*/
|
|
39
|
+
export async function collectOpenClawSessions(filePath) {
|
|
40
|
+
const st = await stat(filePath).catch(() => null);
|
|
41
|
+
if (!st || !st.isFile() || st.size === 0)
|
|
42
|
+
return [];
|
|
43
|
+
let assistantMessages = 0;
|
|
44
|
+
let totalMessages = 0;
|
|
45
|
+
let minTimestamp = null;
|
|
46
|
+
let maxTimestamp = null;
|
|
47
|
+
let lastModel = null;
|
|
48
|
+
const stream = createReadStream(filePath, { encoding: "utf8" });
|
|
49
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
50
|
+
try {
|
|
51
|
+
for await (const line of rl) {
|
|
52
|
+
if (!line)
|
|
53
|
+
continue;
|
|
54
|
+
let obj;
|
|
55
|
+
try {
|
|
56
|
+
obj = JSON.parse(line);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
totalMessages++;
|
|
62
|
+
// Track type
|
|
63
|
+
const type = typeof obj.type === "string" ? obj.type : null;
|
|
64
|
+
if (type === "message") {
|
|
65
|
+
assistantMessages++;
|
|
66
|
+
// Extract model from message.model
|
|
67
|
+
const msg = obj.message;
|
|
68
|
+
if (msg && typeof msg === "object") {
|
|
69
|
+
const model = typeof msg.model === "string" ? msg.model.trim() : null;
|
|
70
|
+
if (model) {
|
|
71
|
+
lastModel = model;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Track timestamps
|
|
76
|
+
const timestamp = typeof obj.timestamp === "string" ? obj.timestamp : null;
|
|
77
|
+
if (timestamp) {
|
|
78
|
+
if (!minTimestamp || timestamp < minTimestamp) {
|
|
79
|
+
minTimestamp = timestamp;
|
|
80
|
+
}
|
|
81
|
+
if (!maxTimestamp || timestamp > maxTimestamp) {
|
|
82
|
+
maxTimestamp = timestamp;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
rl.close();
|
|
89
|
+
stream.destroy();
|
|
90
|
+
}
|
|
91
|
+
// No valid timestamps → can't produce a snapshot
|
|
92
|
+
if (!minTimestamp)
|
|
93
|
+
return [];
|
|
94
|
+
const startedAt = minTimestamp;
|
|
95
|
+
const lastMessageAt = maxTimestamp ?? minTimestamp;
|
|
96
|
+
const durationMs = new Date(lastMessageAt).getTime() - new Date(startedAt).getTime();
|
|
97
|
+
// Session key: sha256 of absolute path (OpenClaw has no native session ID)
|
|
98
|
+
const hash = createHash("sha256")
|
|
99
|
+
.update(resolve(filePath))
|
|
100
|
+
.digest("hex")
|
|
101
|
+
.slice(0, 16);
|
|
102
|
+
const sessionKey = `openclaw:${hash}`;
|
|
103
|
+
// Project ref: agent name from path
|
|
104
|
+
const projectRef = extractAgentName(filePath);
|
|
105
|
+
return [
|
|
106
|
+
{
|
|
107
|
+
sessionKey,
|
|
108
|
+
source: "openclaw",
|
|
109
|
+
kind: "automated",
|
|
110
|
+
startedAt,
|
|
111
|
+
lastMessageAt,
|
|
112
|
+
durationSeconds: Math.max(0, Math.floor(durationMs / 1000)),
|
|
113
|
+
userMessages: 0,
|
|
114
|
+
assistantMessages,
|
|
115
|
+
totalMessages,
|
|
116
|
+
projectRef,
|
|
117
|
+
model: lastModel,
|
|
118
|
+
snapshotAt: new Date().toISOString(),
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=openclaw-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openclaw-session.js","sourceRoot":"","sources":["../../src/parsers/openclaw-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode session collector.
|
|
3
|
+
*
|
|
4
|
+
* Takes an OpenCode session directory (ses_xxx/) and reads all msg_*.json
|
|
5
|
+
* files to produce a single SessionSnapshot with message counts and duration.
|
|
6
|
+
*/
|
|
7
|
+
import type { SessionSnapshot } from "@pew/core";
|
|
8
|
+
/**
|
|
9
|
+
* Collect session snapshots from an OpenCode session directory.
|
|
10
|
+
*
|
|
11
|
+
* Reads all .json files in the directory, counts roles, tracks timestamps
|
|
12
|
+
* and model. Returns 0 or 1 SessionSnapshot.
|
|
13
|
+
*/
|
|
14
|
+
export declare function collectOpenCodeSessions(sessionDir: string): Promise<SessionSnapshot[]>;
|
|
15
|
+
//# sourceMappingURL=opencode-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode-session.d.ts","sourceRoot":"","sources":["../../src/parsers/opencode-session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAU,MAAM,WAAW,CAAC;AAazD;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,EAAE,CAAC,CAmH5B"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode session collector.
|
|
3
|
+
*
|
|
4
|
+
* Takes an OpenCode session directory (ses_xxx/) and reads all msg_*.json
|
|
5
|
+
* files to produce a single SessionSnapshot with message counts and duration.
|
|
6
|
+
*/
|
|
7
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
8
|
+
import { join, basename } from "node:path";
|
|
9
|
+
/**
|
|
10
|
+
* Coerce an epoch value to milliseconds.
|
|
11
|
+
* Values < 1e12 are treated as seconds and multiplied by 1000.
|
|
12
|
+
*/
|
|
13
|
+
function coerceEpochMs(v) {
|
|
14
|
+
const n = Number(v);
|
|
15
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
16
|
+
return 0;
|
|
17
|
+
if (n < 1e12)
|
|
18
|
+
return Math.floor(n * 1000);
|
|
19
|
+
return Math.floor(n);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Collect session snapshots from an OpenCode session directory.
|
|
23
|
+
*
|
|
24
|
+
* Reads all .json files in the directory, counts roles, tracks timestamps
|
|
25
|
+
* and model. Returns 0 or 1 SessionSnapshot.
|
|
26
|
+
*/
|
|
27
|
+
export async function collectOpenCodeSessions(sessionDir) {
|
|
28
|
+
let entries;
|
|
29
|
+
try {
|
|
30
|
+
entries = await readdir(sessionDir, { withFileTypes: true });
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
// Collect .json files, sorted for deterministic order
|
|
36
|
+
const jsonFiles = entries
|
|
37
|
+
.filter((e) => e.isFile() && e.name.endsWith(".json"))
|
|
38
|
+
.map((e) => e.name)
|
|
39
|
+
.sort();
|
|
40
|
+
if (jsonFiles.length === 0)
|
|
41
|
+
return [];
|
|
42
|
+
let sessionId = null;
|
|
43
|
+
let userMessages = 0;
|
|
44
|
+
let assistantMessages = 0;
|
|
45
|
+
let totalMessages = 0;
|
|
46
|
+
let minEpochMs = null;
|
|
47
|
+
let maxEpochMs = null;
|
|
48
|
+
let lastModel = null;
|
|
49
|
+
for (const fileName of jsonFiles) {
|
|
50
|
+
let raw;
|
|
51
|
+
try {
|
|
52
|
+
raw = await readFile(join(sessionDir, fileName), "utf8");
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
let msg;
|
|
58
|
+
try {
|
|
59
|
+
msg = JSON.parse(raw);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
totalMessages++;
|
|
65
|
+
// Track sessionID from first message that has one
|
|
66
|
+
if (!sessionId && typeof msg.sessionID === "string") {
|
|
67
|
+
sessionId = msg.sessionID;
|
|
68
|
+
}
|
|
69
|
+
// Count by role
|
|
70
|
+
const role = typeof msg.role === "string" ? msg.role : null;
|
|
71
|
+
if (role === "user") {
|
|
72
|
+
userMessages++;
|
|
73
|
+
}
|
|
74
|
+
else if (role === "assistant") {
|
|
75
|
+
assistantMessages++;
|
|
76
|
+
}
|
|
77
|
+
// Track timestamps — use completed if available, else created
|
|
78
|
+
const time = msg.time;
|
|
79
|
+
if (time) {
|
|
80
|
+
const completedMs = coerceEpochMs(time.completed);
|
|
81
|
+
const createdMs = coerceEpochMs(time.created);
|
|
82
|
+
// For min, prefer created (message start)
|
|
83
|
+
const msgStart = createdMs || completedMs;
|
|
84
|
+
// For max, prefer completed (message end)
|
|
85
|
+
const msgEnd = completedMs || createdMs;
|
|
86
|
+
if (msgStart) {
|
|
87
|
+
if (!minEpochMs || msgStart < minEpochMs)
|
|
88
|
+
minEpochMs = msgStart;
|
|
89
|
+
}
|
|
90
|
+
if (msgEnd) {
|
|
91
|
+
if (!maxEpochMs || msgEnd > maxEpochMs)
|
|
92
|
+
maxEpochMs = msgEnd;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Track model
|
|
96
|
+
const model = typeof msg.modelID === "string"
|
|
97
|
+
? msg.modelID.trim()
|
|
98
|
+
: typeof msg.model === "string"
|
|
99
|
+
? msg.model.trim()
|
|
100
|
+
: null;
|
|
101
|
+
if (model) {
|
|
102
|
+
lastModel = model;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// No valid timestamps → can't produce a snapshot
|
|
106
|
+
if (!minEpochMs)
|
|
107
|
+
return [];
|
|
108
|
+
const startedAt = new Date(minEpochMs).toISOString();
|
|
109
|
+
const lastMessageAt = new Date(maxEpochMs ?? minEpochMs).toISOString();
|
|
110
|
+
const durationSeconds = Math.max(0, Math.floor(((maxEpochMs ?? minEpochMs) - minEpochMs) / 1000));
|
|
111
|
+
// Derive session key: prefer sessionID from messages, fallback to directory name
|
|
112
|
+
const dirName = basename(sessionDir);
|
|
113
|
+
const sessionKey = `opencode:${sessionId ?? dirName}`;
|
|
114
|
+
return [
|
|
115
|
+
{
|
|
116
|
+
sessionKey,
|
|
117
|
+
source: "opencode",
|
|
118
|
+
kind: "human",
|
|
119
|
+
startedAt,
|
|
120
|
+
lastMessageAt,
|
|
121
|
+
durationSeconds,
|
|
122
|
+
userMessages,
|
|
123
|
+
assistantMessages,
|
|
124
|
+
totalMessages,
|
|
125
|
+
projectRef: null,
|
|
126
|
+
model: lastModel,
|
|
127
|
+
snapshotAt: new Date().toISOString(),
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=opencode-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode-session.js","sourceRoot":"","sources":["../../src/parsers/opencode-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { QueryMessagesFn } from "./opencode-sqlite.js";
|
|
2
|
+
import type { SessionRow, SessionMessageRow } from "./opencode-sqlite-session.js";
|
|
3
|
+
/**
|
|
4
|
+
* Open an OpenCode SQLite database in read-only mode
|
|
5
|
+
* and return a queryMessages function for use with parseOpenCodeSqlite().
|
|
6
|
+
*
|
|
7
|
+
* Uses bun:sqlite for zero-dependency SQLite access.
|
|
8
|
+
* Returns null if the database cannot be opened.
|
|
9
|
+
*/
|
|
10
|
+
export declare function openMessageDb(dbPath: string): {
|
|
11
|
+
queryMessages: QueryMessagesFn;
|
|
12
|
+
close: () => void;
|
|
13
|
+
} | null;
|
|
14
|
+
/** Function type for querying sessions updated since a given timestamp */
|
|
15
|
+
export type QuerySessionsFn = (lastTimeUpdated: number) => SessionRow[];
|
|
16
|
+
/** Function type for querying messages belonging to given session IDs */
|
|
17
|
+
export type QuerySessionMessagesFn = (sessionIds: string[]) => SessionMessageRow[];
|
|
18
|
+
/**
|
|
19
|
+
* Open an OpenCode SQLite database in read-only mode
|
|
20
|
+
* and return session query functions for use with collectOpenCodeSqliteSessions().
|
|
21
|
+
*
|
|
22
|
+
* Returns null if the database cannot be opened.
|
|
23
|
+
*/
|
|
24
|
+
export declare function openSessionDb(dbPath: string): {
|
|
25
|
+
querySessions: QuerySessionsFn;
|
|
26
|
+
querySessionMessages: QuerySessionMessagesFn;
|
|
27
|
+
close: () => void;
|
|
28
|
+
} | null;
|
|
29
|
+
//# sourceMappingURL=opencode-sqlite-db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode-sqlite-db.d.ts","sourceRoot":"","sources":["../../src/parsers/opencode-sqlite-db.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAElF;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,GACb;IAAE,aAAa,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,IAAI,CAAA;CAAE,GAAG,IAAI,CAmB9D;AAED,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,CAAC,eAAe,EAAE,MAAM,KAAK,UAAU,EAAE,CAAC;AAExE,yEAAyE;AACzE,MAAM,MAAM,sBAAsB,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,iBAAiB,EAAE,CAAC;AAEnF;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,GACb;IACD,aAAa,EAAE,eAAe,CAAC;IAC/B,oBAAoB,EAAE,sBAAsB,CAAC;IAC7C,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,GAAG,IAAI,CA6CP"}
|