sigrank 0.15.5 → 0.16.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/README.md +132 -77
- package/adapters.mjs +508 -240
- package/badges.mjs +264 -0
- package/cascade.mjs +185 -54
- package/cli.mjs +1311 -893
- package/connect.mjs +2 -2
- package/index.mjs +78 -59
- package/keystore.mjs +64 -44
- package/narrate.mjs +34 -22
- package/package.json +4 -2
- package/preflight.mjs +191 -0
- package/sign.mjs +46 -27
- package/submit.mjs +166 -62
- package/tokenpull.mjs +337 -188
- package/tools.mjs +2154 -534
- package/tui.mjs +1860 -1081
package/adapters.mjs
CHANGED
|
@@ -32,46 +32,65 @@
|
|
|
32
32
|
* adapter object as `dataGap: string` so tokenpull() can surface them to the user.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
-
import { readdir, readFile } from
|
|
36
|
-
import { join,
|
|
37
|
-
import { homedir } from
|
|
38
|
-
import { execFile as execFileCb } from
|
|
39
|
-
import { promisify } from
|
|
35
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
36
|
+
import { join, dirname } from "node:path";
|
|
37
|
+
import { homedir } from "node:os";
|
|
38
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
39
|
+
import { promisify } from "node:util";
|
|
40
40
|
|
|
41
|
-
const execFileP = promisify(execFileCb)
|
|
42
|
-
const DAY_MS = 86_400_000 // shared with tokenpull.mjs but kept local to avoid circular import
|
|
41
|
+
const execFileP = promisify(execFileCb);
|
|
42
|
+
const DAY_MS = 86_400_000; // shared with tokenpull.mjs but kept local to avoid circular import
|
|
43
43
|
|
|
44
44
|
// ── File-system helpers ───────────────────────────────────────────────────────
|
|
45
45
|
|
|
46
46
|
/** Recursively yield every file whose name matches `pred` under dir (skips symlink dirs). */
|
|
47
47
|
async function* walkFiles(dir, pred, counter = { n: 0 }, max = 10_000) {
|
|
48
|
-
if (counter.n >= max) return
|
|
49
|
-
let entries
|
|
50
|
-
try {
|
|
48
|
+
if (counter.n >= max) return;
|
|
49
|
+
let entries;
|
|
50
|
+
try {
|
|
51
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
52
|
+
} catch {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
51
55
|
for (const e of entries) {
|
|
52
|
-
if (counter.n >= max) return
|
|
53
|
-
const full = join(dir, e.name)
|
|
54
|
-
if (e.isSymbolicLink()) continue
|
|
55
|
-
if (e.isDirectory()) {
|
|
56
|
-
|
|
56
|
+
if (counter.n >= max) return;
|
|
57
|
+
const full = join(dir, e.name);
|
|
58
|
+
if (e.isSymbolicLink()) continue;
|
|
59
|
+
if (e.isDirectory()) {
|
|
60
|
+
yield* walkFiles(full, pred, counter, max);
|
|
61
|
+
} else if (e.isFile() && pred(e.name)) {
|
|
62
|
+
counter.n++;
|
|
63
|
+
yield full;
|
|
64
|
+
}
|
|
57
65
|
}
|
|
58
66
|
}
|
|
59
67
|
|
|
60
|
-
const isJsonl = (n) =>
|
|
61
|
-
|
|
68
|
+
const isJsonl = (n) =>
|
|
69
|
+
n.endsWith(".jsonl") ||
|
|
70
|
+
n.endsWith(".jsonl.deleted") ||
|
|
71
|
+
n.match(/\.jsonl\.reset\.\d+$/);
|
|
72
|
+
const isJson = (n) => n.endsWith(".json") && !n.endsWith(".jsonl");
|
|
62
73
|
|
|
63
74
|
/** Read a file as UTF-8, return null on error. */
|
|
64
75
|
async function readUtf8(path) {
|
|
65
|
-
try {
|
|
76
|
+
try {
|
|
77
|
+
return await readFile(path, "utf8");
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
66
81
|
}
|
|
67
82
|
|
|
68
83
|
/** Parse each newline-delimited JSON line, yield parsed objects silently skipping bad lines. */
|
|
69
84
|
function* parseJsonl(text, filePath) {
|
|
70
|
-
if (!text) return
|
|
71
|
-
for (const line of text.split(
|
|
72
|
-
const s = line.trim()
|
|
73
|
-
if (!s) continue
|
|
74
|
-
try {
|
|
85
|
+
if (!text) return;
|
|
86
|
+
for (const line of text.split("\n")) {
|
|
87
|
+
const s = line.trim();
|
|
88
|
+
if (!s) continue;
|
|
89
|
+
try {
|
|
90
|
+
yield [JSON.parse(s), filePath];
|
|
91
|
+
} catch {
|
|
92
|
+
/* skip malformed */
|
|
93
|
+
}
|
|
75
94
|
}
|
|
76
95
|
}
|
|
77
96
|
|
|
@@ -80,223 +99,340 @@ function* parseJsonl(text, filePath) {
|
|
|
80
99
|
* (matches the execFile hardening used in tools.mjs / tokenpull.mjs). */
|
|
81
100
|
async function sqliteJson(dbPath, sql) {
|
|
82
101
|
try {
|
|
83
|
-
const { stdout } = await execFileP(
|
|
84
|
-
|
|
85
|
-
|
|
102
|
+
const { stdout } = await execFileP("sqlite3", ["-json", dbPath, sql], {
|
|
103
|
+
timeout: 10_000,
|
|
104
|
+
});
|
|
105
|
+
return JSON.parse(stdout || "[]");
|
|
106
|
+
} catch {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
86
109
|
}
|
|
87
110
|
|
|
88
111
|
// ── Env-var helper ────────────────────────────────────────────────────────────
|
|
89
112
|
/** Resolve roots from env var or default. Supports comma-separated list. */
|
|
90
113
|
function roots(envVar, defaultPath) {
|
|
91
|
-
const v = process.env[envVar]
|
|
92
|
-
if (v)
|
|
93
|
-
|
|
114
|
+
const v = process.env[envVar];
|
|
115
|
+
if (v)
|
|
116
|
+
return v
|
|
117
|
+
.split(",")
|
|
118
|
+
.map((s) => s.trim())
|
|
119
|
+
.filter(Boolean);
|
|
120
|
+
return [defaultPath];
|
|
94
121
|
}
|
|
95
122
|
|
|
96
123
|
// ── 1. Amp ────────────────────────────────────────────────────────────────────
|
|
97
124
|
// ~/.local/share/amp/threads/**/*.json
|
|
98
125
|
// Fields: assistant message usage: input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens
|
|
99
126
|
export const ampAdapter = {
|
|
100
|
-
platform:
|
|
101
|
-
defaultRoot: () => join(homedir(),
|
|
127
|
+
platform: "amp",
|
|
128
|
+
defaultRoot: () => join(homedir(), ".local", "share", "amp"),
|
|
102
129
|
async *messages(root) {
|
|
103
|
-
for (const r of roots(
|
|
104
|
-
const threadsDir = join(r,
|
|
130
|
+
for (const r of roots("AMP_DATA_DIR", root)) {
|
|
131
|
+
const threadsDir = join(r, "threads");
|
|
105
132
|
for await (const path of walkFiles(threadsDir, isJson)) {
|
|
106
|
-
const text = await readUtf8(path)
|
|
107
|
-
if (!text) continue
|
|
108
|
-
let thread
|
|
109
|
-
try {
|
|
133
|
+
const text = await readUtf8(path);
|
|
134
|
+
if (!text) continue;
|
|
135
|
+
let thread;
|
|
136
|
+
try {
|
|
137
|
+
thread = JSON.parse(text);
|
|
138
|
+
} catch {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
110
141
|
// Amp thread is an array or object with messages
|
|
111
|
-
const msgs = Array.isArray(thread) ? thread :
|
|
142
|
+
const msgs = Array.isArray(thread) ? thread : thread.messages || [];
|
|
112
143
|
for (const msg of msgs) {
|
|
113
|
-
if (!msg || msg.role !==
|
|
114
|
-
const u = msg.usage || (msg.metadata && msg.metadata.usage) || {}
|
|
115
|
-
const input
|
|
116
|
-
const output = Number(u.output_tokens || u.outputTokens || 0)
|
|
117
|
-
const cacheCreate = Number(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
144
|
+
if (!msg || msg.role !== "assistant") continue;
|
|
145
|
+
const u = msg.usage || (msg.metadata && msg.metadata.usage) || {};
|
|
146
|
+
const input = Number(u.input_tokens || u.inputTokens || 0);
|
|
147
|
+
const output = Number(u.output_tokens || u.outputTokens || 0);
|
|
148
|
+
const cacheCreate = Number(
|
|
149
|
+
u.cache_creation_tokens || u.cacheCreationTokens || 0,
|
|
150
|
+
);
|
|
151
|
+
const cacheRead = Number(
|
|
152
|
+
u.cache_read_tokens || u.cacheReadTokens || 0,
|
|
153
|
+
);
|
|
154
|
+
if (input + output + cacheCreate + cacheRead === 0) continue;
|
|
155
|
+
yield {
|
|
156
|
+
id: msg.id || null,
|
|
157
|
+
sid: thread.id || null,
|
|
158
|
+
ts: msg.timestamp || msg.created_at || null,
|
|
159
|
+
input,
|
|
160
|
+
output,
|
|
161
|
+
cacheCreate,
|
|
162
|
+
cacheRead,
|
|
163
|
+
file: path,
|
|
164
|
+
};
|
|
121
165
|
}
|
|
122
166
|
}
|
|
123
167
|
}
|
|
124
168
|
},
|
|
125
|
-
}
|
|
169
|
+
};
|
|
126
170
|
|
|
127
171
|
// ── 2. Kimi ───────────────────────────────────────────────────────────────────
|
|
128
172
|
// ~/.kimi/sessions/<group-id>/<session-id>/wire.jsonl
|
|
129
173
|
// StatusUpdate lines only; token_usage: { input_other, output, input_cache_read, input_cache_creation }
|
|
130
174
|
export const kimiAdapter = {
|
|
131
|
-
platform:
|
|
132
|
-
defaultRoot: () => join(homedir(),
|
|
175
|
+
platform: "kimi",
|
|
176
|
+
defaultRoot: () => join(homedir(), ".kimi"),
|
|
133
177
|
async *messages(root) {
|
|
134
|
-
const sessionsDir = join(roots(
|
|
178
|
+
const sessionsDir = join(roots("KIMI_DATA_DIR", root)[0], "sessions");
|
|
135
179
|
for await (const path of walkFiles(sessionsDir, isJsonl)) {
|
|
136
|
-
const text = await readUtf8(path)
|
|
180
|
+
const text = await readUtf8(path);
|
|
137
181
|
for (const [ev] of parseJsonl(text, path)) {
|
|
138
|
-
if (!ev || ev.type !==
|
|
139
|
-
const u = ev.token_usage || {}
|
|
140
|
-
const input
|
|
141
|
-
const output = Number(u.output || 0)
|
|
142
|
-
const cacheCreate = Number(u.input_cache_creation || 0)
|
|
143
|
-
const cacheRead
|
|
144
|
-
if (input + output + cacheCreate + cacheRead === 0) continue
|
|
145
|
-
yield {
|
|
182
|
+
if (!ev || ev.type !== "StatusUpdate") continue;
|
|
183
|
+
const u = ev.token_usage || {};
|
|
184
|
+
const input = Number(u.input_other || 0);
|
|
185
|
+
const output = Number(u.output || 0);
|
|
186
|
+
const cacheCreate = Number(u.input_cache_creation || 0);
|
|
187
|
+
const cacheRead = Number(u.input_cache_read || 0);
|
|
188
|
+
if (input + output + cacheCreate + cacheRead === 0) continue;
|
|
189
|
+
yield {
|
|
190
|
+
id: ev.id || null,
|
|
191
|
+
sid: null,
|
|
192
|
+
ts: ev.timestamp || ev.created_at || null,
|
|
193
|
+
input,
|
|
194
|
+
output,
|
|
195
|
+
cacheCreate,
|
|
196
|
+
cacheRead,
|
|
197
|
+
file: path,
|
|
198
|
+
};
|
|
146
199
|
}
|
|
147
200
|
}
|
|
148
201
|
},
|
|
149
|
-
}
|
|
202
|
+
};
|
|
150
203
|
|
|
151
204
|
// ── 3. Qwen ───────────────────────────────────────────────────────────────────
|
|
152
205
|
// ~/.qwen/projects/{project}/chats/*.jsonl
|
|
153
206
|
// usageMetadata: { promptTokenCount, candidatesTokenCount, cachedContentTokenCount, thoughtsTokenCount }
|
|
154
207
|
// No cache creation field. Reasoning (thoughtsTokenCount) → output.
|
|
155
208
|
export const qwenAdapter = {
|
|
156
|
-
platform:
|
|
209
|
+
platform: "qwen",
|
|
157
210
|
estimated: true, // no cacheCreate in logs
|
|
158
|
-
defaultRoot: () => join(homedir(),
|
|
211
|
+
defaultRoot: () => join(homedir(), ".qwen"),
|
|
159
212
|
async *messages(root) {
|
|
160
|
-
const projectsDir = join(roots(
|
|
213
|
+
const projectsDir = join(roots("QWEN_DATA_DIR", root)[0], "projects");
|
|
161
214
|
for await (const path of walkFiles(projectsDir, isJsonl)) {
|
|
162
|
-
const text = await readUtf8(path)
|
|
215
|
+
const text = await readUtf8(path);
|
|
163
216
|
for (const [ev] of parseJsonl(text, path)) {
|
|
164
|
-
if (!ev || !ev.usageMetadata) continue
|
|
165
|
-
const u = ev.usageMetadata
|
|
166
|
-
const rawInput
|
|
167
|
-
const rawOutput = Number(u.candidatesTokenCount || 0)
|
|
168
|
-
const thoughts
|
|
169
|
-
const cacheRead = Number(u.cachedContentTokenCount || 0)
|
|
217
|
+
if (!ev || !ev.usageMetadata) continue;
|
|
218
|
+
const u = ev.usageMetadata;
|
|
219
|
+
const rawInput = Number(u.promptTokenCount || 0);
|
|
220
|
+
const rawOutput = Number(u.candidatesTokenCount || 0);
|
|
221
|
+
const thoughts = Number(u.thoughtsTokenCount || 0); // reasoning → output
|
|
222
|
+
const cacheRead = Number(u.cachedContentTokenCount || 0);
|
|
170
223
|
// promptTokenCount is inclusive of cached; subtract to get fresh input
|
|
171
|
-
const input
|
|
172
|
-
const output = rawOutput + thoughts
|
|
173
|
-
if (input + output + cacheRead === 0) continue
|
|
174
|
-
yield {
|
|
224
|
+
const input = Math.max(0, rawInput - cacheRead);
|
|
225
|
+
const output = rawOutput + thoughts;
|
|
226
|
+
if (input + output + cacheRead === 0) continue;
|
|
227
|
+
yield {
|
|
228
|
+
id: ev.id || null,
|
|
229
|
+
sid: null,
|
|
230
|
+
ts: ev.timestamp || null,
|
|
231
|
+
input,
|
|
232
|
+
output,
|
|
233
|
+
cacheCreate: 0,
|
|
234
|
+
cacheRead,
|
|
235
|
+
file: path,
|
|
236
|
+
};
|
|
175
237
|
}
|
|
176
238
|
}
|
|
177
239
|
},
|
|
178
|
-
}
|
|
240
|
+
};
|
|
179
241
|
|
|
180
242
|
// ── 4. pi-agent ───────────────────────────────────────────────────────────────
|
|
181
243
|
// ~/.pi/agent/sessions/**/*.jsonl
|
|
182
244
|
// Fields: inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens
|
|
183
245
|
export const piAdapter = {
|
|
184
|
-
platform:
|
|
185
|
-
defaultRoot: () => join(homedir(),
|
|
246
|
+
platform: "pi",
|
|
247
|
+
defaultRoot: () => join(homedir(), ".pi", "agent", "sessions"),
|
|
186
248
|
async *messages(root) {
|
|
187
|
-
for (const r of roots(
|
|
249
|
+
for (const r of roots("PI_AGENT_DIR", root)) {
|
|
188
250
|
for await (const path of walkFiles(r, isJsonl)) {
|
|
189
|
-
const text = await readUtf8(path)
|
|
251
|
+
const text = await readUtf8(path);
|
|
190
252
|
for (const [ev] of parseJsonl(text, path)) {
|
|
191
|
-
if (!ev) continue
|
|
253
|
+
if (!ev) continue;
|
|
192
254
|
// pi-agent stores usage in assistant messages or usage events
|
|
193
|
-
const u = ev.usage || ev
|
|
194
|
-
const input
|
|
195
|
-
const output = Number(u.outputTokens || u.output_tokens || 0)
|
|
196
|
-
const cacheCreate = Number(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
255
|
+
const u = ev.usage || ev;
|
|
256
|
+
const input = Number(u.inputTokens || u.input_tokens || 0);
|
|
257
|
+
const output = Number(u.outputTokens || u.output_tokens || 0);
|
|
258
|
+
const cacheCreate = Number(
|
|
259
|
+
u.cacheCreationTokens || u.cache_creation_tokens || 0,
|
|
260
|
+
);
|
|
261
|
+
const cacheRead = Number(
|
|
262
|
+
u.cacheReadTokens || u.cache_read_tokens || 0,
|
|
263
|
+
);
|
|
264
|
+
if (input + output + cacheCreate + cacheRead === 0) continue;
|
|
265
|
+
yield {
|
|
266
|
+
id: ev.id || null,
|
|
267
|
+
sid: ev.sessionId || null,
|
|
268
|
+
ts: ev.timestamp || null,
|
|
269
|
+
input,
|
|
270
|
+
output,
|
|
271
|
+
cacheCreate,
|
|
272
|
+
cacheRead,
|
|
273
|
+
file: path,
|
|
274
|
+
};
|
|
200
275
|
}
|
|
201
276
|
}
|
|
202
277
|
}
|
|
203
278
|
},
|
|
204
|
-
}
|
|
279
|
+
};
|
|
205
280
|
|
|
206
281
|
// ── 5. OpenClaw ───────────────────────────────────────────────────────────────
|
|
207
282
|
// ~/.openclaw/ (also ~/.clawdbot/, ~/.moltbot/, ~/.moldbot/)
|
|
208
283
|
// agents/<agentId>/sessions/<uuid>.jsonl (+ .deleted.<ts> and .reset.<ts> variants)
|
|
209
284
|
// Per-message: input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens
|
|
210
285
|
export const openclawAdapter = {
|
|
211
|
-
platform:
|
|
286
|
+
platform: "openclaw",
|
|
212
287
|
defaultRoot: () => {
|
|
213
|
-
const env = process.env.OPENCLAW_DIR
|
|
214
|
-
if (env) return env.split(
|
|
215
|
-
|
|
216
|
-
const p = join(homedir(), d)
|
|
217
|
-
return p // return first; walkFiles silently skips missing
|
|
218
|
-
}
|
|
219
|
-
return join(homedir(), '.openclaw')
|
|
288
|
+
const env = process.env.OPENCLAW_DIR;
|
|
289
|
+
if (env) return env.split(",")[0].trim();
|
|
290
|
+
return join(homedir(), ".openclaw");
|
|
220
291
|
},
|
|
221
292
|
async *messages(root) {
|
|
222
293
|
const dirs = process.env.OPENCLAW_DIR
|
|
223
|
-
? process.env.OPENCLAW_DIR.split(
|
|
224
|
-
: [
|
|
225
|
-
|
|
294
|
+
? process.env.OPENCLAW_DIR.split(",").map((s) => s.trim())
|
|
295
|
+
: [
|
|
296
|
+
join(homedir(), ".openclaw"),
|
|
297
|
+
join(homedir(), ".clawdbot"),
|
|
298
|
+
join(homedir(), ".moltbot"),
|
|
299
|
+
join(homedir(), ".moldbot"),
|
|
300
|
+
];
|
|
301
|
+
const pred = (n) => isJsonl(n) || n.endsWith(".json"); // covers archived variants
|
|
226
302
|
for (const dir of dirs) {
|
|
227
303
|
for await (const path of walkFiles(dir, pred)) {
|
|
228
|
-
const text = await readUtf8(path)
|
|
304
|
+
const text = await readUtf8(path);
|
|
229
305
|
for (const [ev] of parseJsonl(text, path)) {
|
|
230
|
-
if (!ev || ev.role !==
|
|
231
|
-
const u = ev.usage || ev.tokens || {}
|
|
232
|
-
const input
|
|
233
|
-
const output = Number(u.output_tokens || u.outputTokens || 0)
|
|
234
|
-
const cacheCreate = Number(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
306
|
+
if (!ev || ev.role !== "assistant") continue;
|
|
307
|
+
const u = ev.usage || ev.tokens || {};
|
|
308
|
+
const input = Number(u.input_tokens || u.inputTokens || 0);
|
|
309
|
+
const output = Number(u.output_tokens || u.outputTokens || 0);
|
|
310
|
+
const cacheCreate = Number(
|
|
311
|
+
u.cache_creation_tokens || u.cacheCreationTokens || 0,
|
|
312
|
+
);
|
|
313
|
+
const cacheRead = Number(
|
|
314
|
+
u.cache_read_tokens || u.cacheReadTokens || 0,
|
|
315
|
+
);
|
|
316
|
+
if (input + output + cacheCreate + cacheRead === 0) continue;
|
|
317
|
+
yield {
|
|
318
|
+
id: ev.id || null,
|
|
319
|
+
sid: null,
|
|
320
|
+
ts: ev.timestamp || null,
|
|
321
|
+
input,
|
|
322
|
+
output,
|
|
323
|
+
cacheCreate,
|
|
324
|
+
cacheRead,
|
|
325
|
+
file: path,
|
|
326
|
+
};
|
|
238
327
|
}
|
|
239
328
|
}
|
|
240
329
|
}
|
|
241
330
|
},
|
|
242
|
-
}
|
|
331
|
+
};
|
|
243
332
|
|
|
244
333
|
// ── 6. Droid ──────────────────────────────────────────────────────────────────
|
|
245
334
|
// ~/.factory/sessions/**/*.settings.json
|
|
246
335
|
// Fields: input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens, thinking_tokens
|
|
247
336
|
// Thinking tokens → output. Per-settings-file (session-level granularity).
|
|
337
|
+
// CUMULATIVE-COLUMN GUARD: settings files contain session-level totals. If the same session
|
|
338
|
+
// appears in multiple files, summing double-counts. We set both id and sid to the session id
|
|
339
|
+
// so tokenpull()'s (sid|id) keep-last dedup collapses duplicates to the latest values.
|
|
248
340
|
export const droidAdapter = {
|
|
249
|
-
platform:
|
|
250
|
-
defaultRoot: () => join(homedir(),
|
|
341
|
+
platform: "droid",
|
|
342
|
+
defaultRoot: () => join(homedir(), ".factory", "sessions"),
|
|
251
343
|
async *messages(root) {
|
|
252
|
-
for (const r of roots(
|
|
253
|
-
for await (const path of walkFiles(r, (n) =>
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
344
|
+
for (const r of roots("DROID_SESSIONS_DIR", root)) {
|
|
345
|
+
for await (const path of walkFiles(r, (n) =>
|
|
346
|
+
n.endsWith(".settings.json"),
|
|
347
|
+
)) {
|
|
348
|
+
const text = await readUtf8(path);
|
|
349
|
+
if (!text) continue;
|
|
350
|
+
let s;
|
|
351
|
+
try {
|
|
352
|
+
s = JSON.parse(text);
|
|
353
|
+
} catch {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const input = Number(s.input_tokens || 0);
|
|
357
|
+
const output =
|
|
358
|
+
Number(s.output_tokens || 0) + Number(s.thinking_tokens || 0);
|
|
359
|
+
const cacheCreate = Number(s.cache_creation_tokens || 0);
|
|
360
|
+
const cacheRead = Number(s.cache_read_tokens || 0);
|
|
361
|
+
if (input + output + cacheCreate + cacheRead === 0) continue;
|
|
362
|
+
const sessionId = String(s.session_id || s.id || "");
|
|
363
|
+
yield {
|
|
364
|
+
id: sessionId,
|
|
365
|
+
sid: sessionId,
|
|
366
|
+
ts: s.updated_at || s.created_at || null,
|
|
367
|
+
input,
|
|
368
|
+
output,
|
|
369
|
+
cacheCreate,
|
|
370
|
+
cacheRead,
|
|
371
|
+
file: path,
|
|
372
|
+
};
|
|
264
373
|
}
|
|
265
374
|
}
|
|
266
375
|
},
|
|
267
|
-
}
|
|
376
|
+
};
|
|
268
377
|
|
|
269
378
|
// ── 7. Codebuff ───────────────────────────────────────────────────────────────
|
|
270
379
|
// ~/.config/manicode/projects/<project>/chats/<chat-id>/chat-messages.json
|
|
271
380
|
// assistant messages: metadata.usage or metadata.codebuff.usage
|
|
272
381
|
export const codebuffAdapter = {
|
|
273
|
-
platform:
|
|
274
|
-
defaultRoot: () => join(homedir(),
|
|
382
|
+
platform: "codebuff",
|
|
383
|
+
defaultRoot: () => join(homedir(), ".config", "manicode"),
|
|
275
384
|
async *messages(root) {
|
|
276
385
|
const dirs = process.env.CODEBUFF_DATA_DIR
|
|
277
|
-
? process.env.CODEBUFF_DATA_DIR.split(
|
|
278
|
-
: [
|
|
386
|
+
? process.env.CODEBUFF_DATA_DIR.split(",").map((s) => s.trim())
|
|
387
|
+
: [
|
|
388
|
+
join(homedir(), ".config", "manicode"),
|
|
389
|
+
join(homedir(), ".config", "manicode-dev"),
|
|
390
|
+
join(homedir(), ".config", "manicode-staging"),
|
|
391
|
+
];
|
|
279
392
|
for (const dir of dirs) {
|
|
280
|
-
for await (const path of walkFiles(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (!
|
|
393
|
+
for await (const path of walkFiles(
|
|
394
|
+
dir,
|
|
395
|
+
(n) => n === "chat-messages.json",
|
|
396
|
+
)) {
|
|
397
|
+
const text = await readUtf8(path);
|
|
398
|
+
if (!text) continue;
|
|
399
|
+
let msgs;
|
|
400
|
+
try {
|
|
401
|
+
msgs = JSON.parse(text);
|
|
402
|
+
} catch {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (!Array.isArray(msgs)) continue;
|
|
286
406
|
for (const msg of msgs) {
|
|
287
|
-
if (!msg || msg.role !==
|
|
288
|
-
const u =
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
407
|
+
if (!msg || msg.role !== "assistant") continue;
|
|
408
|
+
const u =
|
|
409
|
+
(msg.metadata &&
|
|
410
|
+
(msg.metadata.usage || msg.metadata.codebuff?.usage)) ||
|
|
411
|
+
{};
|
|
412
|
+
const input = Number(u.input_tokens || u.inputTokens || 0);
|
|
413
|
+
const output = Number(u.output_tokens || u.outputTokens || 0);
|
|
414
|
+
const cacheCreate = Number(
|
|
415
|
+
u.cache_creation_tokens || u.cacheCreationTokens || 0,
|
|
416
|
+
);
|
|
417
|
+
const cacheRead = Number(
|
|
418
|
+
u.cache_read_tokens || u.cacheReadTokens || 0,
|
|
419
|
+
);
|
|
420
|
+
if (input + output + cacheCreate + cacheRead === 0) continue;
|
|
421
|
+
yield {
|
|
422
|
+
id: msg.id || null,
|
|
423
|
+
sid: null,
|
|
424
|
+
ts: msg.timestamp || msg.created_at || null,
|
|
425
|
+
input,
|
|
426
|
+
output,
|
|
427
|
+
cacheCreate,
|
|
428
|
+
cacheRead,
|
|
429
|
+
file: path,
|
|
430
|
+
};
|
|
295
431
|
}
|
|
296
432
|
}
|
|
297
433
|
}
|
|
298
434
|
},
|
|
299
|
-
}
|
|
435
|
+
};
|
|
300
436
|
|
|
301
437
|
// ── 8. Gemini CLI ─────────────────────────────────────────────────────────────
|
|
302
438
|
// ~/.gemini/tmp/*/chats/*.json and *.jsonl
|
|
@@ -304,179 +440,311 @@ export const codebuffAdapter = {
|
|
|
304
440
|
// SigRank mapping: input = input−cached (fresh), cacheRead = cached, cacheCreate = 0 (not exposed),
|
|
305
441
|
// output = output + thought (reasoning→output)
|
|
306
442
|
export const geminiAdapter = {
|
|
307
|
-
platform:
|
|
443
|
+
platform: "gemini",
|
|
308
444
|
estimated: true, // no cacheCreate field in Gemini logs
|
|
309
|
-
defaultRoot: () => join(homedir(),
|
|
445
|
+
defaultRoot: () => join(homedir(), ".gemini", "tmp"),
|
|
310
446
|
async *messages(root) {
|
|
311
|
-
for (const r of roots(
|
|
312
|
-
for await (const path of walkFiles(
|
|
313
|
-
|
|
314
|
-
|
|
447
|
+
for (const r of roots("GEMINI_DATA_DIR", root)) {
|
|
448
|
+
for await (const path of walkFiles(
|
|
449
|
+
r,
|
|
450
|
+
(n) => n.endsWith(".json") || n.endsWith(".jsonl"),
|
|
451
|
+
)) {
|
|
452
|
+
const text = await readUtf8(path);
|
|
453
|
+
if (!text) continue;
|
|
315
454
|
// Try JSONL first, then single JSON
|
|
316
|
-
let parsed = []
|
|
455
|
+
let parsed = [];
|
|
317
456
|
try {
|
|
318
|
-
parsed = text
|
|
457
|
+
parsed = text
|
|
458
|
+
.trim()
|
|
459
|
+
.split("\n")
|
|
460
|
+
.filter(Boolean)
|
|
461
|
+
.map((l) => JSON.parse(l));
|
|
319
462
|
} catch {
|
|
320
|
-
try {
|
|
463
|
+
try {
|
|
464
|
+
parsed = [JSON.parse(text)];
|
|
465
|
+
} catch {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
321
468
|
}
|
|
322
469
|
for (const ev of parsed) {
|
|
323
|
-
if (!ev) continue
|
|
470
|
+
if (!ev) continue;
|
|
324
471
|
// Gemini usage may be at top level or nested in usageMetadata
|
|
325
|
-
const u = ev.usageMetadata || ev.usage || ev
|
|
326
|
-
const rawInput
|
|
327
|
-
const rawOutput = Number(u.output || u.candidatesTokenCount || 0)
|
|
328
|
-
const cached
|
|
329
|
-
const thought
|
|
330
|
-
if (rawInput + rawOutput + cached + thought === 0) continue
|
|
331
|
-
const input
|
|
332
|
-
const output = rawOutput + thought
|
|
333
|
-
yield {
|
|
472
|
+
const u = ev.usageMetadata || ev.usage || ev;
|
|
473
|
+
const rawInput = Number(u.input || u.promptTokenCount || 0);
|
|
474
|
+
const rawOutput = Number(u.output || u.candidatesTokenCount || 0);
|
|
475
|
+
const cached = Number(u.cached || u.cachedContentTokenCount || 0);
|
|
476
|
+
const thought = Number(u.thought || u.thoughtsTokenCount || 0);
|
|
477
|
+
if (rawInput + rawOutput + cached + thought === 0) continue;
|
|
478
|
+
const input = Math.max(0, rawInput - cached); // strip cached from input
|
|
479
|
+
const output = rawOutput + thought; // reasoning → output
|
|
480
|
+
yield {
|
|
481
|
+
id: ev.id || null,
|
|
482
|
+
sid: null,
|
|
483
|
+
ts: ev.timestamp || null,
|
|
484
|
+
input,
|
|
485
|
+
output,
|
|
486
|
+
cacheCreate: 0,
|
|
487
|
+
cacheRead: cached,
|
|
488
|
+
file: path,
|
|
489
|
+
};
|
|
334
490
|
}
|
|
335
491
|
}
|
|
336
492
|
}
|
|
337
493
|
},
|
|
338
|
-
}
|
|
494
|
+
};
|
|
339
495
|
|
|
340
496
|
// ── 9. GitHub Copilot CLI ─────────────────────────────────────────────────────
|
|
341
497
|
// ~/.copilot/otel/*.jsonl (requires COPILOT_OTEL_ENABLED=true before session start)
|
|
342
498
|
// OpenTelemetry spans; looks for llm.token_count.prompt / completion / cached attributes
|
|
343
499
|
export const copilotAdapter = {
|
|
344
|
-
platform:
|
|
345
|
-
defaultRoot: () => join(homedir(),
|
|
500
|
+
platform: "copilot",
|
|
501
|
+
defaultRoot: () => join(homedir(), ".copilot", "otel"),
|
|
346
502
|
async *messages(root) {
|
|
347
503
|
const dir = process.env.COPILOT_OTEL_FILE_EXPORTER_PATH
|
|
348
|
-
?
|
|
349
|
-
: root
|
|
504
|
+
? dirname(process.env.COPILOT_OTEL_FILE_EXPORTER_PATH)
|
|
505
|
+
: root;
|
|
350
506
|
for await (const path of walkFiles(dir, isJsonl)) {
|
|
351
|
-
const text = await readUtf8(path)
|
|
507
|
+
const text = await readUtf8(path);
|
|
352
508
|
for (const [ev] of parseJsonl(text, path)) {
|
|
353
|
-
if (!ev) continue
|
|
509
|
+
if (!ev) continue;
|
|
354
510
|
// OTel span: attributes may be an object or array of {key,value} pairs
|
|
355
|
-
const attrs = ev.attributes || ev.resource?.attributes || {}
|
|
511
|
+
const attrs = ev.attributes || ev.resource?.attributes || {};
|
|
356
512
|
const get = (k) => {
|
|
357
|
-
if (typeof attrs ===
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
513
|
+
if (typeof attrs === "object" && !Array.isArray(attrs))
|
|
514
|
+
return attrs[k];
|
|
515
|
+
if (Array.isArray(attrs)) {
|
|
516
|
+
const a = attrs.find((x) => x.key === k);
|
|
517
|
+
return a?.value?.intValue ?? a?.value?.stringValue ?? null;
|
|
518
|
+
}
|
|
519
|
+
return null;
|
|
520
|
+
};
|
|
521
|
+
const input = Number(
|
|
522
|
+
get("llm.token_count.prompt") ||
|
|
523
|
+
get("gen_ai.usage.input_tokens") ||
|
|
524
|
+
0,
|
|
525
|
+
);
|
|
526
|
+
const output = Number(
|
|
527
|
+
get("llm.token_count.completion") ||
|
|
528
|
+
get("gen_ai.usage.output_tokens") ||
|
|
529
|
+
0,
|
|
530
|
+
);
|
|
531
|
+
const cacheCreate = Number(get("llm.token_count.cache_creation") || 0);
|
|
532
|
+
const cacheRead = Number(
|
|
533
|
+
get("llm.token_count.cache_read") ||
|
|
534
|
+
get("gen_ai.usage.cache_read_input_tokens") ||
|
|
535
|
+
0,
|
|
536
|
+
);
|
|
537
|
+
if (input + output + cacheCreate + cacheRead === 0) continue;
|
|
538
|
+
yield {
|
|
539
|
+
id: ev.traceId || ev.spanId || null,
|
|
540
|
+
sid: null,
|
|
541
|
+
ts: ev.startTimeUnixNano
|
|
542
|
+
? new Date(Number(ev.startTimeUnixNano) / 1e6).toISOString()
|
|
543
|
+
: null,
|
|
544
|
+
input,
|
|
545
|
+
output,
|
|
546
|
+
cacheCreate,
|
|
547
|
+
cacheRead,
|
|
548
|
+
file: path,
|
|
549
|
+
};
|
|
367
550
|
}
|
|
368
551
|
}
|
|
369
552
|
},
|
|
370
|
-
setupNote:
|
|
371
|
-
|
|
553
|
+
setupNote:
|
|
554
|
+
"Requires COPILOT_OTEL_ENABLED=true and COPILOT_OTEL_EXPORTER_TYPE=file set BEFORE starting the Copilot session. Without this, no local token logs are written.",
|
|
555
|
+
};
|
|
372
556
|
|
|
373
557
|
// ── 10. OpenCode ──────────────────────────────────────────────────────────────
|
|
374
558
|
// ~/.local/share/opencode — JSON message files, but costs stored as 0 and token fields
|
|
375
559
|
// are calculated via LiteLLM pricing (not stored in the log). No raw token fields.
|
|
376
560
|
// SigRank cannot derive pillars from OpenCode logs with current log format.
|
|
377
561
|
export const opencodeAdapter = {
|
|
378
|
-
platform:
|
|
379
|
-
defaultRoot: () => join(homedir(),
|
|
562
|
+
platform: "opencode",
|
|
563
|
+
defaultRoot: () => join(homedir(), ".local", "share", "opencode"),
|
|
380
564
|
estimated: true,
|
|
381
|
-
dataGap:
|
|
565
|
+
dataGap:
|
|
566
|
+
"OpenCode logs store cost:0 and derive tokens via LiteLLM at runtime — raw token counts are not persisted. SigRank cannot read pillars from OpenCode logs with the current format. Track https://github.com/ccusage/ccusage for format changes.",
|
|
382
567
|
// eslint-disable-next-line require-yield
|
|
383
|
-
async *messages() {
|
|
384
|
-
|
|
568
|
+
async *messages() {
|
|
569
|
+
/* no data available */
|
|
570
|
+
},
|
|
571
|
+
};
|
|
385
572
|
|
|
386
573
|
// ── 11. Goose ─────────────────────────────────────────────────────────────────
|
|
387
574
|
// SQLite: sessions.db at standard Goose data roots or $GOOSE_PATH_ROOT/data/sessions/sessions.db
|
|
388
575
|
// Columns: accumulated_input_tokens (or input_tokens), accumulated_output_tokens (or output_tokens),
|
|
389
576
|
// accumulated_total_tokens (or total_tokens). NO cache fields. Reasoning = total-input-output.
|
|
577
|
+
// CUMULATIVE-COLUMN GUARD: accumulated_* columns are per-session running totals. If the sessions
|
|
578
|
+
// table has multiple rows per session id, summing them downstream double-counts. We set both sid
|
|
579
|
+
// and id to the session id and ORDER BY updated_at so tokenpull()'s (sid|id) keep-last dedup
|
|
580
|
+
// collapses duplicate session rows to the latest (max accumulated) values.
|
|
390
581
|
export const gooseAdapter = {
|
|
391
|
-
platform:
|
|
582
|
+
platform: "goose",
|
|
392
583
|
estimated: true, // no cacheCreate or cacheRead
|
|
393
584
|
defaultRoot: () => {
|
|
394
|
-
const env = process.env.GOOSE_PATH_ROOT
|
|
395
|
-
if (env) return env
|
|
585
|
+
const env = process.env.GOOSE_PATH_ROOT;
|
|
586
|
+
if (env) return env;
|
|
396
587
|
// Standard locations (macOS first, then XDG)
|
|
397
|
-
return join(homedir(),
|
|
588
|
+
return join(homedir(), "Library", "Application Support", "goose");
|
|
398
589
|
},
|
|
399
590
|
async *messages(root) {
|
|
400
591
|
const dbCandidates = process.env.GOOSE_PATH_ROOT
|
|
401
|
-
? [join(process.env.GOOSE_PATH_ROOT,
|
|
592
|
+
? [join(process.env.GOOSE_PATH_ROOT, "data", "sessions", "sessions.db")]
|
|
402
593
|
: [
|
|
403
|
-
join(
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
594
|
+
join(
|
|
595
|
+
homedir(),
|
|
596
|
+
"Library",
|
|
597
|
+
"Application Support",
|
|
598
|
+
"goose",
|
|
599
|
+
"sessions",
|
|
600
|
+
"sessions.db",
|
|
601
|
+
),
|
|
602
|
+
join(
|
|
603
|
+
homedir(),
|
|
604
|
+
".local",
|
|
605
|
+
"share",
|
|
606
|
+
"goose",
|
|
607
|
+
"sessions",
|
|
608
|
+
"sessions.db",
|
|
609
|
+
),
|
|
610
|
+
join(
|
|
611
|
+
homedir(),
|
|
612
|
+
".local",
|
|
613
|
+
"share",
|
|
614
|
+
"Block",
|
|
615
|
+
"goose",
|
|
616
|
+
"sessions",
|
|
617
|
+
"sessions.db",
|
|
618
|
+
),
|
|
619
|
+
];
|
|
407
620
|
for (const db of dbCandidates) {
|
|
408
|
-
|
|
621
|
+
// ORDER BY updated_at so the latest cumulative row per session is yielded last
|
|
622
|
+
// (tokenpull keep-last dedup picks the final row = max accumulated values).
|
|
623
|
+
const rows = await sqliteJson(
|
|
624
|
+
db,
|
|
625
|
+
"SELECT * FROM sessions ORDER BY updated_at",
|
|
626
|
+
);
|
|
409
627
|
for (const row of rows) {
|
|
410
|
-
const input
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
628
|
+
const input = Number(
|
|
629
|
+
row.accumulated_input_tokens || row.input_tokens || 0,
|
|
630
|
+
);
|
|
631
|
+
const output = Number(
|
|
632
|
+
row.accumulated_output_tokens || row.output_tokens || 0,
|
|
633
|
+
);
|
|
634
|
+
const total = Number(
|
|
635
|
+
row.accumulated_total_tokens || row.total_tokens || 0,
|
|
636
|
+
);
|
|
637
|
+
const reasoning = Math.max(0, total - input - output); // folded into output
|
|
638
|
+
if (input + output === 0) continue;
|
|
639
|
+
const sessionId = String(row.id || row.session_id || "");
|
|
640
|
+
yield {
|
|
641
|
+
id: sessionId,
|
|
642
|
+
sid: sessionId,
|
|
643
|
+
ts: row.created_at || row.updated_at || null,
|
|
644
|
+
input,
|
|
645
|
+
output: output + reasoning,
|
|
646
|
+
cacheCreate: 0,
|
|
647
|
+
cacheRead: 0,
|
|
648
|
+
file: db,
|
|
649
|
+
};
|
|
416
650
|
}
|
|
417
651
|
}
|
|
418
652
|
},
|
|
419
|
-
}
|
|
653
|
+
};
|
|
420
654
|
|
|
421
655
|
// ── 12. Kilo ──────────────────────────────────────────────────────────────────
|
|
422
656
|
// SQLite: ~/.local/share/kilo/kilo.db
|
|
423
657
|
// Per-message rows with model, input/output/cache token columns.
|
|
424
658
|
export const kiloAdapter = {
|
|
425
|
-
platform:
|
|
426
|
-
defaultRoot: () => join(homedir(),
|
|
659
|
+
platform: "kilo",
|
|
660
|
+
defaultRoot: () => join(homedir(), ".local", "share", "kilo"),
|
|
427
661
|
async *messages(root) {
|
|
428
|
-
const dbPath = join(roots(
|
|
429
|
-
const rows = await sqliteJson(
|
|
662
|
+
const dbPath = join(roots("KILO_DATA_DIR", root)[0], "kilo.db");
|
|
663
|
+
const rows = await sqliteJson(
|
|
664
|
+
dbPath,
|
|
665
|
+
'SELECT * FROM messages WHERE role="assistant"',
|
|
666
|
+
);
|
|
430
667
|
for (const row of rows) {
|
|
431
|
-
const input
|
|
432
|
-
const output = Number(row.output_tokens || row.outputTokens || 0)
|
|
433
|
-
const cacheCreate = Number(
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
668
|
+
const input = Number(row.input_tokens || row.inputTokens || 0);
|
|
669
|
+
const output = Number(row.output_tokens || row.outputTokens || 0);
|
|
670
|
+
const cacheCreate = Number(
|
|
671
|
+
row.cache_creation_tokens || row.cacheCreationTokens || 0,
|
|
672
|
+
);
|
|
673
|
+
const cacheRead = Number(
|
|
674
|
+
row.cache_read_tokens || row.cacheReadTokens || 0,
|
|
675
|
+
);
|
|
676
|
+
if (input + output + cacheCreate + cacheRead === 0) continue;
|
|
677
|
+
yield {
|
|
678
|
+
id: String(row.id || ""),
|
|
679
|
+
sid: String(row.session_id || row.sessionId || ""),
|
|
680
|
+
ts: row.created_at || row.timestamp || null,
|
|
681
|
+
input,
|
|
682
|
+
output,
|
|
683
|
+
cacheCreate,
|
|
684
|
+
cacheRead,
|
|
685
|
+
file: dbPath,
|
|
686
|
+
};
|
|
437
687
|
}
|
|
438
688
|
},
|
|
439
|
-
}
|
|
689
|
+
};
|
|
440
690
|
|
|
441
691
|
// ── 13. Hermes Agent ─────────────────────────────────────────────────────────
|
|
442
692
|
// SQLite: ~/.hermes/state.db
|
|
443
693
|
// Per-session rows: input, output, cache_read, cache_write (=cacheCreate), reasoning_tokens → output
|
|
694
|
+
// CUMULATIVE-COLUMN GUARD: session rows may contain cumulative totals. If the same session
|
|
695
|
+
// appears in multiple rows, summing double-counts. We set both id and sid to the session id
|
|
696
|
+
// and ORDER BY updated_at so tokenpull()'s (sid|id) keep-last dedup collapses duplicates
|
|
697
|
+
// to the latest values.
|
|
444
698
|
export const hermesAdapter = {
|
|
445
|
-
platform:
|
|
446
|
-
defaultRoot: () => join(homedir(),
|
|
699
|
+
platform: "hermes",
|
|
700
|
+
defaultRoot: () => join(homedir(), ".hermes"),
|
|
447
701
|
async *messages(root) {
|
|
448
|
-
for (const r of roots(
|
|
449
|
-
const dbPath = join(r,
|
|
450
|
-
|
|
702
|
+
for (const r of roots("HERMES_HOME", root)) {
|
|
703
|
+
const dbPath = join(r, "state.db");
|
|
704
|
+
// ORDER BY updated_at so the latest cumulative row per session is yielded last.
|
|
705
|
+
const rows = await sqliteJson(
|
|
706
|
+
dbPath,
|
|
707
|
+
"SELECT * FROM sessions ORDER BY updated_at",
|
|
708
|
+
);
|
|
451
709
|
for (const row of rows) {
|
|
452
|
-
const input
|
|
453
|
-
const reasoning = Number(row.reasoning_tokens || 0)
|
|
454
|
-
const output
|
|
455
|
-
const cacheCreate = Number(row.cache_write || row.cache_creation || 0)
|
|
456
|
-
const cacheRead
|
|
457
|
-
if (input + output + cacheCreate + cacheRead === 0) continue
|
|
458
|
-
|
|
710
|
+
const input = Number(row.input || 0);
|
|
711
|
+
const reasoning = Number(row.reasoning_tokens || 0);
|
|
712
|
+
const output = Number(row.output || 0) + reasoning;
|
|
713
|
+
const cacheCreate = Number(row.cache_write || row.cache_creation || 0);
|
|
714
|
+
const cacheRead = Number(row.cache_read || 0);
|
|
715
|
+
if (input + output + cacheCreate + cacheRead === 0) continue;
|
|
716
|
+
const sessionId = String(row.id || row.session_id || "");
|
|
717
|
+
yield {
|
|
718
|
+
id: sessionId,
|
|
719
|
+
sid: sessionId,
|
|
720
|
+
ts: row.created_at || row.updated_at || null,
|
|
721
|
+
input,
|
|
722
|
+
output,
|
|
723
|
+
cacheCreate,
|
|
724
|
+
cacheRead,
|
|
725
|
+
file: dbPath,
|
|
726
|
+
};
|
|
459
727
|
}
|
|
460
728
|
}
|
|
461
729
|
},
|
|
462
|
-
}
|
|
730
|
+
};
|
|
463
731
|
|
|
464
732
|
// ── Registry ──────────────────────────────────────────────────────────────────
|
|
465
733
|
/** All non-Claude, non-Codex adapters keyed by platform ID. */
|
|
466
734
|
export const ADAPTERS = {
|
|
467
|
-
amp:
|
|
468
|
-
kimi:
|
|
469
|
-
qwen:
|
|
470
|
-
pi:
|
|
735
|
+
amp: ampAdapter,
|
|
736
|
+
kimi: kimiAdapter,
|
|
737
|
+
qwen: qwenAdapter,
|
|
738
|
+
pi: piAdapter,
|
|
471
739
|
openclaw: openclawAdapter,
|
|
472
|
-
droid:
|
|
740
|
+
droid: droidAdapter,
|
|
473
741
|
codebuff: codebuffAdapter,
|
|
474
|
-
gemini:
|
|
475
|
-
copilot:
|
|
742
|
+
gemini: geminiAdapter,
|
|
743
|
+
copilot: copilotAdapter,
|
|
476
744
|
opencode: opencodeAdapter,
|
|
477
|
-
goose:
|
|
478
|
-
kilo:
|
|
479
|
-
hermes:
|
|
480
|
-
}
|
|
745
|
+
goose: gooseAdapter,
|
|
746
|
+
kilo: kiloAdapter,
|
|
747
|
+
hermes: hermesAdapter,
|
|
748
|
+
};
|
|
481
749
|
|
|
482
|
-
export const ALL_PLATFORMS = Object.keys(ADAPTERS).concat([
|
|
750
|
+
export const ALL_PLATFORMS = Object.keys(ADAPTERS).concat(["claude", "codex"]);
|