@nocoo/pew 0.2.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 (69) hide show
  1. package/dist/bin.d.ts +3 -0
  2. package/dist/bin.d.ts.map +1 -0
  3. package/dist/bin.js +5 -0
  4. package/dist/bin.js.map +1 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +221 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/login.d.ts +34 -0
  10. package/dist/commands/login.d.ts.map +1 -0
  11. package/dist/commands/login.js +108 -0
  12. package/dist/commands/login.js.map +1 -0
  13. package/dist/commands/status.d.ts +19 -0
  14. package/dist/commands/status.d.ts.map +1 -0
  15. package/dist/commands/status.js +35 -0
  16. package/dist/commands/status.js.map +1 -0
  17. package/dist/commands/sync.d.ts +49 -0
  18. package/dist/commands/sync.d.ts.map +1 -0
  19. package/dist/commands/sync.js +267 -0
  20. package/dist/commands/sync.js.map +1 -0
  21. package/dist/commands/upload.d.ts +53 -0
  22. package/dist/commands/upload.d.ts.map +1 -0
  23. package/dist/commands/upload.js +185 -0
  24. package/dist/commands/upload.js.map +1 -0
  25. package/dist/config/manager.d.ts +14 -0
  26. package/dist/config/manager.d.ts.map +1 -0
  27. package/dist/config/manager.js +32 -0
  28. package/dist/config/manager.js.map +1 -0
  29. package/dist/discovery/sources.d.ts +37 -0
  30. package/dist/discovery/sources.d.ts.map +1 -0
  31. package/dist/discovery/sources.js +110 -0
  32. package/dist/discovery/sources.js.map +1 -0
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +2 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/parsers/claude.d.ts +34 -0
  38. package/dist/parsers/claude.d.ts.map +1 -0
  39. package/dist/parsers/claude.js +101 -0
  40. package/dist/parsers/claude.js.map +1 -0
  41. package/dist/parsers/gemini.d.ts +38 -0
  42. package/dist/parsers/gemini.d.ts.map +1 -0
  43. package/dist/parsers/gemini.js +142 -0
  44. package/dist/parsers/gemini.js.map +1 -0
  45. package/dist/parsers/openclaw.d.ts +23 -0
  46. package/dist/parsers/openclaw.d.ts.map +1 -0
  47. package/dist/parsers/openclaw.js +85 -0
  48. package/dist/parsers/openclaw.js.map +1 -0
  49. package/dist/parsers/opencode.d.ts +35 -0
  50. package/dist/parsers/opencode.d.ts.map +1 -0
  51. package/dist/parsers/opencode.js +118 -0
  52. package/dist/parsers/opencode.js.map +1 -0
  53. package/dist/storage/cursor-store.d.ts +14 -0
  54. package/dist/storage/cursor-store.d.ts.map +1 -0
  55. package/dist/storage/cursor-store.js +34 -0
  56. package/dist/storage/cursor-store.js.map +1 -0
  57. package/dist/storage/local-queue.d.ts +30 -0
  58. package/dist/storage/local-queue.d.ts.map +1 -0
  59. package/dist/storage/local-queue.js +70 -0
  60. package/dist/storage/local-queue.js.map +1 -0
  61. package/dist/utils/buckets.d.ts +17 -0
  62. package/dist/utils/buckets.d.ts.map +1 -0
  63. package/dist/utils/buckets.js +38 -0
  64. package/dist/utils/buckets.js.map +1 -0
  65. package/dist/utils/paths.d.ts +17 -0
  66. package/dist/utils/paths.d.ts.map +1 -0
  67. package/dist/utils/paths.js +21 -0
  68. package/dist/utils/paths.js.map +1 -0
  69. package/package.json +44 -0
