@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.
Files changed (72) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +117 -8
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/session-sync.d.ts +62 -0
  5. package/dist/commands/session-sync.d.ts.map +1 -0
  6. package/dist/commands/session-sync.js +443 -0
  7. package/dist/commands/session-sync.js.map +1 -0
  8. package/dist/commands/session-upload.d.ts +55 -0
  9. package/dist/commands/session-upload.d.ts.map +1 -0
  10. package/dist/commands/session-upload.js +177 -0
  11. package/dist/commands/session-upload.js.map +1 -0
  12. package/dist/commands/status.d.ts +9 -0
  13. package/dist/commands/status.d.ts.map +1 -1
  14. package/dist/commands/status.js +22 -11
  15. package/dist/commands/status.js.map +1 -1
  16. package/dist/commands/sync.d.ts +12 -0
  17. package/dist/commands/sync.d.ts.map +1 -1
  18. package/dist/commands/sync.js +159 -3
  19. package/dist/commands/sync.js.map +1 -1
  20. package/dist/discovery/sources.d.ts +5 -0
  21. package/dist/discovery/sources.d.ts.map +1 -1
  22. package/dist/discovery/sources.js +7 -0
  23. package/dist/discovery/sources.js.map +1 -1
  24. package/dist/parsers/claude-session.d.ts +19 -0
  25. package/dist/parsers/claude-session.d.ts.map +1 -0
  26. package/dist/parsers/claude-session.js +131 -0
  27. package/dist/parsers/claude-session.js.map +1 -0
  28. package/dist/parsers/codex-session.d.ts +24 -0
  29. package/dist/parsers/codex-session.d.ts.map +1 -0
  30. package/dist/parsers/codex-session.js +140 -0
  31. package/dist/parsers/codex-session.js.map +1 -0
  32. package/dist/parsers/codex.d.ts +37 -0
  33. package/dist/parsers/codex.d.ts.map +1 -0
  34. package/dist/parsers/codex.js +136 -0
  35. package/dist/parsers/codex.js.map +1 -0
  36. package/dist/parsers/gemini-session.d.ts +19 -0
  37. package/dist/parsers/gemini-session.d.ts.map +1 -0
  38. package/dist/parsers/gemini-session.js +103 -0
  39. package/dist/parsers/gemini-session.js.map +1 -0
  40. package/dist/parsers/openclaw-session.d.ts +20 -0
  41. package/dist/parsers/openclaw-session.d.ts.map +1 -0
  42. package/dist/parsers/openclaw-session.js +122 -0
  43. package/dist/parsers/openclaw-session.js.map +1 -0
  44. package/dist/parsers/opencode-session.d.ts +15 -0
  45. package/dist/parsers/opencode-session.d.ts.map +1 -0
  46. package/dist/parsers/opencode-session.js +131 -0
  47. package/dist/parsers/opencode-session.js.map +1 -0
  48. package/dist/parsers/opencode-sqlite-db.d.ts +29 -0
  49. package/dist/parsers/opencode-sqlite-db.d.ts.map +1 -0
  50. package/dist/parsers/opencode-sqlite-db.js +71 -0
  51. package/dist/parsers/opencode-sqlite-db.js.map +1 -0
  52. package/dist/parsers/opencode-sqlite-session.d.ts +32 -0
  53. package/dist/parsers/opencode-sqlite-session.d.ts.map +1 -0
  54. package/dist/parsers/opencode-sqlite-session.js +121 -0
  55. package/dist/parsers/opencode-sqlite-session.js.map +1 -0
  56. package/dist/parsers/opencode-sqlite.d.ts +53 -0
  57. package/dist/parsers/opencode-sqlite.d.ts.map +1 -0
  58. package/dist/parsers/opencode-sqlite.js +104 -0
  59. package/dist/parsers/opencode-sqlite.js.map +1 -0
  60. package/dist/storage/session-cursor-store.d.ts +14 -0
  61. package/dist/storage/session-cursor-store.d.ts.map +1 -0
  62. package/dist/storage/session-cursor-store.js +34 -0
  63. package/dist/storage/session-cursor-store.js.map +1 -0
  64. package/dist/storage/session-queue.d.ts +28 -0
  65. package/dist/storage/session-queue.d.ts.map +1 -0
  66. package/dist/storage/session-queue.js +65 -0
  67. package/dist/storage/session-queue.js.map +1 -0
  68. package/dist/utils/paths.d.ts +4 -0
  69. package/dist/utils/paths.d.ts.map +1 -1
  70. package/dist/utils/paths.js +5 -0
  71. package/dist/utils/paths.js.map +1 -1
  72. package/package.json +1 -1