@@ -0,0 +1,110 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ /**
4
+ * Recursively collect files matching a predicate under a directory.
5
+ * Uses withFileTypes to avoid separate stat() calls per entry.
6
+ * Returns absolute paths sorted alphabetically.
7
+ */
8
+ async function collectFiles(dir, predicate) {
9
+ const results = [];
10
+ async function walk(currentDir) {
11
+ let entries;
12
+ try {
13
+ entries = await readdir(currentDir, { withFileTypes: true });
14
+ }
15
+ catch {
16
+ return;
17
+ }
18
+ for (const entry of entries) {
19
+ const fullPath = join(currentDir, entry.name);
20
+ if (entry.isDirectory()) {
21
+ await walk(fullPath);
22
+ }
23
+ else if (entry.isFile() && predicate(entry.name)) {
24
+ results.push(fullPath);
25
+ }
26
+ }
27
+ }
28
+ await walk(dir);
29
+ return results.sort();
30
+ }
31
+ /**
32
+ * Discover Claude Code JSONL files.
33
+ * Path pattern: ~/.claude/projects/\*\*\/*.jsonl
34
+ */
35
+ export async function discoverClaudeFiles(claudeDir) {
36
+ const projectsDir = join(claudeDir, "projects");
37
+ return collectFiles(projectsDir, (name) => name.endsWith(".jsonl"));
38
+ }
39
+ /**
40
+ * Discover Gemini CLI session files.
41
+ * Path pattern: ~/.gemini/tmp/\*\/chats/session-*.json
42
+ */
43
+ export async function discoverGeminiFiles(geminiDir) {
44
+ const tmpDir = join(geminiDir, "tmp");
45
+ return collectFiles(tmpDir, (name) => name.startsWith("session-") && name.endsWith(".json"));
46
+ }
47
+ /**
48
+ * Discover OpenCode message files with directory-level mtime optimization.
49
+ *
50
+ * Instead of stat()-ing all 66K+ message files, we stat() only the ~3K
51
+ * session directories. If a directory's mtime hasn't changed since last
52
+ * sync, we skip the entire directory (no readdir, no file stat).
53
+ *
54
+ * Path pattern: ~/.local/share/opencode/storage/message/ses_*\/msg_*.json
55
+ */
56
+ export async function discoverOpenCodeFiles(messageDir, knownDirMtimes) {
57
+ const known = knownDirMtimes ?? {};
58
+ const newDirMtimes = {};
59
+ const files = [];
60
+ let skippedDirs = 0;
61
+ let entries;
62
+ try {
63
+ entries = await readdir(messageDir, { withFileTypes: true });
64
+ }
65
+ catch {
66
+ return { files: [], dirMtimes: {}, skippedDirs: 0 };
67
+ }
68
+ for (const entry of entries) {
69
+ if (!entry.isDirectory())
70
+ continue;
71
+ const dirPath = join(messageDir, entry.name);
72
+ let dirStat;
73
+ try {
74
+ dirStat = await stat(dirPath);
75
+ }
76
+ catch {
77
+ continue;
78
+ }
79
+ const currentMtime = dirStat.mtimeMs;
80
+ newDirMtimes[dirPath] = currentMtime;
81
+ // Skip directory if mtime unchanged
82
+ if (known[dirPath] === currentMtime) {
83
+ skippedDirs++;
84
+ continue;
85
+ }
86
+ // Directory changed — read its files
87
+ let dirEntries;
88
+ try {
89
+ dirEntries = await readdir(dirPath, { withFileTypes: true });
90
+ }
91
+ catch {
92
+ continue;
93
+ }
94
+ for (const fileEntry of dirEntries) {
95
+ if (fileEntry.isFile() && fileEntry.name.endsWith(".json")) {
96
+ files.push(join(dirPath, fileEntry.name));
97
+ }
98
+ }
99
+ }
100
+ return { files: files.sort(), dirMtimes: newDirMtimes, skippedDirs };
101
+ }
102
+ /**
103
+ * Discover OpenClaw session files.
104
+ * Path pattern: ~/.openclaw/agents/\*\/sessions/*.jsonl
105
+ */
106
+ export async function discoverOpenClawFiles(openclawDir) {
107
+ const agentsDir = join(openclawDir, "agents");
108
+ return collectFiles(agentsDir, (name) => name.endsWith(".jsonl"));
109
+ }
110
+ //# sourceMappingURL=sources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources.js","sourceRoot":"","sources":["../../src/discovery/sources.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { main } from "./cli.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { main } from "./cli.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { Source, TokenDelta } from "@pew/core";
2
+ /** A parsed token delta with metadata for bucket aggregation */
3
+ export interface ParsedDelta {
4
+ source: Source;
5
+ model: string;
6
+ timestamp: string;
7
+ tokens: TokenDelta;
8
+ }
9
+ /** Result of parsing a single Claude JSONL file */
10
+ export interface ClaudeFileResult {
11
+ deltas: ParsedDelta[];
12
+ endOffset: number;
13
+ }
14
+ /**
15
+ * Normalize Claude's usage object to our TokenDelta format.
16
+ *
17
+ * Claude API fields:
18
+ * input_tokens + cache_creation_input_tokens → inputTokens
19
+ * cache_read_input_tokens → cachedInputTokens
20
+ * output_tokens → outputTokens
21
+ * (hardcoded 0) → reasoningOutputTokens
22
+ */
23
+ export declare function normalizeClaudeUsage(u: Record<string, unknown>): TokenDelta;
24
+ /**
25
+ * Parse a Claude Code JSONL file incrementally from a byte offset.
26
+ *
27
+ * Each assistant message with non-zero usage produces a standalone delta
28
+ * (no running-total diffing needed — each usage block is absolute).
29
+ */
30
+ export declare function parseClaudeFile(opts: {
31
+ filePath: string;
32
+ startOffset: number;
33
+ }): Promise<ClaudeFileResult>;
34
+ //# sourceMappingURL=claude.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/parsers/claude.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEpD,gEAAgE;AAChE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,mDAAmD;AACnD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AASD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAS3E;AAYD;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgE5B"}
@@ -0,0 +1,101 @@
1
+ import { createReadStream } from "node:fs";
2
+ import { stat } from "node:fs/promises";
3
+ import { createInterface } from "node:readline";
4
+ /** Coerce to non-negative integer, returning 0 for invalid values */
5
+ function toNonNegInt(v) {
6
+ const n = Number(v);
7
+ if (!Number.isFinite(n) || n < 0)
8
+ return 0;
9
+ return Math.floor(n);
10
+ }
11
+ /**
12
+ * Normalize Claude's usage object to our TokenDelta format.
13
+ *
14
+ * Claude API fields:
15
+ * input_tokens + cache_creation_input_tokens → inputTokens
16
+ * cache_read_input_tokens → cachedInputTokens
17
+ * output_tokens → outputTokens
18
+ * (hardcoded 0) → reasoningOutputTokens
19
+ */
20
+ export function normalizeClaudeUsage(u) {
21
+ return {
22
+ inputTokens: toNonNegInt(u?.input_tokens) +
23
+ toNonNegInt(u?.cache_creation_input_tokens),
24
+ cachedInputTokens: toNonNegInt(u?.cache_read_input_tokens),
25
+ outputTokens: toNonNegInt(u?.output_tokens),
26
+ reasoningOutputTokens: 0,
27
+ };
28
+ }
29
+ /** Check if a TokenDelta is all zeros */
30
+ function isAllZero(delta) {
31
+ return (delta.inputTokens === 0 &&
32
+ delta.cachedInputTokens === 0 &&
33
+ delta.outputTokens === 0 &&
34
+ delta.reasoningOutputTokens === 0);
35
+ }
36
+ /**
37
+ * Parse a Claude Code JSONL file incrementally from a byte offset.
38
+ *
39
+ * Each assistant message with non-zero usage produces a standalone delta
40
+ * (no running-total diffing needed — each usage block is absolute).
41
+ */
42
+ export async function parseClaudeFile(opts) {
43
+ const { filePath, startOffset } = opts;
44
+ const deltas = [];
45
+ const st = await stat(filePath).catch(() => null);
46
+ if (!st || !st.isFile())
47
+ return { deltas, endOffset: startOffset };
48
+ const endOffset = st.size;
49
+ if (startOffset >= endOffset)
50
+ return { deltas, endOffset };
51
+ const stream = createReadStream(filePath, {
52
+ encoding: "utf8",
53
+ start: startOffset,
54
+ });
55
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
56
+ try {
57
+ for await (const line of rl) {
58
+ // Fast-path: skip lines that can't contain usage data
59
+ if (!line || !line.includes('"usage"'))
60
+ continue;
61
+ let obj;
62
+ try {
63
+ obj = JSON.parse(line);
64
+ }
65
+ catch {
66
+ continue;
67
+ }
68
+ // Extract usage from message.usage or obj.usage
69
+ const msg = obj?.message;
70
+ const usage = (msg?.usage || obj?.usage);
71
+ if (!usage || typeof usage !== "object")
72
+ continue;
73
+ // Extract model
74
+ const model = typeof (msg?.model ?? obj?.model) === "string"
75
+ ? String(msg?.model ?? obj?.model).trim()
76
+ : null;
77
+ if (!model)
78
+ continue;
79
+ // Extract timestamp
80
+ const timestamp = typeof obj?.timestamp === "string" ? obj.timestamp : null;
81
+ if (!timestamp)
82
+ continue;
83
+ // Normalize and filter zero deltas
84
+ const delta = normalizeClaudeUsage(usage);
85
+ if (isAllZero(delta))
86
+ continue;
87
+ deltas.push({
88
+ source: "claude-code",
89
+ model,
90
+ timestamp,
91
+ tokens: delta,
92
+ });
93
+ }
94
+ }
95
+ finally {
96
+ rl.close();
97
+ stream.destroy();
98
+ }
99
+ return { deltas, endOffset };
100
+ }
101
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/parsers/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAiBhD,qEAAqE;AACrE,SAAS,WAAW,CAAC,CAAU;IAC7B,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACpB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,CAA0B;IAC7D,OAAO;QACL,WAAW,EACT,WAAW,CAAC,CAAC,EAAE,YAAY,CAAC;YAC5B,WAAW,CAAC,CAAC,EAAE,2BAA2B,CAAC;QAC7C,iBAAiB,EAAE,WAAW,CAAC,CAAC,EAAE,uBAAuB,CAAC;QAC1D,YAAY,EAAE,WAAW,CAAC,CAAC,EAAE,aAAa,CAAC;QAC3C,qBAAqB,EAAE,CAAC;KACzB,CAAC;AACJ,CAAC;AAED,yCAAyC;AACzC,SAAS,SAAS,CAAC,KAAiB;IAClC,OAAO,CACL,KAAK,CAAC,WAAW,KAAK,CAAC;QACvB,KAAK,CAAC,iBAAiB,KAAK,CAAC;QAC7B,KAAK,CAAC,YAAY,KAAK,CAAC;QACxB,KAAK,CAAC,qBAAqB,KAAK,CAAC,CAClC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAGrC;IACC,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IACvC,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAClD,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;IAEnE,MAAM,SAAS,GAAG,EAAE,CAAC,IAAI,CAAC;IAC1B,IAAI,WAAW,IAAI,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAE3D,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,EAAE;QACxC,QAAQ,EAAE,MAAM;QAChB,KAAK,EAAE,WAAW;KACnB,CAAC,CAAC;IACH,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;IAEnE,IAAI,CAAC;QACH,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,EAAE,EAAE,CAAC;YAC5B,sDAAsD;YACtD,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAAE,SAAS;YAEjD,IAAI,GAA4B,CAAC;YACjC,IAAI,CAAC;gBACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACzB,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YAED,gDAAgD;YAChD,MAAM,GAAG,GAAG,GAAG,EAAE,OAA8C,CAAC;YAChE,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,KAAK,IAAI,GAAG,EAAE,KAAK,CAE1B,CAAC;YACd,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,SAAS;YAElD,gBAAgB;YAChB,MAAM,KAAK,GACT,OAAO,CAAC,GAAG,EAAE,KAAK,IAAI,GAAG,EAAE,KAAK,CAAC,KAAK,QAAQ;gBAC5C,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,IAAI,GAAG,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE;gBACzC,CAAC,CAAC,IAAI,CAAC;YACX,IAAI,CAAC,KAAK;gBAAE,SAAS;YAErB,oBAAoB;YACpB,MAAM,SAAS,GACb,OAAO,GAAG,EAAE,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;YAC5D,IAAI,CAAC,SAAS;gBAAE,SAAS;YAEzB,mCAAmC;YACnC,MAAM,KAAK,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC1C,IAAI,SAAS,CAAC,KAAK,CAAC;gBAAE,SAAS;YAE/B,MAAM,CAAC,IAAI,CAAC;gBACV,MAAM,EAAE,aAAa;gBACrB,KAAK;gBACL,SAAS;gBACT,MAAM,EAAE,KAAK;aACd,CAAC,CAAC;QACL,CAAC;IACH,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { TokenDelta } from "@pew/core";
2
+ import type { ParsedDelta } from "./claude.js";
3
+ /** Result of parsing a Gemini session JSON file */
4
+ export interface GeminiFileResult {
5
+ deltas: ParsedDelta[];
6
+ lastIndex: number;
7
+ lastTotals: TokenDelta | null;
8
+ lastModel: string | null;
9
+ }
10
+ /**
11
+ * Normalize Gemini's token object to our TokenDelta format.
12
+ *
13
+ * Gemini fields:
14
+ * input → inputTokens
15
+ * cached → cachedInputTokens
16
+ * output + tool → outputTokens
17
+ * thoughts → reasoningOutputTokens
18
+ */
19
+ export declare function normalizeGeminiTokens(tokens: Record<string, unknown> | null | undefined): TokenDelta | null;
20
+ /**
21
+ * Diff current cumulative totals against previous.
22
+ * Returns null if no change, or the full current if totals reset (decreased).
23
+ * Shared by Gemini and OpenCode parsers.
24
+ */
25
+ export declare function diffTotals(current: TokenDelta, previous: TokenDelta | null): TokenDelta | null;
26
+ /**
27
+ * Parse a Gemini CLI session JSON file.
28
+ *
29
+ * Gemini stores one JSON file per session with a `messages[]` array.
30
+ * Each `type: "gemini"` message has cumulative `tokens` — we diff
31
+ * against the previous totals to compute per-message deltas.
32
+ */
33
+ export declare function parseGeminiFile(opts: {
34
+ filePath: string;
35
+ startIndex: number;
36
+ lastTotals: TokenDelta | null;
37
+ }): Promise<GeminiFileResult>;
38
+ //# sourceMappingURL=gemini.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/parsers/gemini.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAU,UAAU,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,mDAAmD;AACnD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AASD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GACjD,UAAU,GAAG,IAAI,CASnB;AAiBD;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,UAAU,GAAG,IAAI,GAC1B,UAAU,GAAG,IAAI,CA8BnB;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;CAC/B,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmF5B"}
@@ -0,0 +1,142 @@
1
+ import { readFile } from "node:fs/promises";
2
+ /** Coerce to non-negative integer */
3
+ function toNonNegInt(v) {
4
+ const n = Number(v);
5
+ if (!Number.isFinite(n) || n < 0)
6
+ return 0;
7
+ return Math.floor(n);
8
+ }
9
+ /**
10
+ * Normalize Gemini's token object to our TokenDelta format.
11
+ *
12
+ * Gemini fields:
13
+ * input → inputTokens
14
+ * cached → cachedInputTokens
15
+ * output + tool → outputTokens
16
+ * thoughts → reasoningOutputTokens
17
+ */
18
+ export function normalizeGeminiTokens(tokens) {
19
+ if (!tokens || typeof tokens !== "object")
20
+ return null;
21
+ return {
22
+ inputTokens: toNonNegInt(tokens.input),
23
+ cachedInputTokens: toNonNegInt(tokens.cached),
24
+ outputTokens: toNonNegInt(tokens.output) + toNonNegInt(tokens.tool),
25
+ reasoningOutputTokens: toNonNegInt(tokens.thoughts),
26
+ };
27
+ }
28
+ /** Check if a TokenDelta is all zeros */
29
+ function isAllZero(d) {
30
+ return (d.inputTokens === 0 &&
31
+ d.cachedInputTokens === 0 &&
32
+ d.outputTokens === 0 &&
33
+ d.reasoningOutputTokens === 0);
34
+ }
35
+ /** Compute total from a TokenDelta (for reset detection) */
36
+ function totalOf(d) {
37
+ return d.inputTokens + d.cachedInputTokens + d.outputTokens + d.reasoningOutputTokens;
38
+ }
39
+ /**
40
+ * Diff current cumulative totals against previous.
41
+ * Returns null if no change, or the full current if totals reset (decreased).
42
+ * Shared by Gemini and OpenCode parsers.
43
+ */
44
+ export function diffTotals(current, previous) {
45
+ if (!previous)
46
+ return current;
47
+ // Same → no change
48
+ if (current.inputTokens === previous.inputTokens &&
49
+ current.cachedInputTokens === previous.cachedInputTokens &&
50
+ current.outputTokens === previous.outputTokens &&
51
+ current.reasoningOutputTokens === previous.reasoningOutputTokens) {
52
+ return null;
53
+ }
54
+ // Total decreased → reset, treat current as full delta
55
+ if (totalOf(current) < totalOf(previous))
56
+ return current;
57
+ const delta = {
58
+ inputTokens: Math.max(0, current.inputTokens - previous.inputTokens),
59
+ cachedInputTokens: Math.max(0, current.cachedInputTokens - previous.cachedInputTokens),
60
+ outputTokens: Math.max(0, current.outputTokens - previous.outputTokens),
61
+ reasoningOutputTokens: Math.max(0, current.reasoningOutputTokens - previous.reasoningOutputTokens),
62
+ };
63
+ return isAllZero(delta) ? null : delta;
64
+ }
65
+ /**
66
+ * Parse a Gemini CLI session JSON file.
67
+ *
68
+ * Gemini stores one JSON file per session with a `messages[]` array.
69
+ * Each `type: "gemini"` message has cumulative `tokens` — we diff
70
+ * against the previous totals to compute per-message deltas.
71
+ */
72
+ export async function parseGeminiFile(opts) {
73
+ const { filePath } = opts;
74
+ let { startIndex, lastTotals } = opts;
75
+ const deltas = [];
76
+ let lastModel = null;
77
+ let raw;
78
+ try {
79
+ raw = await readFile(filePath, "utf8");
80
+ }
81
+ catch {
82
+ return { deltas, lastIndex: startIndex, lastTotals, lastModel };
83
+ }
84
+ if (!raw.trim()) {
85
+ return { deltas, lastIndex: startIndex, lastTotals, lastModel };
86
+ }
87
+ let session;
88
+ try {
89
+ session = JSON.parse(raw);
90
+ }
91
+ catch {
92
+ return { deltas, lastIndex: startIndex, lastTotals, lastModel };
93
+ }
94
+ const messages = Array.isArray(session?.messages)
95
+ ? session.messages
96
+ : [];
97
+ // Reset if lastIndex is beyond the array (file rewrite)
98
+ if (startIndex >= messages.length) {
99
+ startIndex = -1;
100
+ lastTotals = null;
101
+ }
102
+ let totals = lastTotals;
103
+ let model = lastModel;
104
+ const begin = startIndex + 1;
105
+ for (let idx = begin; idx < messages.length; idx++) {
106
+ const msg = messages[idx];
107
+ if (!msg || typeof msg !== "object")
108
+ continue;
109
+ // Track model
110
+ const msgModel = typeof msg.model === "string" ? msg.model.trim() : null;
111
+ if (msgModel)
112
+ model = msgModel;
113
+ // Extract timestamp
114
+ const timestamp = typeof msg.timestamp === "string" ? msg.timestamp : null;
115
+ // Normalize tokens
116
+ const currentTotals = normalizeGeminiTokens(msg.tokens);
117
+ if (!timestamp || !currentTotals) {
118
+ totals = currentTotals || totals;
119
+ continue;
120
+ }
121
+ // Compute delta
122
+ const delta = diffTotals(currentTotals, totals);
123
+ if (!delta || isAllZero(delta)) {
124
+ totals = currentTotals;
125
+ continue;
126
+ }
127
+ deltas.push({
128
+ source: "gemini-cli",
129
+ model: model || "unknown",
130
+ timestamp,
131
+ tokens: delta,
132
+ });
133
+ totals = currentTotals;
134
+ }
135
+ return {
136
+ deltas,
137
+ lastIndex: messages.length - 1,
138
+ lastTotals: totals,
139
+ lastModel: model,
140
+ };
141
+ }
142
+ //# sourceMappingURL=gemini.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../src/parsers/gemini.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { ParsedDelta } from "./claude.js";
2
+ /** Result of parsing an OpenClaw JSONL session file */
3
+ export interface OpenClawFileResult {
4
+ deltas: ParsedDelta[];
5
+ endOffset: number;
6
+ }
7
+ /**
8
+ * Parse an OpenClaw session JSONL file incrementally from a byte offset.
9
+ *
10
+ * OpenClaw writes one JSONL line per event. We look for `type: "message"`
11
+ * lines with `message.usage` containing absolute token counts (no diffing needed).
12
+ *
13
+ * OpenClaw normalization:
14
+ * input → inputTokens
15
+ * cacheRead + cacheWrite → cachedInputTokens
16
+ * output → outputTokens
17
+ * (hardcoded 0) → reasoningOutputTokens
18
+ */
19
+ export declare function parseOpenClawFile(opts: {
20
+ filePath: string;
21
+ startOffset: number;
22
+ }): Promise<OpenClawFileResult>;
23
+ //# sourceMappingURL=openclaw.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openclaw.d.ts","sourceRoot":"","sources":["../../src/parsers/openclaw.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,uDAAuD;AACvD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAYD;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAmE9B"}
@@ -0,0 +1,85 @@
1
+ import { createReadStream } from "node:fs";
2
+ import { stat } from "node:fs/promises";
3
+ import { createInterface } from "node:readline";
4
+ /** Check if a TokenDelta is all zeros */
5
+ function isAllZero(d) {
6
+ return (d.inputTokens === 0 &&
7
+ d.cachedInputTokens === 0 &&
8
+ d.outputTokens === 0 &&
9
+ d.reasoningOutputTokens === 0);
10
+ }
11
+ /**
12
+ * Parse an OpenClaw session JSONL file incrementally from a byte offset.
13
+ *
14
+ * OpenClaw writes one JSONL line per event. We look for `type: "message"`
15
+ * lines with `message.usage` containing absolute token counts (no diffing needed).
16
+ *
17
+ * OpenClaw normalization:
18
+ * input → inputTokens
19
+ * cacheRead + cacheWrite → cachedInputTokens
20
+ * output → outputTokens
21
+ * (hardcoded 0) → reasoningOutputTokens
22
+ */
23
+ export async function parseOpenClawFile(opts) {
24
+ const { filePath, startOffset } = opts;
25
+ const deltas = [];
26
+ const st = await stat(filePath).catch(() => null);
27
+ if (!st || !st.isFile())
28
+ return { deltas, endOffset: startOffset };
29
+ const endOffset = st.size;
30
+ if (startOffset >= endOffset)
31
+ return { deltas, endOffset };
32
+ const stream = createReadStream(filePath, {
33
+ encoding: "utf8",
34
+ start: startOffset,
35
+ });
36
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
37
+ try {
38
+ for await (const line of rl) {
39
+ if (!line)
40
+ continue;
41
+ // Fast-path: OpenClaw messages include "usage" and "totalTokens"
42
+ if (!line.includes('"usage"') || !line.includes("totalTokens"))
43
+ continue;
44
+ let obj;
45
+ try {
46
+ obj = JSON.parse(line);
47
+ }
48
+ catch {
49
+ continue;
50
+ }
51
+ if (obj?.type !== "message")
52
+ continue;
53
+ const msg = obj?.message;
54
+ if (!msg || typeof msg !== "object")
55
+ continue;
56
+ const usage = msg.usage;
57
+ if (!usage || typeof usage !== "object")
58
+ continue;
59
+ const timestamp = typeof obj?.timestamp === "string" ? obj.timestamp : null;
60
+ if (!timestamp)
61
+ continue;
62
+ const model = typeof msg.model === "string" ? msg.model.trim() : "unknown";
63
+ const tokens = {
64
+ inputTokens: Number(usage.input || 0),
65
+ cachedInputTokens: Number(usage.cacheRead || 0) + Number(usage.cacheWrite || 0),
66
+ outputTokens: Number(usage.output || 0),
67
+ reasoningOutputTokens: 0,
68
+ };
69
+ if (isAllZero(tokens))
70
+ continue;
71
+ deltas.push({
72
+ source: "openclaw",
73
+ model,
74
+ timestamp,
75
+ tokens,
76
+ });
77
+ }
78
+ }
79
+ finally {
80
+ rl.close();
81
+ stream.destroy();
82
+ }
83
+ return { deltas, endOffset };
84
+ }
85
+ //# sourceMappingURL=openclaw.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openclaw.js","sourceRoot":"","sources":["../../src/parsers/openclaw.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { TokenDelta } from "@pew/core";
2
+ import type { ParsedDelta } from "./claude.js";
3
+ /** Result of parsing a single OpenCode message file */
4
+ export interface OpenCodeFileResult {
5
+ delta: ParsedDelta | null;
6
+ messageKey: string | null;
7
+ lastTotals: TokenDelta | null;
8
+ }
9
+ /**
10
+ * Coerce an epoch value to milliseconds.
11
+ * Values < 1e12 are treated as seconds and multiplied by 1000.
12
+ */
13
+ export declare function coerceEpochMs(v: unknown): number;
14
+ /**
15
+ * Normalize OpenCode's token object to our TokenDelta format.
16
+ *
17
+ * OpenCode fields:
18
+ * input + cache.write → inputTokens
19
+ * cache.read → cachedInputTokens
20
+ * output → outputTokens
21
+ * reasoning → reasoningOutputTokens
22
+ */
23
+ export declare function normalizeOpenCodeTokens(tokens: Record<string, unknown> | null | undefined): TokenDelta | null;
24
+ /**
25
+ * Parse a single OpenCode message JSON file.
26
+ *
27
+ * Each file is a standalone JSON object for one message.
28
+ * Uses diff against previous totals to compute incremental deltas
29
+ * (reuses diffTotals from Gemini parser — same logic).
30
+ */
31
+ export declare function parseOpenCodeFile(opts: {
32
+ filePath: string;
33
+ lastTotals: TokenDelta | null;
34
+ }): Promise<OpenCodeFileResult>;
35
+ //# sourceMappingURL=opencode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../src/parsers/opencode.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAU,UAAU,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,uDAAuD;AACvD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;CAC/B;AASD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAKhD;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GACjD,UAAU,GAAG,IAAI,CAanB;AAYD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;CAC/B,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAwE9B"}