@@ -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"}
@@ -0,0 +1,71 @@
1
+ import { Database } from "bun:sqlite";
2
+ /**
3
+ * Open an OpenCode SQLite database in read-only mode
4
+ * and return a queryMessages function for use with parseOpenCodeSqlite().
5
+ *
6
+ * Uses bun:sqlite for zero-dependency SQLite access.
7
+ * Returns null if the database cannot be opened.
8
+ */
9
+ export function openMessageDb(dbPath) {
10
+ let db;
11
+ try {
12
+ db = new Database(dbPath, { readonly: true });
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ const stmt = db.query(`SELECT id, session_id, time_created, json_extract(data, '$.role') as role, data
18
+ FROM message
19
+ WHERE time_created >= ?
20
+ ORDER BY time_created ASC`);
21
+ return {
22
+ queryMessages: (lastTimeCreated) => stmt.all(lastTimeCreated),
23
+ close: () => db.close(),
24
+ };
25
+ }
26
+ /**
27
+ * Open an OpenCode SQLite database in read-only mode
28
+ * and return session query functions for use with collectOpenCodeSqliteSessions().
29
+ *
30
+ * Returns null if the database cannot be opened.
31
+ */
32
+ export function openSessionDb(dbPath) {
33
+ let db;
34
+ try {
35
+ db = new Database(dbPath, { readonly: true });
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ const sessionStmt = db.query(`SELECT id, project_id, title, time_created, time_updated
41
+ FROM session
42
+ WHERE time_updated >= ?
43
+ ORDER BY time_updated ASC`);
44
+ return {
45
+ querySessions: (lastTimeUpdated) => sessionStmt.all(lastTimeUpdated),
46
+ querySessionMessages: (sessionIds) => {
47
+ if (sessionIds.length === 0)
48
+ return [];
49
+ // SQLite has a 999 parameter limit. Batch session IDs into chunks
50
+ // of 500 to stay well under the limit.
51
+ const CHUNK_SIZE = 500;
52
+ const results = [];
53
+ for (let i = 0; i < sessionIds.length; i += CHUNK_SIZE) {
54
+ const chunk = sessionIds.slice(i, i + CHUNK_SIZE);
55
+ const placeholders = chunk.map(() => "?").join(",");
56
+ const stmt = db.query(`SELECT session_id, json_extract(data, '$.role') as role, time_created, data
57
+ FROM message
58
+ WHERE session_id IN (${placeholders})
59
+ ORDER BY time_created ASC`);
60
+ results.push(...stmt.all(...chunk));
61
+ }
62
+ // Re-sort across chunks to maintain global time_created order
63
+ if (sessionIds.length > CHUNK_SIZE) {
64
+ results.sort((a, b) => a.time_created - b.time_created);
65
+ }
66
+ return results;
67
+ },
68
+ close: () => db.close(),
69
+ };
70
+ }
71
+ //# sourceMappingURL=opencode-sqlite-db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-sqlite-db.js","sourceRoot":"","sources":["../../src/parsers/opencode-sqlite-db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAc;IAEd,IAAI,EAAY,CAAC;IACjB,IAAI,CAAC;QACH,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,GAAG,EAAE,CAAC,KAAK,CACnB;;;+BAG2B,CAC5B,CAAC;IAEF,OAAO;QACL,aAAa,EAAE,CAAC,eAAuB,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC;QACrE,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE;KACxB,CAAC;AACJ,CAAC;AAQD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAc;IAMd,IAAI,EAAY,CAAC;IACjB,IAAI,CAAC;QACH,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,WAAW,GAAG,EAAE,CAAC,KAAK,CAC1B;;;+BAG2B,CAC5B,CAAC;IAEF,OAAO;QACL,aAAa,EAAE,CAAC,eAAuB,EAAE,EAAE,CACzC,WAAW,CAAC,GAAG,CAAC,eAAe,CAAC;QAElC,oBAAoB,EAAE,CAAC,UAAoB,EAAE,EAAE;YAC7C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YACvC,kEAAkE;YAClE,uCAAuC;YACvC,MAAM,UAAU,GAAG,GAAG,CAAC;YACvB,MAAM,OAAO,GAAwB,EAAE,CAAC;YACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,IAAI,UAAU,EAAE,CAAC;gBACvD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC;gBAClD,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACpD,MAAM,IAAI,GAAG,EAAE,CAAC,KAAK,CACnB;;kCAEwB,YAAY;qCACT,CAC5B,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;YACtC,CAAC;YACD,8DAA8D;YAC9D,IAAI,UAAU,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;gBACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;YAC1D,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE;KACxB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * OpenCode SQLite session collector.
3
+ *
4
+ * Queries the `session` and `message` tables from opencode.db
5
+ * to produce SessionSnapshot records. Uses dependency injection
6
+ * (pre-fetched rows) for testability without bun:sqlite.
7
+ */
8
+ import type { SessionSnapshot } from "@pew/core";
9
+ /** Row shape from the session table */
10
+ export interface SessionRow {
11
+ id: string;
12
+ project_id: string | null;
13
+ title: string | null;
14
+ time_created: number;
15
+ time_updated: number;
16
+ }
17
+ /** Row shape from message table for session collection (minimal) */
18
+ export interface SessionMessageRow {
19
+ session_id: string;
20
+ role: string;
21
+ time_created: number;
22
+ data: string;
23
+ }
24
+ /**
25
+ * Collect session snapshots from pre-fetched SQLite rows.
26
+ *
27
+ * Groups messages by session, counts roles, tracks timestamps and model.
28
+ * Uses session table time_created/time_updated as fallback timestamps
29
+ * when message-level time is unavailable.
30
+ */
31
+ export declare function collectOpenCodeSqliteSessions(sessions: SessionRow[], messages: SessionMessageRow[]): SessionSnapshot[];
32
+ //# sourceMappingURL=opencode-sqlite-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-sqlite-session.d.ts","sourceRoot":"","sources":["../../src/parsers/opencode-sqlite-session.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAU,MAAM,WAAW,CAAC;AAEzD,uCAAuC;AACvC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,oEAAoE;AACpE,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd;AAaD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,UAAU,EAAE,EACtB,QAAQ,EAAE,iBAAiB,EAAE,GAC5B,eAAe,EAAE,CAuGnB"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * OpenCode SQLite session collector.
3
+ *
4
+ * Queries the `session` and `message` tables from opencode.db
5
+ * to produce SessionSnapshot records. Uses dependency injection
6
+ * (pre-fetched rows) for testability without bun:sqlite.
7
+ */
8
+ /**
9
+ * Coerce an epoch value to milliseconds.
10
+ * Values < 1e12 are treated as seconds and multiplied by 1000.
11
+ */
12
+ function coerceEpochMs(v) {
13
+ const n = Number(v);
14
+ if (!Number.isFinite(n) || n <= 0)
15
+ return 0;
16
+ if (n < 1e12)
17
+ return Math.floor(n * 1000);
18
+ return Math.floor(n);
19
+ }
20
+ /**
21
+ * Collect session snapshots from pre-fetched SQLite rows.
22
+ *
23
+ * Groups messages by session, counts roles, tracks timestamps and model.
24
+ * Uses session table time_created/time_updated as fallback timestamps
25
+ * when message-level time is unavailable.
26
+ */
27
+ export function collectOpenCodeSqliteSessions(sessions, messages) {
28
+ if (sessions.length === 0)
29
+ return [];
30
+ // Group messages by session_id
31
+ const msgMap = new Map();
32
+ for (const msg of messages) {
33
+ const list = msgMap.get(msg.session_id);
34
+ if (list) {
35
+ list.push(msg);
36
+ }
37
+ else {
38
+ msgMap.set(msg.session_id, [msg]);
39
+ }
40
+ }
41
+ const snapshots = [];
42
+ for (const session of sessions) {
43
+ const sessionMessages = msgMap.get(session.id) ?? [];
44
+ let userMessages = 0;
45
+ let assistantMessages = 0;
46
+ const totalMessages = sessionMessages.length;
47
+ let minEpochMs = null;
48
+ let maxEpochMs = null;
49
+ let lastModel = null;
50
+ for (const msg of sessionMessages) {
51
+ // Count by role from the role column (faster than parsing JSON)
52
+ if (msg.role === "user") {
53
+ userMessages++;
54
+ }
55
+ else if (msg.role === "assistant") {
56
+ assistantMessages++;
57
+ }
58
+ // Parse data JSON for time and model extraction
59
+ let data = null;
60
+ try {
61
+ data = JSON.parse(msg.data);
62
+ }
63
+ catch {
64
+ // If data is corrupted, we still count the message but skip time/model
65
+ }
66
+ if (data) {
67
+ const time = data.time;
68
+ if (time) {
69
+ const completedMs = coerceEpochMs(time.completed);
70
+ const createdMs = coerceEpochMs(time.created);
71
+ const msgStart = createdMs || completedMs;
72
+ const msgEnd = completedMs || createdMs;
73
+ if (msgStart) {
74
+ if (!minEpochMs || msgStart < minEpochMs)
75
+ minEpochMs = msgStart;
76
+ }
77
+ if (msgEnd) {
78
+ if (!maxEpochMs || msgEnd > maxEpochMs)
79
+ maxEpochMs = msgEnd;
80
+ }
81
+ }
82
+ // Track model from assistant messages
83
+ const model = typeof data.modelID === "string"
84
+ ? data.modelID.trim()
85
+ : typeof data.model === "string"
86
+ ? data.model.trim()
87
+ : null;
88
+ if (model) {
89
+ lastModel = model;
90
+ }
91
+ }
92
+ }
93
+ // Fallback to session table timestamps if messages lack time
94
+ if (!minEpochMs) {
95
+ minEpochMs = coerceEpochMs(session.time_created) || null;
96
+ }
97
+ if (!maxEpochMs) {
98
+ maxEpochMs = coerceEpochMs(session.time_updated) || minEpochMs;
99
+ }
100
+ // Must have at least a start time
101
+ if (!minEpochMs)
102
+ continue;
103
+ const endMs = maxEpochMs ?? minEpochMs;
104
+ snapshots.push({
105
+ sessionKey: `opencode:${session.id}`,
106
+ source: "opencode",
107
+ kind: "human",
108
+ startedAt: new Date(minEpochMs).toISOString(),
109
+ lastMessageAt: new Date(endMs).toISOString(),
110
+ durationSeconds: Math.max(0, Math.floor((endMs - minEpochMs) / 1000)),
111
+ userMessages,
112
+ assistantMessages,
113
+ totalMessages,
114
+ projectRef: session.project_id ?? null,
115
+ model: lastModel,
116
+ snapshotAt: new Date().toISOString(),
117
+ });
118
+ }
119
+ return snapshots;
120
+ }
121
+ //# sourceMappingURL=opencode-sqlite-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-sqlite-session.js","sourceRoot":"","sources":["../../src/parsers/opencode-sqlite-session.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAqBH;;;GAGG;AACH,SAAS,aAAa,CAAC,CAAU;IAC/B,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACpB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC5C,IAAI,CAAC,GAAG,IAAI;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1C,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,6BAA6B,CAC3C,QAAsB,EACtB,QAA6B;IAE7B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAErC,+BAA+B;IAC/B,MAAM,MAAM,GAAG,IAAI,GAAG,EAA+B,CAAC;IACtD,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACxC,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAsB,EAAE,CAAC;IAExC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;QAErD,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,iBAAiB,GAAG,CAAC,CAAC;QAC1B,MAAM,aAAa,GAAG,eAAe,CAAC,MAAM,CAAC;QAE7C,IAAI,UAAU,GAAkB,IAAI,CAAC;QACrC,IAAI,UAAU,GAAkB,IAAI,CAAC;QACrC,IAAI,SAAS,GAAkB,IAAI,CAAC;QAEpC,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;YAClC,gEAAgE;YAChE,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACxB,YAAY,EAAE,CAAC;YACjB,CAAC;iBAAM,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACpC,iBAAiB,EAAE,CAAC;YACtB,CAAC;YAED,gDAAgD;YAChD,IAAI,IAAI,GAAmC,IAAI,CAAC;YAChD,IAAI,CAAC;gBACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9B,CAAC;YAAC,MAAM,CAAC;gBACP,uEAAuE;YACzE,CAAC;YAED,IAAI,IAAI,EAAE,CAAC;gBACT,MAAM,IAAI,GAAG,IAAI,CAAC,IAA2C,CAAC;gBAC9D,IAAI,IAAI,EAAE,CAAC;oBACT,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBAClD,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;oBAE9C,MAAM,QAAQ,GAAG,SAAS,IAAI,WAAW,CAAC;oBAC1C,MAAM,MAAM,GAAG,WAAW,IAAI,SAAS,CAAC;oBAExC,IAAI,QAAQ,EAAE,CAAC;wBACb,IAAI,CAAC,UAAU,IAAI,QAAQ,GAAG,UAAU;4BAAE,UAAU,GAAG,QAAQ,CAAC;oBAClE,CAAC;oBACD,IAAI,MAAM,EAAE,CAAC;wBACX,IAAI,CAAC,UAAU,IAAI,MAAM,GAAG,UAAU;4BAAE,UAAU,GAAG,MAAM,CAAC;oBAC9D,CAAC;gBACH,CAAC;gBAED,sCAAsC;gBACtC,MAAM,KAAK,GACT,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ;oBAC9B,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE;oBACrB,CAAC,CAAC,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;wBAC9B,CAAC,CAAE,IAAI,CAAC,KAAgB,CAAC,IAAI,EAAE;wBAC/B,CAAC,CAAC,IAAI,CAAC;gBACb,IAAI,KAAK,EAAE,CAAC;oBACV,SAAS,GAAG,KAAK,CAAC;gBACpB,CAAC;YACH,CAAC;QACH,CAAC;QAED,6DAA6D;QAC7D,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,UAAU,GAAG,aAAa,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC;QAC3D,CAAC;QACD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,UAAU,GAAG,aAAa,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,UAAU,CAAC;QACjE,CAAC;QAED,kCAAkC;QAClC,IAAI,CAAC,UAAU;YAAE,SAAS;QAE1B,MAAM,KAAK,GAAG,UAAU,IAAI,UAAU,CAAC;QAEvC,SAAS,CAAC,IAAI,CAAC;YACb,UAAU,EAAE,YAAY,OAAO,CAAC,EAAE,EAAE;YACpC,MAAM,EAAE,UAAoB;YAC5B,IAAI,EAAE,OAAO;YACb,SAAS,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE;YAC7C,aAAa,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE;YAC5C,eAAe,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;YACrE,YAAY;YACZ,iBAAiB;YACjB,aAAa;YACb,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI;YACtC,KAAK,EAAE,SAAS;YAChB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACrC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,53 @@
1
+ import type { ParsedDelta } from "./claude.js";
2
+ /** Result of parsing OpenCode SQLite database */
3
+ export interface OpenCodeSqliteResult {
4
+ /** Parsed token deltas (one per assistant message) */
5
+ deltas: ParsedDelta[];
6
+ /** Message keys for dedup: "sessionId|messageId" */
7
+ messageKeys: Set<string>;
8
+ /** Highest time_created seen (for cursor advancement) */
9
+ maxTimeCreated: number;
10
+ /** DB file inode (for detecting replacement/recreation) */
11
+ inode: number;
12
+ }
13
+ /** Row shape from the message table */
14
+ export interface MessageRow {
15
+ id: string;
16
+ session_id: string;
17
+ time_created: number;
18
+ /** Extracted via json_extract(data, '$.role') at the SQL level */
19
+ role: string | null;
20
+ data: string;
21
+ }
22
+ /**
23
+ * Function that queries the message table.
24
+ * Accepts lastTimeCreated and returns rows where time_created >= lastTimeCreated.
25
+ * Callers must filter out previously-processed IDs from the prior batch
26
+ * to handle same-millisecond boundary dedup.
27
+ */
28
+ export type QueryMessagesFn = (lastTimeCreated: number) => MessageRow[];
29
+ /**
30
+ * Parse message rows from OpenCode's SQLite database for token usage records.
31
+ *
32
+ * Processes rows from the `message` table where `time_created > lastTimeCreated`.
33
+ *
34
+ * Unlike the JSON file parser, no diffTotals is needed — each SQLite row
35
+ * is an independent message with absolute token values.
36
+ *
37
+ * The `queryMessages` function is injected to decouple from `bun:sqlite`
38
+ * for testability. Use `openMessageDb()` from `opencode-sqlite-db.ts` to
39
+ * create the real adapter at runtime.
40
+ */
41
+ export declare function processOpenCodeMessages(rows: MessageRow[]): Omit<OpenCodeSqliteResult, "inode">;
42
+ /**
43
+ * High-level entry: parse OpenCode SQLite database for token usage.
44
+ *
45
+ * Opens the database, queries new messages, and returns parsed deltas.
46
+ * The `queryMessages` function provides the database access layer.
47
+ */
48
+ export declare function parseOpenCodeSqlite(opts: {
49
+ dbPath: string;
50
+ lastTimeCreated: number;
51
+ queryMessages?: QueryMessagesFn;
52
+ }): Promise<OpenCodeSqliteResult>;
53
+ //# sourceMappingURL=opencode-sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-sqlite.d.ts","sourceRoot":"","sources":["../../src/parsers/opencode-sqlite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACnC,sDAAsD;IACtD,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,oDAAoD;IACpD,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;CACf;AAED,uCAAuC;AACvC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,eAAe,EAAE,MAAM,KAAK,UAAU,EAAE,CAAC;AAiBxE;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,UAAU,EAAE,GACjB,IAAI,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAsDrC;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,eAAe,CAAC;CACjC,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAgChC"}